From 4079123c93bed52b653a8d53f4520aafc33c16b9 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 19 Aug 2023 18:21:21 -0500 Subject: [PATCH 001/258] =?UTF-8?q?WIP:=20New=20app=20=E2=80=9Cstamplog?= =?UTF-8?q?=E2=80=9D,=20with=20partial=20implementation=20of=20main=20scre?= =?UTF-8?q?en?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/stamplog/app.js | 340 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 apps/stamplog/app.js diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js new file mode 100644 index 0000000000..df11c5f154 --- /dev/null +++ b/apps/stamplog/app.js @@ -0,0 +1,340 @@ +Layout = require('Layout'); +locale = require('locale'); +storage = require('Storage'); + +// Storage filename to store user's timestamp log +const LOG_FILENAME = 'stamplog.json'; + +// Min number of pixels of movement to recognize a touchscreen drag/swipe +const DRAG_THRESHOLD = 10; + +var settings = { + logItemFont: '12x20' +}; + + +// Fetch a stringified image +function getIcon(id) { + if (id == 'add') { +// Graphics.createImage(` +// XX X X X X +// XX X X X X +// XXXXXX X X X X +// XXXXXX X X X X +// XX X X X X +// XX X X X X +// X XX X X +// X X X X +// X XX X +// X X X +// X X +// X XX +// XXX XX +// XXXXX +// XXXX +// XX +// `); + return "\0\x17\x10\x81\x000\t\x12`$K\xF0\x91'\xE2D\x83\t\x12\x06$H\x00\xB1 \x01$\x80\x042\x00\b(\x00 \x00A\x80\x01\xCC\x00\x03\xE0\x00\x0F\x00\x00\x18\x00\x00"; + } else if (id == 'menu') { +// Graphics.createImage(` +// +// +// +// +// XXXXXXXXXXXXXXXX +// XXXXXXXXXXXXXXXX +// +// +// XXXXXXXXXXXXXXXX +// XXXXXXXXXXXXXXXX +// +// +// XXXXXXXXXXXXXXXX +// XXXXXXXXXXXXXXXX +// +// +// `); + return "\0\x10\x10\x81\0\0\0\0\0\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0"; + } +} + + +//// Data models ////////////////////////////////// + +// High-level timestamp log object that provides an interface to the +// UI for managing log entries and automatically loading/saving +// changes to flash storage. +class StampLog { + constructor(filename) { + // Name of file to save log to + this.filename = filename; + + // `true` when we have changes that need to be saved + this.isDirty = false; + // Wait at most this many msec upon first data change before + // saving (this is to avoid excessive writes to flash if several + // changes happen quickly; we wait a little bit so they can be + // rolled into a single write) + this.saveTimeout = 30000; + // setTimeout ID for scheduled save job + this.saveId = null; + // Underlying raw log data object. Outside this class it's + // recommended to use only the class methods to change it rather + // than modifying the object directly to ensure that changes are + // recognized and saved to storage. + this.log = this.load(); + } + + // Return the version of the log data that is currently in storage + load() { + let log = storage.readJSON(this.filename, true); + if (!log) log = []; + // Convert stringified datetimes back into Date objects + for (let logEntry of log) { + logEntry.stamp = new Date(logEntry.stamp); + } + return log; + } + + // Write current log data to storage if anything needs to be saved + save() { + // Cancel any pending scheduled calls to save() + if (this.saveId) { + clearTimeout(this.saveId); + this.saveId = null; + } + + if (this.isDirty) { + if (storage.writeJSON(this.filename, this.log)) { + console.log('stamplog: save to storage completed'); + this.isDirty = false; + } else { + console.log('stamplog: save to storage FAILED'); + } + } else { + console.log('stamplog: skipping save to storage because no changes made'); + } + } + + // Mark log as needing to be (re)written to storage + setDirty() { + this.isDirty = true; + if (!this.saveId) { + this.saveId = setTimeout(this.save.bind(this), this.saveTimeout); + } + } + + // Add a timestamp for the current time to the end of the log + addEntry() { + this.log.push({ + stamp: new Date() + }); + this.setDirty(); + } + + // Delete the log objects given in the array `entries` from the log + deleteEntries(entries) { + this.log = this.log.filter(entry => !entries.includes(entry)); + this.setDirty(); + } +} + + +//// UI /////////////////////////////////////////// + +// UI layout render callback for log entries +function renderLogItem(elem) { + if (elem.item) { + g.setColor(g.theme.bg) + .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) + .setFont('12x20') + .setFontAlign(-1, -1) + .setColor(g.theme.fg) + .drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y) + .drawString(locale.date(elem.item.stamp, 1) + + '\n' + + locale.time(elem.item.stamp).trim(), + elem.x, elem.y); + } else { + g.setColor(g.blendColor(g.theme.bg, g.theme.fg, 0.25)) + .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1); + } +} + +// Main app screen interface, launched by calling start() +class MainScreen { + constructor(stampLog) { + this.stampLog = stampLog; + + // Values set up by start() + this.logItemsPerPage = null; + this.logScrollPos = null; + this.layout = null; + + // Handlers/listeners + this.buttonTimeoutId = null; + this.listeners = {}; + } + + // Launch this UI and make it live + start() { + this.layout = this.getLayout(); + mainScreen.scrollLog('b'); + mainScreen.render(); + + Object.assign(this.listeners, this.getTouchListeners()); + Bangle.on('drag', this.listeners.drag); + } + + // Stop this UI, shut down all timers/listeners, and otherwise clean up + stop() { + if (this.buttonTimeoutId) { + clearTimeout(this.buttonTimeoutId); + this.buttonTimeoutId = null; + } + + // Kill layout handlers + Bangle.removeListener('drag', this.listeners.drag); + Bangle.setUI(); + + // Probably not necessary, but provides feedback for debugging :-) + g.clear(); + } + + // Generate the layout structure for the main UI + getLayout() { + let layout = new Layout( + {type: 'v', + c: [ + // Placeholder to force bottom alignment when there is unused + // vertical screen space + {type: '', id: 'placeholder', fillx: 1, filly: 1}, + + {type: 'v', + id: 'logItems', + + // To be filled in with log item elements once we determine + // how many will fit on screen + c: [], + }, + + {type: 'h', + id: 'buttons', + c: [ + {type: 'btn', font: '6x8:2', fillx: 1, label: '+ XX:XX', id: 'addBtn', + cb: this.addTimestamp.bind(this)}, + {type: 'btn', font: '6x8:2', label: getIcon('menu'), id: 'menuBtn', + cb: L => console.log(L)}, + ], + }, + ], + } + ); + + // Calculate how many log items per page we have space to display + layout.update(); + let availableHeight = layout.placeholder.h; + g.setFont(settings.logItemFont); + let logItemHeight = g.getFontHeight() * 2; + this.logItemsPerPage = Math.floor(availableHeight / logItemHeight); + + // Populate log items in layout + for (i = 0; i < this.logItemsPerPage; i++) { + layout.logItems.c.push( + {type: 'custom', render: renderLogItem, item: undefined, fillx: 1, height: logItemHeight} + ); + } + layout.update(); + + return layout; + } + + // Redraw a particular display `item`, or everything if `item` is falsey + render(item) { + if (!item || item == 'log') { + let layLogItems = this.layout.logItems; + let logIdx = this.logScrollPos - this.logItemsPerPage; + for (let elem of layLogItems.c) { + logIdx++; + elem.item = this.stampLog.log[logIdx]; + } + this.layout.render(layLogItems); + } + + if (!item || item == 'buttons') { + this.layout.addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim(); + this.layout.render(this.layout.buttons); + + // Auto-update time of day indication on log-add button upon next minute + if (!this.buttonTimeoutId) { + this.buttonTimeoutId = setTimeout( + () => { + this.buttonTimeoutId = null; + this.render('buttons'); + }, + 60000 - (Date.now() % 60000) + ); + } + } + + } + + getTouchListeners() { + let distanceY = null; + + function dragHandler(ev) { + // Handle up/down swipes for scrolling + if (ev.b) { + if (distanceY === null) { + // Drag started + distanceY = ev.dy; + } else { + // Drag in progress + distanceY += ev.dy; + } + } else { + // Drag ended + if (Math.abs(distanceY) > DRAG_THRESHOLD) { + this.scrollLog(distanceY > 0 ? 'u' : 'd'); + this.render('log'); + } + distanceY = null; + } + } + + return { + 'drag': dragHandler.bind(this), + }; + } + + // Add current timestamp to log and update UI display + addTimestamp() { + this.stampLog.addEntry(); + this.scrollLog('b'); + this.render('log'); + } + + // Scroll display in given direction or to given position: + // 'u': up, 'd': down, 't': to top, 'b': to bottom + scrollLog(how) { + top = (this.stampLog.log.length - 1) % this.logItemsPerPage; + bottom = this.stampLog.log.length - 1; + + if (how == 'u') { + this.logScrollPos -= this.logItemsPerPage; + } else if (how == 'd') { + this.logScrollPos += this.logItemsPerPage; + } else if (how == 't') { + this.logScrollPos = top; + } else if (how == 'b') { + this.logScrollPos = bottom; + } + + this.logScrollPos = E.clip(this.logScrollPos, top, bottom); + } +} + + +stampLog = new StampLog(); +mainScreen = new MainScreen(stampLog); +mainScreen.start(); From a7024aa52b57c7659a81d2fb3fe4e615a1878e1b Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 21 Aug 2023 15:01:42 -0500 Subject: [PATCH 002/258] Save log on quit and display some kind of alert if error occurs saving --- apps/stamplog/app.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index df11c5f154..f147774487 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -110,6 +110,7 @@ class StampLog { this.isDirty = false; } else { console.log('stamplog: save to storage FAILED'); + this.emit('saveError'); } } else { console.log('stamplog: skipping save to storage because no changes made'); @@ -335,6 +336,15 @@ class MainScreen { } +Bangle.loadWidgets(); +Bangle.drawWidgets(); + stampLog = new StampLog(); +E.on('kill', stampLog.save.bind(stampLog)); +stampLog.on('saveError', () => { + E.showAlert('Trouble saving timestamp log: Data may be lost!', + "Can't save log"); +}); + mainScreen = new MainScreen(stampLog); mainScreen.start(); From 3b5fe9c4a8e1a55f80c45d06f7e661922aaa5214 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 24 Aug 2023 15:12:25 -0500 Subject: [PATCH 003/258] Fix accidental use of global var instead of this --- apps/stamplog/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index f147774487..6263fc85a9 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -180,8 +180,8 @@ class MainScreen { // Launch this UI and make it live start() { this.layout = this.getLayout(); - mainScreen.scrollLog('b'); - mainScreen.render(); + this.scrollLog('b'); + this.render(); Object.assign(this.listeners, this.getTouchListeners()); Bangle.on('drag', this.listeners.drag); From 705d6619a8d1c3b960ebeb1f49850d8f57f6f353 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 24 Aug 2023 15:14:34 -0500 Subject: [PATCH 004/258] Make save error fit screen better and try to avoid disrupting current UI --- apps/stamplog/app.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 6263fc85a9..d01102dd6b 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -197,9 +197,6 @@ class MainScreen { // Kill layout handlers Bangle.removeListener('drag', this.listeners.drag); Bangle.setUI(); - - // Probably not necessary, but provides feedback for debugging :-) - g.clear(); } // Generate the layout structure for the main UI @@ -336,15 +333,25 @@ class MainScreen { } +function saveErrorAlert() { + currentUI.stop(); + E.showPrompt( + 'Trouble saving timestamp log; data may be lost!', + {title: "Can't save log", + img: '', + buttons: {'Ok': true}, + } + ).then(currentUI.start.bind(currentUI)); +} + + Bangle.loadWidgets(); Bangle.drawWidgets(); stampLog = new StampLog(); E.on('kill', stampLog.save.bind(stampLog)); -stampLog.on('saveError', () => { - E.showAlert('Trouble saving timestamp log: Data may be lost!', - "Can't save log"); -}); +stampLog.on('saveError', saveErrorAlert); + +var currentUI = new MainScreen(stampLog); +currentUI.start(); -mainScreen = new MainScreen(stampLog); -mainScreen.start(); From 575c09436303e574785607ce0dd954009992bf44 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 25 Aug 2023 23:30:54 -0500 Subject: [PATCH 005/258] Minor refactoring --- apps/stamplog/app.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index d01102dd6b..02698cdaae 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -164,6 +164,7 @@ function renderLogItem(elem) { // Main app screen interface, launched by calling start() class MainScreen { + constructor(stampLog) { this.stampLog = stampLog; @@ -179,12 +180,11 @@ class MainScreen { // Launch this UI and make it live start() { - this.layout = this.getLayout(); + this._initLayout(); this.scrollLog('b'); this.render(); - Object.assign(this.listeners, this.getTouchListeners()); - Bangle.on('drag', this.listeners.drag); + this._initTouch(); } // Stop this UI, shut down all timers/listeners, and otherwise clean up @@ -199,8 +199,7 @@ class MainScreen { Bangle.setUI(); } - // Generate the layout structure for the main UI - getLayout() { + _initLayout() { let layout = new Layout( {type: 'v', c: [ @@ -244,7 +243,7 @@ class MainScreen { } layout.update(); - return layout; + this.layout = layout; } // Redraw a particular display `item`, or everything if `item` is falsey @@ -263,7 +262,8 @@ class MainScreen { this.layout.addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim(); this.layout.render(this.layout.buttons); - // Auto-update time of day indication on log-add button upon next minute + // Auto-update time of day indication on log-add button upon + // next minute if (!this.buttonTimeoutId) { this.buttonTimeoutId = setTimeout( () => { @@ -277,7 +277,7 @@ class MainScreen { } - getTouchListeners() { + _initTouch() { let distanceY = null; function dragHandler(ev) { @@ -300,9 +300,8 @@ class MainScreen { } } - return { - 'drag': dragHandler.bind(this), - }; + this.listeners.drag = dragHandler.bind(this); + Bangle.on('drag', this.listeners.drag); } // Add current timestamp to log and update UI display @@ -335,12 +334,12 @@ class MainScreen { function saveErrorAlert() { currentUI.stop(); + // Not `showAlert` because the icon plus message don't fit the + // screen well E.showPrompt( 'Trouble saving timestamp log; data may be lost!', {title: "Can't save log", - img: '', - buttons: {'Ok': true}, - } + buttons: {'Ok': true}} ).then(currentUI.start.bind(currentUI)); } From 1307d3b21c76bb23ac31ec8c8d3e991422867c75 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 27 Aug 2023 21:53:03 -0500 Subject: [PATCH 006/258] Implement scroll bar for log display --- apps/stamplog/app.js | 86 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 02698cdaae..2e05dddc71 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -8,6 +8,9 @@ const LOG_FILENAME = 'stamplog.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe const DRAG_THRESHOLD = 10; +// Width of scroll indicators +const SCROLL_BAR_WIDTH = 12; + var settings = { logItemFont: '12x20' }; @@ -162,6 +165,42 @@ function renderLogItem(elem) { } } +// Render a scroll indicator +// `scroll` format: { +// pos: int, +// min: int, +// max: int, +// itemsPerPage: int, +// } +function renderScrollBar(elem, scroll) { + const border = 1; + const boxArea = elem.h - 2 * border; + const boxSize = E.clip( + Math.round( + scroll.itemsPerPage / (scroll.max - scroll.min + 1) * (elem.h - 2) + ), + 3, + boxArea + ); + const boxTop = (scroll.max - scroll.min) ? + Math.round( + (scroll.pos - scroll.min) / (scroll.max - scroll.min) + * (boxArea - boxSize) + elem.y + border + ) : elem.y + border; + + // Draw border + g.setColor(g.theme.fg) + .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) + // Draw scroll box area + .setColor(g.theme.bg) + .fillRect(elem.x + border, elem.y + border, + elem.x + elem.w - border - 1, elem.y + elem.h - border - 1) + // Draw scroll box + .setColor(g.blendColor(g.theme.bg, g.theme.fg, 0.5)) + .fillRect(elem.x + border, boxTop, + elem.x + elem.w - border - 1, boxTop + boxSize - 1); +} + // Main app screen interface, launched by calling start() class MainScreen { @@ -207,14 +246,21 @@ class MainScreen { // vertical screen space {type: '', id: 'placeholder', fillx: 1, filly: 1}, - {type: 'v', - id: 'logItems', - - // To be filled in with log item elements once we determine - // how many will fit on screen - c: [], + {type: 'h', + c: [ + {type: 'v', + id: 'logItems', + + // To be filled in with log item elements once we + // determine how many will fit on screen + c: [], + }, + {type: 'custom', + id: 'logScroll', + render: elem => { renderScrollBar(elem, this.logScrollInfo()); } + }, + ], }, - {type: 'h', id: 'buttons', c: [ @@ -241,6 +287,8 @@ class MainScreen { {type: 'custom', render: renderLogItem, item: undefined, fillx: 1, height: logItemHeight} ); } + layout.logScroll.height = logItemHeight * this.logItemsPerPage; + layout.logScroll.width = SCROLL_BAR_WIDTH; layout.update(); this.layout = layout; @@ -256,6 +304,7 @@ class MainScreen { elem.item = this.stampLog.log[logIdx]; } this.layout.render(layLogItems); + this.layout.render(this.layout.logScroll); } if (!item || item == 'buttons') { @@ -311,23 +360,32 @@ class MainScreen { this.render('log'); } + // Get scroll information for log display + logScrollInfo() { + return { + pos: this.logScrollPos, + min: (this.stampLog.log.length - 1) % this.logItemsPerPage, + max: this.stampLog.log.length - 1, + itemsPerPage: this.logItemsPerPage + }; + } + // Scroll display in given direction or to given position: // 'u': up, 'd': down, 't': to top, 'b': to bottom scrollLog(how) { - top = (this.stampLog.log.length - 1) % this.logItemsPerPage; - bottom = this.stampLog.log.length - 1; + let scroll = this.logScrollInfo(); if (how == 'u') { - this.logScrollPos -= this.logItemsPerPage; + this.logScrollPos -= scroll.itemsPerPage; } else if (how == 'd') { - this.logScrollPos += this.logItemsPerPage; + this.logScrollPos += scroll.itemsPerPage; } else if (how == 't') { - this.logScrollPos = top; + this.logScrollPos = scroll.min; } else if (how == 'b') { - this.logScrollPos = bottom; + this.logScrollPos = scroll.max; } - this.logScrollPos = E.clip(this.logScrollPos, top, bottom); + this.logScrollPos = E.clip(this.logScrollPos, scroll.min, scroll.max); } } From b64806e96a2f3cbabb7b2ba5ffc5f6b7328471e4 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 27 Aug 2023 21:55:40 -0500 Subject: [PATCH 007/258] Shorten some names --- apps/stamplog/app.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 2e05dddc71..c979bf3490 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -208,8 +208,8 @@ class MainScreen { this.stampLog = stampLog; // Values set up by start() - this.logItemsPerPage = null; - this.logScrollPos = null; + this.itemsPerPage = null; + this.scrollPos = null; this.layout = null; // Handlers/listeners @@ -220,7 +220,7 @@ class MainScreen { // Launch this UI and make it live start() { this._initLayout(); - this.scrollLog('b'); + this.scroll('b'); this.render(); this._initTouch(); @@ -257,7 +257,7 @@ class MainScreen { }, {type: 'custom', id: 'logScroll', - render: elem => { renderScrollBar(elem, this.logScrollInfo()); } + render: elem => { renderScrollBar(elem, this.scrollInfo()); } }, ], }, @@ -279,15 +279,15 @@ class MainScreen { let availableHeight = layout.placeholder.h; g.setFont(settings.logItemFont); let logItemHeight = g.getFontHeight() * 2; - this.logItemsPerPage = Math.floor(availableHeight / logItemHeight); + this.itemsPerPage = Math.floor(availableHeight / logItemHeight); // Populate log items in layout - for (i = 0; i < this.logItemsPerPage; i++) { + for (i = 0; i < this.itemsPerPage; i++) { layout.logItems.c.push( {type: 'custom', render: renderLogItem, item: undefined, fillx: 1, height: logItemHeight} ); } - layout.logScroll.height = logItemHeight * this.logItemsPerPage; + layout.logScroll.height = logItemHeight * this.itemsPerPage; layout.logScroll.width = SCROLL_BAR_WIDTH; layout.update(); @@ -298,7 +298,7 @@ class MainScreen { render(item) { if (!item || item == 'log') { let layLogItems = this.layout.logItems; - let logIdx = this.logScrollPos - this.logItemsPerPage; + let logIdx = this.scrollPos - this.itemsPerPage; for (let elem of layLogItems.c) { logIdx++; elem.item = this.stampLog.log[logIdx]; @@ -342,7 +342,7 @@ class MainScreen { } else { // Drag ended if (Math.abs(distanceY) > DRAG_THRESHOLD) { - this.scrollLog(distanceY > 0 ? 'u' : 'd'); + this.scroll(distanceY > 0 ? 'u' : 'd'); this.render('log'); } distanceY = null; @@ -356,36 +356,36 @@ class MainScreen { // Add current timestamp to log and update UI display addTimestamp() { this.stampLog.addEntry(); - this.scrollLog('b'); + this.scroll('b'); this.render('log'); } // Get scroll information for log display - logScrollInfo() { + scrollInfo() { return { - pos: this.logScrollPos, - min: (this.stampLog.log.length - 1) % this.logItemsPerPage, + pos: this.scrollPos, + min: (this.stampLog.log.length - 1) % this.itemsPerPage, max: this.stampLog.log.length - 1, - itemsPerPage: this.logItemsPerPage + itemsPerPage: this.itemsPerPage }; } // Scroll display in given direction or to given position: // 'u': up, 'd': down, 't': to top, 'b': to bottom - scrollLog(how) { - let scroll = this.logScrollInfo(); + scroll(how) { + let scroll = this.scrollInfo(); if (how == 'u') { - this.logScrollPos -= scroll.itemsPerPage; + this.scrollPos -= scroll.itemsPerPage; } else if (how == 'd') { - this.logScrollPos += scroll.itemsPerPage; + this.scrollPos += scroll.itemsPerPage; } else if (how == 't') { - this.logScrollPos = scroll.min; + this.scrollPos = scroll.min; } else if (how == 'b') { - this.logScrollPos = scroll.max; + this.scrollPos = scroll.max; } - this.logScrollPos = E.clip(this.logScrollPos, scroll.min, scroll.max); + this.scrollPos = E.clip(this.scrollPos, scroll.min, scroll.max); } } From 12f3a46a7e61598f7fabab5c4b9a4957b3ef72b9 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 27 Aug 2023 21:58:10 -0500 Subject: [PATCH 008/258] Auto render log when scroll() called --- apps/stamplog/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index c979bf3490..0b74fc2e15 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -221,7 +221,7 @@ class MainScreen { start() { this._initLayout(); this.scroll('b'); - this.render(); + this.render('buttons'); this._initTouch(); } @@ -343,7 +343,6 @@ class MainScreen { // Drag ended if (Math.abs(distanceY) > DRAG_THRESHOLD) { this.scroll(distanceY > 0 ? 'u' : 'd'); - this.render('log'); } distanceY = null; } @@ -357,7 +356,6 @@ class MainScreen { addTimestamp() { this.stampLog.addEntry(); this.scroll('b'); - this.render('log'); } // Get scroll information for log display @@ -386,6 +384,8 @@ class MainScreen { } this.scrollPos = E.clip(this.scrollPos, scroll.min, scroll.max); + + this.render('log'); } } From e8fc765943316d89dbe449f4257d5ac3f8540764 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 27 Aug 2023 22:28:05 -0500 Subject: [PATCH 009/258] Improve scrolling: haptic feedback, with multiple steps per swipe available --- apps/stamplog/app.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 0b74fc2e15..4776e173f7 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -6,7 +6,7 @@ storage = require('Storage'); const LOG_FILENAME = 'stamplog.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe -const DRAG_THRESHOLD = 10; +const DRAG_THRESHOLD = 30; // Width of scroll indicators const SCROLL_BAR_WIDTH = 12; @@ -340,10 +340,13 @@ class MainScreen { distanceY += ev.dy; } } else { - // Drag ended - if (Math.abs(distanceY) > DRAG_THRESHOLD) { - this.scroll(distanceY > 0 ? 'u' : 'd'); - } + // Drag released + distanceY = null; + } + if (Math.abs(distanceY) > DRAG_THRESHOLD) { + // Scroll threshold reached + Bangle.buzz(50, .2); + this.scroll(distanceY > 0 ? 'u' : 'd'); distanceY = null; } } From 43727c1fdbeab688aaf020e1bdaa9cc75d6e5b48 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 2 Sep 2023 17:14:50 -0500 Subject: [PATCH 010/258] Implement fixed log size (new entries replace old) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also fix saving log to Storage file named “undefined” instead of correct name --- apps/stamplog/app.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 4776e173f7..71269fb439 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -12,7 +12,8 @@ const DRAG_THRESHOLD = 30; const SCROLL_BAR_WIDTH = 12; var settings = { - logItemFont: '12x20' + logItemFont: '12x20', + maxLogLength: 6 }; @@ -68,9 +69,12 @@ function getIcon(id) { // UI for managing log entries and automatically loading/saving // changes to flash storage. class StampLog { - constructor(filename) { + constructor(filename, maxLength) { // Name of file to save log to this.filename = filename; + // Maximum entries for log before old entries are overwritten with + // newer ones + this.maxLength = maxLength; // `true` when we have changes that need to be saved this.isDirty = false; @@ -130,6 +134,13 @@ class StampLog { // Add a timestamp for the current time to the end of the log addEntry() { + // If log full, purge an old entry to make room for new one + if (this.maxLength) { + while (this.log.length + 1 > this.maxLength) { + this.log.shift(); + } + } + // Add new entry this.log.push({ stamp: new Date() }); @@ -408,7 +419,7 @@ function saveErrorAlert() { Bangle.loadWidgets(); Bangle.drawWidgets(); -stampLog = new StampLog(); +stampLog = new StampLog(LOG_FILENAME, settings.maxLogLength); E.on('kill', stampLog.save.bind(stampLog)); stampLog.on('saveError', saveErrorAlert); From a4a5e1eaf62d8571083584dd36ea5c3b15bddec5 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 2 Sep 2023 18:19:45 -0500 Subject: [PATCH 011/258] Fix scrollbar geometry Nitpick: scrollbar box was larger than the true percentage of screen pages displayed because we weren't taking into account that we only scroll by full screens at a time. --- apps/stamplog/app.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 71269fb439..7b5975f5fd 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -268,7 +268,7 @@ class MainScreen { }, {type: 'custom', id: 'logScroll', - render: elem => { renderScrollBar(elem, this.scrollInfo()); } + render: elem => { renderScrollBar(elem, this.scrollBarInfo()); } }, ], }, @@ -382,6 +382,25 @@ class MainScreen { }; } + // Like scrollInfo, but adjust the data so as to suggest scrollbar + // geometry that accurately reflects the nature of the scrolling + // (page by page rather than item by item) + scrollBarInfo() { + const info = this.scrollInfo(); + + function toPage(scrollPos) { + return Math.floor(scrollPos / info.itemsPerPage); + } + + return { + // Define 1 “screenfull” as the unit here + itemsPerPage: 1, + pos: toPage(info.pos), + min: toPage(info.min), + max: toPage(info.max), + }; + } + // Scroll display in given direction or to given position: // 'u': up, 'd': down, 't': to top, 'b': to bottom scroll(how) { From a741e86a4145e51f539f7f98bf51622ab6ae24d6 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 4 Sep 2023 22:39:06 -0500 Subject: [PATCH 012/258] Avoid UTF-8 for now since there are encoding problems with the IDE --- apps/stamplog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 7b5975f5fd..1d54d8e328 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -393,7 +393,7 @@ class MainScreen { } return { - // Define 1 “screenfull” as the unit here + // Define 1 "screenfull" as the unit here itemsPerPage: 1, pos: toPage(info.pos), min: toPage(info.min), From f4b3dd78d82eb7967f303bfb1e77cc6f9105b0a7 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 4 Sep 2023 23:11:32 -0500 Subject: [PATCH 013/258] Change some default configuration details --- apps/stamplog/app.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 1d54d8e328..344f183c81 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -3,7 +3,7 @@ locale = require('locale'); storage = require('Storage'); // Storage filename to store user's timestamp log -const LOG_FILENAME = 'stamplog.json'; +const LOG_FILENAME = 'timestamplog.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe const DRAG_THRESHOLD = 30; @@ -13,7 +13,7 @@ const SCROLL_BAR_WIDTH = 12; var settings = { logItemFont: '12x20', - maxLogLength: 6 + maxLogLength: 30 }; @@ -444,4 +444,3 @@ stampLog.on('saveError', saveErrorAlert); var currentUI = new MainScreen(stampLog); currentUI.start(); - From 4134e9a322587f15e80801a9b4b92918a73a235f Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 11:50:23 -0500 Subject: [PATCH 014/258] Implement basis for settings menu --- apps/stamplog/app.js | 47 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 344f183c81..d6317c1cab 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -2,8 +2,9 @@ Layout = require('Layout'); locale = require('locale'); storage = require('Storage'); -// Storage filename to store user's timestamp log +// Storage filenames const LOG_FILENAME = 'timestamplog.json'; +const SETTINGS_FILENAME = 'timestamplog.settings.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe const DRAG_THRESHOLD = 30; @@ -11,10 +12,20 @@ const DRAG_THRESHOLD = 30; // Width of scroll indicators const SCROLL_BAR_WIDTH = 12; -var settings = { + +// Settings + +const SETTINGS = Object.assign({ logItemFont: '12x20', + logItemFontSize: 1, maxLogLength: 30 -}; +}, storage.readJSON(SETTINGS_FILENAME, true) || {}); + +function saveSettings() { + if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { + E.showAlert('Trouble saving settings', "Can't save settings"); + } +} // Fetch a stringified image @@ -278,7 +289,7 @@ class MainScreen { {type: 'btn', font: '6x8:2', fillx: 1, label: '+ XX:XX', id: 'addBtn', cb: this.addTimestamp.bind(this)}, {type: 'btn', font: '6x8:2', label: getIcon('menu'), id: 'menuBtn', - cb: L => console.log(L)}, + cb: settingsMenu}, ], }, ], @@ -288,7 +299,7 @@ class MainScreen { // Calculate how many log items per page we have space to display layout.update(); let availableHeight = layout.placeholder.h; - g.setFont(settings.logItemFont); + g.setFont(SETTINGS.logItemFont); let logItemHeight = g.getFontHeight() * 2; this.itemsPerPage = Math.floor(availableHeight / logItemHeight); @@ -423,6 +434,30 @@ class MainScreen { } +function settingsMenu() { + function endMenu() { + saveSettings(); + currentUI.start(); + } + + currentUI.stop(); + E.showMenu({ + '': { + title: 'Timestamp Logger', + back: endMenu, + }, + 'Max log size': { + value: SETTINGS.maxLogLength, + min: 5, max: 100, step: 5, + onchange: v => { + SETTINGS.maxLogLength = v; + stampLog.maxLength = v; + } + }, + }); +} + + function saveErrorAlert() { currentUI.stop(); // Not `showAlert` because the icon plus message don't fit the @@ -438,7 +473,7 @@ function saveErrorAlert() { Bangle.loadWidgets(); Bangle.drawWidgets(); -stampLog = new StampLog(LOG_FILENAME, settings.maxLogLength); +stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength); E.on('kill', stampLog.save.bind(stampLog)); stampLog.on('saveError', saveErrorAlert); From 5a5cb62ebc2db997a5ed13198af409fc2dbda643 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 11:50:38 -0500 Subject: [PATCH 015/258] Make haptic scroll feedback a little stronger --- apps/stamplog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index d6317c1cab..0663826c70 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -367,7 +367,7 @@ class MainScreen { } if (Math.abs(distanceY) > DRAG_THRESHOLD) { // Scroll threshold reached - Bangle.buzz(50, .2); + Bangle.buzz(50, .5); this.scroll(distanceY > 0 ? 'u' : 'd'); distanceY = null; } From 56d4dae3570a2d582c8380642205f74e4fada702 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 21:08:01 -0500 Subject: [PATCH 016/258] Implement log font selection --- apps/stamplog/app.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 0663826c70..cd310f913f 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -16,8 +16,8 @@ const SCROLL_BAR_WIDTH = 12; // Settings const SETTINGS = Object.assign({ - logItemFont: '12x20', - logItemFontSize: 1, + logFont: '12x20', + logFontSize: 1, maxLogLength: 30 }, storage.readJSON(SETTINGS_FILENAME, true) || {}); @@ -173,7 +173,7 @@ function renderLogItem(elem) { if (elem.item) { g.setColor(g.theme.bg) .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) - .setFont('12x20') + .setFont(SETTINGS.logFont) .setFontAlign(-1, -1) .setColor(g.theme.fg) .drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y) @@ -299,7 +299,7 @@ class MainScreen { // Calculate how many log items per page we have space to display layout.update(); let availableHeight = layout.placeholder.h; - g.setFont(SETTINGS.logItemFont); + g.setFont(SETTINGS.logFont); let logItemHeight = g.getFontHeight() * 2; this.itemsPerPage = Math.floor(availableHeight / logItemHeight); @@ -439,6 +439,7 @@ function settingsMenu() { saveSettings(); currentUI.start(); } + const fonts = g.getFonts(); currentUI.stop(); E.showMenu({ @@ -446,6 +447,14 @@ function settingsMenu() { title: 'Timestamp Logger', back: endMenu, }, + 'Log font': { + value: fonts.indexOf(SETTINGS.logFont), + min: 0, max: fonts.length - 1, + format: v => fonts[v], + onchange: v => { + SETTINGS.logFont = fonts[v]; + }, + }, 'Max log size': { value: SETTINGS.maxLogLength, min: 5, max: 100, step: 5, From 51c20c9e49e3a8bcec50608a2fbf0e6f3abd1e06 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 21:08:06 -0500 Subject: [PATCH 017/258] Fix log display cosmetics Clear app display region before restarting log UI so as to not leave old junk behind if new font leaves leftover space at top of screen Draw text starting one pixel below log separator lines so the line doesn't overstrike topmost pixels of text --- apps/stamplog/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index cd310f913f..38f8e1eb23 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -180,7 +180,7 @@ function renderLogItem(elem) { .drawString(locale.date(elem.item.stamp, 1) + '\n' + locale.time(elem.item.stamp).trim(), - elem.x, elem.y); + elem.x, elem.y + 1); } else { g.setColor(g.blendColor(g.theme.bg, g.theme.fg, 0.25)) .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1); @@ -242,6 +242,7 @@ class MainScreen { // Launch this UI and make it live start() { this._initLayout(); + this.layout.clear(); this.scroll('b'); this.render('buttons'); From 5a01ae0159c60c3b661d7bc14e1b3a7bf292b2b0 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 21:51:08 -0500 Subject: [PATCH 018/258] Allow both horizontal and vertical font size selection Nifty! --- apps/stamplog/app.js | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 38f8e1eb23..cc4cfd7af4 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -17,7 +17,8 @@ const SCROLL_BAR_WIDTH = 12; const SETTINGS = Object.assign({ logFont: '12x20', - logFontSize: 1, + logFontHSize: 1, + logFontVSize: 1, maxLogLength: 30 }, storage.readJSON(SETTINGS_FILENAME, true) || {}); @@ -27,6 +28,10 @@ function saveSettings() { } } +function fontSpec(name, hsize, vsize) { + return name + ':' + hsize + 'x' + vsize; +} + // Fetch a stringified image function getIcon(id) { @@ -173,7 +178,8 @@ function renderLogItem(elem) { if (elem.item) { g.setColor(g.theme.bg) .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) - .setFont(SETTINGS.logFont) + .setFont(fontSpec(SETTINGS.logFont, + SETTINGS.logFontHSize, SETTINGS.logFontVSize)) .setFontAlign(-1, -1) .setColor(g.theme.fg) .drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y) @@ -300,9 +306,11 @@ class MainScreen { // Calculate how many log items per page we have space to display layout.update(); let availableHeight = layout.placeholder.h; - g.setFont(SETTINGS.logFont); + g.setFont(fontSpec(SETTINGS.logFont, + SETTINGS.logFontHSize, SETTINGS.logFontVSize)); let logItemHeight = g.getFontHeight() * 2; - this.itemsPerPage = Math.floor(availableHeight / logItemHeight); + this.itemsPerPage = Math.max(1, + Math.floor(availableHeight / logItemHeight)); // Populate log items in layout for (i = 0; i < this.itemsPerPage; i++) { @@ -448,6 +456,14 @@ function settingsMenu() { title: 'Timestamp Logger', back: endMenu, }, + 'Max log size': { + value: SETTINGS.maxLogLength, + min: 5, max: 100, step: 5, + onchange: v => { + SETTINGS.maxLogLength = v; + stampLog.maxLength = v; + } + }, 'Log font': { value: fonts.indexOf(SETTINGS.logFont), min: 0, max: fonts.length - 1, @@ -456,13 +472,19 @@ function settingsMenu() { SETTINGS.logFont = fonts[v]; }, }, - 'Max log size': { - value: SETTINGS.maxLogLength, - min: 5, max: 100, step: 5, + 'Log font H size': { + value: SETTINGS.logFontHSize, + min: 1, max: 50, onchange: v => { - SETTINGS.maxLogLength = v; - stampLog.maxLength = v; - } + SETTINGS.logFontHSize = v; + }, + }, + 'Log font V size': { + value: SETTINGS.logFontVSize, + min: 1, max: 50, + onchange: v => { + SETTINGS.logFontVSize = v; + }, }, }); } From fe8694c7095801eb8c00e93ffa35f2b1be662c9b Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 11 Sep 2023 16:18:28 -0500 Subject: [PATCH 019/258] Remove redundant message title --- apps/stamplog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index cc4cfd7af4..0d812eabea 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -24,7 +24,7 @@ const SETTINGS = Object.assign({ function saveSettings() { if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { - E.showAlert('Trouble saving settings', "Can't save settings"); + E.showAlert('Trouble saving settings'); } } From 0127a9654b37b89b94b0470195f41951d6d708c9 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 2 Nov 2023 12:28:24 -0500 Subject: [PATCH 020/258] Implement log rotate option and UI support --- apps/stamplog/app.js | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 0d812eabea..b41c37c433 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -19,7 +19,8 @@ const SETTINGS = Object.assign({ logFont: '12x20', logFontHSize: 1, logFontVSize: 1, - maxLogLength: 30 + maxLogLength: 30, + rotateLog: false, }, storage.readJSON(SETTINGS_FILENAME, true) || {}); function saveSettings() { @@ -168,6 +169,11 @@ class StampLog { this.log = this.log.filter(entry => !entries.includes(entry)); this.setDirty(); } + + // Does the log currently contain the maximum possible number of entries? + isFull() { + return this.log.length >= this.maxLength; + } } @@ -339,7 +345,21 @@ class MainScreen { } if (!item || item == 'buttons') { - this.layout.addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim(); + let addBtn = this.layout.addBtn; + + if (!SETTINGS.rotateLog && this.stampLog.isFull()) { + // Dimmed appearance for unselectable button + addBtn.btnFaceCol = g.blendColor(g.theme.bg2, g.theme.bg, 0.5); + addBtn.btnBorderCol = g.blendColor(g.theme.fg2, g.theme.bg, 0.5); + + addBtn.label = 'Log full'; + } else { + addBtn.btnFaceCol = g.theme.bg2; + addBtn.btnBorderCol = g.theme.fg2; + + addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim(); + } + this.layout.render(this.layout.buttons); // Auto-update time of day indication on log-add button upon @@ -386,10 +406,13 @@ class MainScreen { Bangle.on('drag', this.listeners.drag); } - // Add current timestamp to log and update UI display + // Add current timestamp to log if possible and update UI display addTimestamp() { - this.stampLog.addEntry(); - this.scroll('b'); + if (SETTINGS.rotateLog || !this.stampLog.isFull()) { + this.stampLog.addEntry(); + this.scroll('b'); + this.render('buttons'); + } } // Get scroll information for log display @@ -464,6 +487,12 @@ function settingsMenu() { stampLog.maxLength = v; } }, + 'Rotate log entries': { + value: SETTINGS.rotateLog, + onchange: v => { + SETTINGS.rotateLog = !SETTINGS.rotateLog; + } + }, 'Log font': { value: fonts.indexOf(SETTINGS.logFont), min: 0, max: fonts.length - 1, From 2872ed87eccb057e36b06990812fff09aa45e130 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 2 Nov 2023 15:41:37 -0500 Subject: [PATCH 021/258] Break up settings menu into submenus Although there aren't very many items yet, I feel that minimizing scrolling on the Bangle.js 2 touchscreen makes menu navigation and use easier. --- apps/stamplog/app.js | 113 ++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index b41c37c433..b9184efafd 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -169,7 +169,7 @@ class StampLog { this.log = this.log.filter(entry => !entries.includes(entry)); this.setDirty(); } - + // Does the log currently contain the maximum possible number of entries? isFull() { return this.log.length >= this.maxLength; @@ -467,55 +467,80 @@ class MainScreen { function settingsMenu() { + const fonts = g.getFonts(); + + function topMenu() { + E.showMenu({ + '': { + title: 'Stamplog', + back: endMenu, + }, + 'Log': logMenu, + 'Appearance': appearanceMenu, + }); + } + + function logMenu() { + E.showMenu({ + '': { + title: 'Log', + back: topMenu, + }, + 'Max entries': { + value: SETTINGS.maxLogLength, + min: 5, max: 100, step: 5, + onchange: v => { + SETTINGS.maxLogLength = v; + stampLog.maxLength = v; + } + }, + 'Auto-delete oldest': { + value: SETTINGS.rotateLog, + onchange: v => { + SETTINGS.rotateLog = !SETTINGS.rotateLog; + } + }, + }); + } + + function appearanceMenu() { + E.showMenu({ + '': { + title: 'Appearance', + back: topMenu, + }, + 'Log font': { + value: fonts.indexOf(SETTINGS.logFont), + min: 0, max: fonts.length - 1, + format: v => fonts[v], + onchange: v => { + SETTINGS.logFont = fonts[v]; + }, + }, + 'Log font H size': { + value: SETTINGS.logFontHSize, + min: 1, max: 50, + onchange: v => { + SETTINGS.logFontHSize = v; + }, + }, + 'Log font V size': { + value: SETTINGS.logFontVSize, + min: 1, max: 50, + onchange: v => { + SETTINGS.logFontVSize = v; + }, + }, + }); + } + function endMenu() { saveSettings(); currentUI.start(); } - const fonts = g.getFonts(); currentUI.stop(); - E.showMenu({ - '': { - title: 'Timestamp Logger', - back: endMenu, - }, - 'Max log size': { - value: SETTINGS.maxLogLength, - min: 5, max: 100, step: 5, - onchange: v => { - SETTINGS.maxLogLength = v; - stampLog.maxLength = v; - } - }, - 'Rotate log entries': { - value: SETTINGS.rotateLog, - onchange: v => { - SETTINGS.rotateLog = !SETTINGS.rotateLog; - } - }, - 'Log font': { - value: fonts.indexOf(SETTINGS.logFont), - min: 0, max: fonts.length - 1, - format: v => fonts[v], - onchange: v => { - SETTINGS.logFont = fonts[v]; - }, - }, - 'Log font H size': { - value: SETTINGS.logFontHSize, - min: 1, max: 50, - onchange: v => { - SETTINGS.logFontHSize = v; - }, - }, - 'Log font V size': { - value: SETTINGS.logFontVSize, - min: 1, max: 50, - onchange: v => { - SETTINGS.logFontVSize = v; - }, - }, - }); + topMenu(); } From 798ce38600a209e1c2f386ecefd62cd730561ede Mon Sep 17 00:00:00 2001 From: pinq- Date: Sun, 21 Apr 2024 01:22:43 +0300 Subject: [PATCH 022/258] added timestamp --- apps/accelrec/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index cf434fd7b4..175801ee53 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -6,6 +6,7 @@ var THRESH = 1.04; var accelx = new Int16Array(SAMPLES); var accely = new Int16Array(SAMPLES); // North var accelz = new Int16Array(SAMPLES); // Into clock face +var timestep = new Int16Array(SAMPLES); // Into clock face var accelIdx = 0; var lastAccel; function accelHandlerTrigger(a) {"ram" @@ -26,6 +27,7 @@ function accelHandlerRecord(a) {"ram" accelx[i] = a.x*SCALE*2; accely[i] = -a.y*SCALE*2; accelz[i] = a.z*SCALE*2; + timestep[i] = getTime(); if (accelIdx>=SAMPLES) recordStop(); } function recordStart() {"ram" @@ -167,7 +169,7 @@ function showSaveMenu() { menu["Recording "+i+(exists?" *":"")] = function() { var csv = ""; for (var i=0;i Date: Sun, 21 Apr 2024 01:40:29 +0300 Subject: [PATCH 023/258] milliseconds --- apps/accelrec/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index 175801ee53..d8c359acb8 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -27,7 +27,7 @@ function accelHandlerRecord(a) {"ram" accelx[i] = a.x*SCALE*2; accely[i] = -a.y*SCALE*2; accelz[i] = a.z*SCALE*2; - timestep[i] = getTime(); + timestep[i] = Date.prototype.getTime(); if (accelIdx>=SAMPLES) recordStop(); } function recordStart() {"ram" From efc89bb7cfeebe81ae8ec92f138ff83cb4aa1f09 Mon Sep 17 00:00:00 2001 From: pinq- Date: Sun, 21 Apr 2024 01:45:17 +0300 Subject: [PATCH 024/258] Update app.js --- apps/accelrec/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index d8c359acb8..58d3ce445d 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -27,7 +27,7 @@ function accelHandlerRecord(a) {"ram" accelx[i] = a.x*SCALE*2; accely[i] = -a.y*SCALE*2; accelz[i] = a.z*SCALE*2; - timestep[i] = Date.prototype.getTime(); + timestep[i] = getTime(); if (accelIdx>=SAMPLES) recordStop(); } function recordStart() {"ram" @@ -169,7 +169,7 @@ function showSaveMenu() { menu["Recording "+i+(exists?" *":"")] = function() { var csv = ""; for (var i=0;i Date: Sun, 21 Apr 2024 10:33:32 +0300 Subject: [PATCH 025/258] right way this time --- apps/accelrec/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index 58d3ce445d..512c73d9d9 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -27,7 +27,7 @@ function accelHandlerRecord(a) {"ram" accelx[i] = a.x*SCALE*2; accely[i] = -a.y*SCALE*2; accelz[i] = a.z*SCALE*2; - timestep[i] = getTime(); + timestep[i] = (getTime() - tStart)*100; if (accelIdx>=SAMPLES) recordStop(); } function recordStart() {"ram" @@ -169,7 +169,7 @@ function showSaveMenu() { menu["Recording "+i+(exists?" *":"")] = function() { var csv = ""; for (var i=0;i Date: Sun, 21 Apr 2024 11:07:05 +0300 Subject: [PATCH 026/258] More time and more timestamp digits --- apps/accelrec/app.js | 4 ++-- apps/accelrec/interface.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index 512c73d9d9..d866af9e2d 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -1,6 +1,6 @@ //var acc; var HZ = 100; -var SAMPLES = 5*HZ; // 5 seconds +var SAMPLES = 8*HZ; // 8 seconds var SCALE = 5000; var THRESH = 1.04; var accelx = new Int16Array(SAMPLES); @@ -27,7 +27,7 @@ function accelHandlerRecord(a) {"ram" accelx[i] = a.x*SCALE*2; accely[i] = -a.y*SCALE*2; accelz[i] = a.z*SCALE*2; - timestep[i] = (getTime() - tStart)*100; + timestep[i] = (getTime() - tStart)*1000; if (accelIdx>=SAMPLES) recordStop(); } function recordStart() {"ram" diff --git a/apps/accelrec/interface.html b/apps/accelrec/interface.html index ce35473878..d69b2d50c2 100644 --- a/apps/accelrec/interface.html +++ b/apps/accelrec/interface.html @@ -37,7 +37,7 @@ `; promise = promise.then(function() { document.querySelector(`.btn[fn='${fn}'][act='save']`).addEventListener("click", function() { - Util.saveCSV(fn.slice(0,-4), "X,Y,Z\n"+fileData[fn]); + Util.saveCSV(fn.slice(0,-4), "Time,X,Y,Z\n"+fileData[fn]); }); document.querySelector(`.btn[fn='${fn}'][act='delete']`).addEventListener("click", function() { Util.showModal("Deleting..."); From 651cf4295372a94563add7b45989342fcee0da16 Mon Sep 17 00:00:00 2001 From: pinq- Date: Sun, 21 Apr 2024 17:14:13 +0300 Subject: [PATCH 027/258] try with 400 hz --- apps/accelrec/app.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index d866af9e2d..41a8cb8fcc 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -1,6 +1,6 @@ //var acc; -var HZ = 100; -var SAMPLES = 8*HZ; // 8 seconds +var HZ = 400; +var SAMPLES = 5*HZ; // 5 seconds var SCALE = 5000; var THRESH = 1.04; var accelx = new Int16Array(SAMPLES); @@ -35,7 +35,8 @@ function recordStart() {"ram" accelIdx = 0; lastAccel = []; Bangle.accelWr(0x18,0b01110100); // off, +-8g - Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter + //Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter + Bangle.accelWr(0x1B,0x05 | 0x40); // 400hz output, ODR/2 filter Bangle.accelWr(0x18,0b11110100); // +-8g Bangle.setPollInterval(10); // 100hz input setTimeout(function() { From c961406da98f835477a6459d53868a8b84cc2788 Mon Sep 17 00:00:00 2001 From: pinq- Date: Sun, 21 Apr 2024 17:28:27 +0300 Subject: [PATCH 028/258] set pollintervall to 2.5(400hz) --- apps/accelrec/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index 41a8cb8fcc..1a3e0fd24f 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -38,7 +38,8 @@ function recordStart() {"ram" //Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter Bangle.accelWr(0x1B,0x05 | 0x40); // 400hz output, ODR/2 filter Bangle.accelWr(0x18,0b11110100); // +-8g - Bangle.setPollInterval(10); // 100hz input + //Bangle.setPollInterval(10); // 100hz input + Bangle.setPollInterval(2.5); // 400hz input setTimeout(function() { Bangle.on('accel',accelHandlerTrigger); g.clear(1).setFont("6x8",2).setFontAlign(0,0); From ac36b5bb5531ad4ca2d19c63117b59993ea846e9 Mon Sep 17 00:00:00 2001 From: pinq- Date: Sun, 21 Apr 2024 17:34:20 +0300 Subject: [PATCH 029/258] can't take the space. Lower time to 4 seconds --- apps/accelrec/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index 1a3e0fd24f..d5b5e676ae 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -1,6 +1,6 @@ //var acc; var HZ = 400; -var SAMPLES = 5*HZ; // 5 seconds +var SAMPLES = 4*HZ; // 5 seconds var SCALE = 5000; var THRESH = 1.04; var accelx = new Int16Array(SAMPLES); @@ -39,7 +39,7 @@ function recordStart() {"ram" Bangle.accelWr(0x1B,0x05 | 0x40); // 400hz output, ODR/2 filter Bangle.accelWr(0x18,0b11110100); // +-8g //Bangle.setPollInterval(10); // 100hz input - Bangle.setPollInterval(2.5); // 400hz input + Bangle.setPollInterval(2.5); // 400hz input setTimeout(function() { Bangle.on('accel',accelHandlerTrigger); g.clear(1).setFont("6x8",2).setFontAlign(0,0); From caae40d14d6e5f99369112483466a8115ac98027 Mon Sep 17 00:00:00 2001 From: pinq- Date: Sun, 21 Apr 2024 17:42:24 +0300 Subject: [PATCH 030/258] Cant with 400hz. No space. lets try with 200hz --- apps/accelrec/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index d5b5e676ae..af1c1f5a38 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -1,6 +1,6 @@ //var acc; -var HZ = 400; -var SAMPLES = 4*HZ; // 5 seconds +var HZ = 200; +var SAMPLES = 5*HZ; // 5 seconds var SCALE = 5000; var THRESH = 1.04; var accelx = new Int16Array(SAMPLES); @@ -36,10 +36,10 @@ function recordStart() {"ram" lastAccel = []; Bangle.accelWr(0x18,0b01110100); // off, +-8g //Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter - Bangle.accelWr(0x1B,0x05 | 0x40); // 400hz output, ODR/2 filter + Bangle.accelWr(0x1B,0x04 | 0x40); // 200hz output, ODR/2 filter Bangle.accelWr(0x18,0b11110100); // +-8g //Bangle.setPollInterval(10); // 100hz input - Bangle.setPollInterval(2.5); // 400hz input + Bangle.setPollInterval(5); // 200hz input setTimeout(function() { Bangle.on('accel',accelHandlerTrigger); g.clear(1).setFont("6x8",2).setFontAlign(0,0); From 10e56fb2de449ef16f90d7521bcf796b1297904d Mon Sep 17 00:00:00 2001 From: pinq- Date: Sun, 21 Apr 2024 17:49:40 +0300 Subject: [PATCH 031/258] update --- apps/accelrec/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/accelrec/metadata.json b/apps/accelrec/metadata.json index b8831c31bf..36d700a190 100644 --- a/apps/accelrec/metadata.json +++ b/apps/accelrec/metadata.json @@ -2,7 +2,7 @@ "id": "accelrec", "name": "Acceleration Recorder", "shortName": "Accel Rec", - "version": "0.04", + "version": "0.05", "description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.", "icon": "app.png", "tags": "", From 6bbcdf1de50fcc089880f862638db8096b9bd471 Mon Sep 17 00:00:00 2001 From: pinq- Date: Sun, 21 Apr 2024 18:00:37 +0300 Subject: [PATCH 032/258] Update app.js --- apps/accelrec/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index af1c1f5a38..92efc5c6a6 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -1,5 +1,5 @@ //var acc; -var HZ = 200; +var HZ = 400; var SAMPLES = 5*HZ; // 5 seconds var SCALE = 5000; var THRESH = 1.04; @@ -36,10 +36,10 @@ function recordStart() {"ram" lastAccel = []; Bangle.accelWr(0x18,0b01110100); // off, +-8g //Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter - Bangle.accelWr(0x1B,0x04 | 0x40); // 200hz output, ODR/2 filter + Bangle.accelWr(0x1B,0x05 | 0x40); // 400hz output, ODR/2 filter Bangle.accelWr(0x18,0b11110100); // +-8g //Bangle.setPollInterval(10); // 100hz input - Bangle.setPollInterval(5); // 200hz input + Bangle.setPollInterval(2.5); // 100hz input setTimeout(function() { Bangle.on('accel',accelHandlerTrigger); g.clear(1).setFont("6x8",2).setFontAlign(0,0); From 6cc888000aad2b8744b9841630a1455af24e3e37 Mon Sep 17 00:00:00 2001 From: pinq- Date: Fri, 26 Apr 2024 10:27:07 +0300 Subject: [PATCH 033/258] back to 100hz --- apps/accelrec/app.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index 92efc5c6a6..cfd8d8ca4d 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -1,5 +1,5 @@ //var acc; -var HZ = 400; +var HZ = 100; var SAMPLES = 5*HZ; // 5 seconds var SCALE = 5000; var THRESH = 1.04; @@ -35,11 +35,11 @@ function recordStart() {"ram" accelIdx = 0; lastAccel = []; Bangle.accelWr(0x18,0b01110100); // off, +-8g - //Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter - Bangle.accelWr(0x1B,0x05 | 0x40); // 400hz output, ODR/2 filter + Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter + //Bangle.accelWr(0x1B,0x05 | 0x40); // 400hz output, ODR/2 filter Bangle.accelWr(0x18,0b11110100); // +-8g - //Bangle.setPollInterval(10); // 100hz input - Bangle.setPollInterval(2.5); // 100hz input + Bangle.setPollInterval(10); // 100hz input + //Bangle.setPollInterval(2.5); // 100hz input setTimeout(function() { Bangle.on('accel',accelHandlerTrigger); g.clear(1).setFont("6x8",2).setFontAlign(0,0); From 805f13fb595a73d2a938eebd1c24394276866ac6 Mon Sep 17 00:00:00 2001 From: pinq- Date: Mon, 29 Apr 2024 19:24:44 +0300 Subject: [PATCH 034/258] higher threshold and lower scale for 16bit --- apps/accelrec/app.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/accelrec/app.js b/apps/accelrec/app.js index cfd8d8ca4d..fd28b3440b 100644 --- a/apps/accelrec/app.js +++ b/apps/accelrec/app.js @@ -1,8 +1,8 @@ //var acc; var HZ = 100; var SAMPLES = 5*HZ; // 5 seconds -var SCALE = 5000; -var THRESH = 1.04; +var SCALE = 2000; +var THRESH = 1.4; var accelx = new Int16Array(SAMPLES); var accely = new Int16Array(SAMPLES); // North var accelz = new Int16Array(SAMPLES); // Into clock face @@ -24,7 +24,7 @@ function accelHandlerTrigger(a) {"ram" } function accelHandlerRecord(a) {"ram" var i = accelIdx++; - accelx[i] = a.x*SCALE*2; + accelx[i] = a.x*SCALE*2; // *2 because of 8g mode accely[i] = -a.y*SCALE*2; accelz[i] = a.z*SCALE*2; timestep[i] = (getTime() - tStart)*1000; @@ -36,10 +36,8 @@ function recordStart() {"ram" lastAccel = []; Bangle.accelWr(0x18,0b01110100); // off, +-8g Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter - //Bangle.accelWr(0x1B,0x05 | 0x40); // 400hz output, ODR/2 filter Bangle.accelWr(0x18,0b11110100); // +-8g Bangle.setPollInterval(10); // 100hz input - //Bangle.setPollInterval(2.5); // 100hz input setTimeout(function() { Bangle.on('accel',accelHandlerTrigger); g.clear(1).setFont("6x8",2).setFontAlign(0,0); From 30734047668adce7af677b273566bbde83eafbf0 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 10 May 2024 20:21:36 -0500 Subject: [PATCH 035/258] Rename app --- apps/{stamplog => timestamplog}/app.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/{stamplog => timestamplog}/app.js (100%) diff --git a/apps/stamplog/app.js b/apps/timestamplog/app.js similarity index 100% rename from apps/stamplog/app.js rename to apps/timestamplog/app.js From 1dd88b29b29d3cf9036c9bb43f08adb02371ffb2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 10 May 2024 20:26:13 -0500 Subject: [PATCH 036/258] Fix breakage after recent FW (Date object serializes differently) --- apps/timestamplog/app.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index b9184efafd..521480776e 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -129,7 +129,15 @@ class StampLog { } if (this.isDirty) { - if (storage.writeJSON(this.filename, this.log)) { + let logToSave = []; + for (let logEntry of this.log) { + // Serialize each Date object into an ISO string before saving + let newEntry = Object.assign({}, logEntry); + newEntry.stamp = logEntry.stamp.toISOString(); + logToSave.push(newEntry); + } + + if (storage.writeJSON(this.filename, logToSave)) { console.log('stamplog: save to storage completed'); this.isDirty = false; } else { From 69886274457b6c5851517eb15d2e6de59e579125 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Tue, 21 May 2024 14:55:47 -0500 Subject: [PATCH 037/258] Update app.js --- apps/messagegui/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index 9172bcad71..cd3fb0ab5b 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -505,7 +505,7 @@ function checkMessages(options) { // draw the body g.drawString(l.join("\n"), x+10,r.y+20+pady); } - if (!longBody && msg.src) g.setFontAlign(1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2); + if (!longBody && msg.src) g.setFontAlign(-1,1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+r.h-2); 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 }, select : idx => { From a2c191a0c6a16371eb4f126eadadf1aea374a5a4 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Wed, 22 May 2024 20:58:53 -0500 Subject: [PATCH 038/258] Update metadata.json --- apps/gpspoilog/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gpspoilog/metadata.json b/apps/gpspoilog/metadata.json index 0a0902ceac..41a917b797 100644 --- a/apps/gpspoilog/metadata.json +++ b/apps/gpspoilog/metadata.json @@ -6,7 +6,7 @@ "description": "A simple app to log points of interest with their GPS coordinates and read them back onto your PC. Based on the https://www.espruino.com/Bangle.js+Storage tutorial", "icon": "app.png", "tags": "outdoors", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "interface": "interface.html", "storage": [ {"name":"gpspoilog.app.js","url":"app.js"}, From ab045fbd2437eebadc640504471999e15e95d5a2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 24 May 2024 18:04:13 -0500 Subject: [PATCH 039/258] Implement ability to delete log entries --- apps/timestamplog/app.js | 135 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 521480776e..901ddc458d 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -278,6 +278,7 @@ class MainScreen { // Kill layout handlers Bangle.removeListener('drag', this.listeners.drag); + Bangle.removeListener('touch', this.listeners.touch); Bangle.setUI(); } @@ -329,7 +330,8 @@ class MainScreen { // Populate log items in layout for (i = 0; i < this.itemsPerPage; i++) { layout.logItems.c.push( - {type: 'custom', render: renderLogItem, item: undefined, fillx: 1, height: logItemHeight} + {type: 'custom', render: renderLogItem, item: undefined, itemIdx: undefined, + fillx: 1, height: logItemHeight} ); } layout.logScroll.height = logItemHeight * this.itemsPerPage; @@ -347,6 +349,7 @@ class MainScreen { for (let elem of layLogItems.c) { logIdx++; elem.item = this.stampLog.log[logIdx]; + elem.itemIdx = logIdx; } this.layout.render(layLogItems); this.layout.render(this.layout.logScroll); @@ -412,6 +415,23 @@ class MainScreen { this.listeners.drag = dragHandler.bind(this); Bangle.on('drag', this.listeners.drag); + + function touchHandler(button, xy) { + // Handle taps on log entries + let logUIItems = this.layout.logItems.c; + for (var logUIObj of logUIItems) { + if (!xy.type && + logUIObj.x <= xy.x && xy.x < logUIObj.x + logUIObj.w && + logUIObj.y <= xy.y && xy.y < logUIObj.y + logUIObj.h && + logUIObj.item) { + switchUI(new LogEntryScreen(this.stampLog, logUIObj.itemIdx)); + break; + } + } + } + + this.listeners.touch = touchHandler.bind(this); + Bangle.on('touch', this.listeners.touch); } // Add current timestamp to log if possible and update UI display @@ -474,6 +494,96 @@ class MainScreen { } +// Log entry screen interface, launched by calling start() +class LogEntryScreen { + + constructor(stampLog, logIdx) { + this.stampLog = stampLog; + this.logIdx = logIdx; + this.logItem = stampLog.log[logIdx]; + + this.defaultFont = fontSpec( + SETTINGS.logFont, SETTINGS.logFontHSize, SETTINGS.logFontVSize); + } + + start() { + this._initLayout(); + this.layout.clear(); + this.render(); + } + + stop() { + Bangle.setUI(); + } + + back() { + this.stop(); + switchUI(mainUI); + } + + _initLayout() { + let layout = new Layout( + {type: 'v', + c: [ + {type: 'txt', font: this.defaultFont, label: locale.date(this.logItem.stamp, 1)}, + {type: 'txt', font: this.defaultFont, label: locale.time(this.logItem.stamp).trim()}, + {type: '', id: 'placeholder', fillx: 1, filly: 1}, + {type: 'btn', font: '6x15', label: 'Delete', cb: this.delLogItem.bind(this), + cbl: this.delLogItem.bind(this)}, + ], + }, + { + back: this.back.bind(this), + btns: [ + {label: '<', cb: this.prevLogItem.bind(this)}, + {label: '>', cb: this.nextLogItem.bind(this)}, + ], + } + ); + + layout.update(); + this.layout = layout; + } + + render(item) { + this.layout.clear(); + this.layout.render(); + } + + refresh() { + this.logItem = this.stampLog.log[this.logIdx]; + this._initLayout(); + this.render(); + } + + prevLogItem() { + this.logIdx = this.logIdx ? this.logIdx-1 : this.stampLog.log.length-1; + this.refresh(); + } + + nextLogItem() { + this.logIdx = this.logIdx == this.stampLog.log.length-1 ? 0 : this.logIdx+1; + this.refresh(); + } + + delLogItem() { + this.stampLog.deleteEntries([this.logItem]); + if (!this.stampLog.log.length) { + this.back(); + return; + } else if (this.logIdx > this.stampLog.log.length - 1) { + this.logIdx = this.stampLog.log.length - 1; + } + + // Create a brief “blink” on the screen to provide user feedback + // that the deletion has been performed + this.layout.clear(); + setTimeout(this.refresh.bind(this), 250); + } + +} + + function settingsMenu() { const fonts = g.getFonts(); @@ -508,6 +618,7 @@ function settingsMenu() { SETTINGS.rotateLog = !SETTINGS.rotateLog; } }, + 'Clear log': clearLogPrompt, }); } @@ -547,6 +658,20 @@ function settingsMenu() { currentUI.start(); } + function clearLogPrompt() { + E.showPrompt('Erase ALL log entries?', { + title: 'Clear log', + buttons: {'Erase':1, "Don't":0} + }).then((yes) => { + if (yes) { + stampLog.deleteEntries(stampLog.log) + endMenu(); + } else { + logMenu(); + } + }); + } + currentUI.stop(); topMenu(); } @@ -564,6 +689,13 @@ function saveErrorAlert() { } +function switchUI(newUI) { + currentUI.stop(); + currentUI = newUI; + currentUI.start(); +} + + Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -572,4 +704,5 @@ E.on('kill', stampLog.save.bind(stampLog)); stampLog.on('saveError', saveErrorAlert); var currentUI = new MainScreen(stampLog); +var mainUI = currentUI; currentUI.start(); From 872a7a51de675226c3482372895a0578e48aa857 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 25 May 2024 18:32:18 -0500 Subject: [PATCH 040/258] Add action setting for button presses --- apps/timestamplog/app.js | 44 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 901ddc458d..11d48ecd85 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -21,8 +21,16 @@ const SETTINGS = Object.assign({ logFontVSize: 1, maxLogLength: 30, rotateLog: false, + buttonAction: 'Log time', }, storage.readJSON(SETTINGS_FILENAME, true) || {}); +const SETTINGS_BUTTON_ACTION = [ + 'Log time', + 'Show menu', + 'Quit app', + 'Do nothing', +]; + function saveSettings() { if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { E.showAlert('Trouble saving settings'); @@ -279,6 +287,7 @@ class MainScreen { // Kill layout handlers Bangle.removeListener('drag', this.listeners.drag); Bangle.removeListener('touch', this.listeners.touch); + clearWatch(this.listeners.btnWatch); Bangle.setUI(); } @@ -432,6 +441,23 @@ class MainScreen { this.listeners.touch = touchHandler.bind(this); Bangle.on('touch', this.listeners.touch); + + function buttonHandler() { + let act = SETTINGS.buttonAction; + if (act == 'Log time') { + if (currentUI != mainUI) { + switchUI(mainUI); + } + mainUI.addTimestamp(); + } else if (act == 'Show menu') { + settingsMenu(); + } else if (act == 'Quit app') { + Bangle.showClock(); + } + } + + this.listeners.btnWatch = setWatch(buttonHandler, BTN, + {edge: 'falling', debounce: 50, repeat: true}); } // Add current timestamp to log if possible and update UI display @@ -509,7 +535,7 @@ class LogEntryScreen { start() { this._initLayout(); this.layout.clear(); - this.render(); + this.refresh(); } stop() { @@ -525,8 +551,8 @@ class LogEntryScreen { let layout = new Layout( {type: 'v', c: [ - {type: 'txt', font: this.defaultFont, label: locale.date(this.logItem.stamp, 1)}, - {type: 'txt', font: this.defaultFont, label: locale.time(this.logItem.stamp).trim()}, + {type: 'txt', font: this.defaultFont, id: 'date', label: '?'}, + {type: 'txt', font: this.defaultFont, id: 'time', label: '?'}, {type: '', id: 'placeholder', fillx: 1, filly: 1}, {type: 'btn', font: '6x15', label: 'Delete', cb: this.delLogItem.bind(this), cbl: this.delLogItem.bind(this)}, @@ -552,7 +578,9 @@ class LogEntryScreen { refresh() { this.logItem = this.stampLog.log[this.logIdx]; - this._initLayout(); + this.layout.date.label = locale.date(this.logItem.stamp, 1); + this.layout.time.label = locale.time(this.logItem.stamp).trim(); + this.layout.update(); this.render(); } @@ -595,6 +623,14 @@ function settingsMenu() { }, 'Log': logMenu, 'Appearance': appearanceMenu, + 'Button': { + value: SETTINGS_BUTTON_ACTION.indexOf(SETTINGS.buttonAction), + min: 0, max: SETTINGS_BUTTON_ACTION.length - 1, + format: v => SETTINGS_BUTTON_ACTION[v], + onchange: v => { + SETTINGS.buttonAction = SETTINGS_BUTTON_ACTION[v]; + }, + }, }); } From a08d580a1eb74aecc27b908c71dfcdc792a2d503 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 26 May 2024 13:23:29 -0500 Subject: [PATCH 041/258] Rename function for clarity --- apps/timestamplog/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 11d48ecd85..7505c6afb8 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -320,7 +320,7 @@ class MainScreen { {type: 'btn', font: '6x8:2', fillx: 1, label: '+ XX:XX', id: 'addBtn', cb: this.addTimestamp.bind(this)}, {type: 'btn', font: '6x8:2', label: getIcon('menu'), id: 'menuBtn', - cb: settingsMenu}, + cb: launchSettingsMenu}, ], }, ], @@ -450,7 +450,7 @@ class MainScreen { } mainUI.addTimestamp(); } else if (act == 'Show menu') { - settingsMenu(); + launchSettingsMenu(); } else if (act == 'Quit app') { Bangle.showClock(); } @@ -612,7 +612,7 @@ class LogEntryScreen { } -function settingsMenu() { +function launchSettingsMenu() { const fonts = g.getFonts(); function topMenu() { From 9e2c87cad84239b31d0faac1bb2e341f2884c00b Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 26 May 2024 13:57:41 -0500 Subject: [PATCH 042/258] Remove short-press callback from log entry delete button That was just for testing; the emulator doesn't support long presses. --- apps/timestamplog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 7505c6afb8..9e4fa2273c 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -554,7 +554,7 @@ class LogEntryScreen { {type: 'txt', font: this.defaultFont, id: 'date', label: '?'}, {type: 'txt', font: this.defaultFont, id: 'time', label: '?'}, {type: '', id: 'placeholder', fillx: 1, filly: 1}, - {type: 'btn', font: '6x15', label: 'Delete', cb: this.delLogItem.bind(this), + {type: 'btn', font: '6x15', label: 'Hold to delete', cbl: this.delLogItem.bind(this)}, ], }, From e7477784e91626471930df76dbb772a187553ea2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 27 May 2024 14:38:32 -0500 Subject: [PATCH 043/258] Add app metadata files --- apps/timestamplog/app-icon.js | 1 + apps/timestamplog/app.png | Bin 0 -> 851 bytes apps/timestamplog/changelog.txt | 1 + apps/timestamplog/metadata.json | 16 ++++++++++++++++ 4 files changed, 18 insertions(+) create mode 100644 apps/timestamplog/app-icon.js create mode 100644 apps/timestamplog/app.png create mode 100644 apps/timestamplog/changelog.txt create mode 100644 apps/timestamplog/metadata.json diff --git a/apps/timestamplog/app-icon.js b/apps/timestamplog/app-icon.js new file mode 100644 index 0000000000..b35f05e08b --- /dev/null +++ b/apps/timestamplog/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cB/4ACBQX48AFDAAUkyVJAQoDCBZACDymSoEAgVJkQRJkskGAuJCJEpBwYDCyIRHpVICI0SogRGqQyEAgdIBwIUEyAODAQkJiVJxBoDwARIgJuBiIUBCIKzKCIOCQYWkCJUkpNQoiMBkARKgmJxUIxMlOgIQIQwOJyNBiKeBCJeRyUokUoGZPYAQMRyVFgiwDAAsGCIlI0GIBYfAAgUB2wECCINJikRCIfbAgVt2DaDCIMiwR9DjdggEDtg5DgTECSQIJDtuAEwYRFSQOSBIUN2xWCgANBgVSAYKSBR4k2AgYRCxQDBSQTGKgVRCISSBoARKxUpCIKSFAA0SqNFCIKSFAA0RlUo0iSHCI0losUSRARFkmo0SSEwAPFeoORkmViiSEiARHxJXB0SSFAgQCByEAggRCqiSEilChEgwUIXgMkBgVKSQmiqFBgkQoMUoArESQdE6Y1FxIRESQco0WIEYkRiQRDSQWRmnTpojEwRhFSQOKEYOJEYkQogRESQNNEZEIPoQUCEYeKkIjEoLFBCIdTEYc0EYsiCIlKpQjCkojCNIYREpMpEYwCCEYoCB0gjBkmEEYImCgQRGyWTNYJECbQQjHJQIDBygjNpSHCEZ0QAYIjODoJHPEAgjDA==")) diff --git a/apps/timestamplog/app.png b/apps/timestamplog/app.png new file mode 100644 index 0000000000000000000000000000000000000000..ddb51fe40ba201d8b0a5fb66583b8c720e932898 GIT binary patch literal 851 zcmV-Z1FZasP)EX>4Tx04R}tkv&MmKpe$iQ%glE4rY+zkfAzRC@RuXt5Adrp;l}?mh0_0bHx5XjWeW&~)3( zrc*+`uquRK5keF^5=cslWz30U2EOC#9s#!A#aWjBxj)CCTC@}(AP~leu-ldB4a z91EyJgXH?b{@{1FR%vR|ONyj`(2L`Ii~-?Ypxtzw?_n$MpNqV!Z z#g2fXZQ$a%tI2!7A(Ki)<;agx}&FihRkJASrM_pxZfP+I| zyiD2aKJV`D?d{()o&J6R_g-?`vQ)&D0000FP)t-s00030|Nj600RR60{{R3IQ;Pcl z0004WQchCAP9xMY@lX<1YICzg5G8SnfgE}*!b@1 z51t=j#D?HRPvjnC?vEbvHUy5whXF+D?@up)yG)^UgsjtHwA)qjpRp8Q#Tjjl5Qrsmgl&!Rg)9*m z3xxVOyb-x5k5Jbb6GO^*iAbkZkyf&(oN3HwM{3nnO$T0(1`Q(rrkasj6*;dK3=x$< zc%*vbjVNJAt%mS%3?VB*)3e@O5k884o**a>CI4_47@zBE^OGhj3j#gESrQrlBufGt d*EO;!eF1)BW!G6Pv=smV002ovPDHLkV1lpxZW#ao literal 0 HcmV?d00001 diff --git a/apps/timestamplog/changelog.txt b/apps/timestamplog/changelog.txt new file mode 100644 index 0000000000..ec66c5568c --- /dev/null +++ b/apps/timestamplog/changelog.txt @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json new file mode 100644 index 0000000000..8114b159da --- /dev/null +++ b/apps/timestamplog/metadata.json @@ -0,0 +1,16 @@ +{ "id": "timestamplog", + "name": "Timestamp log", + "shortName":"Timestamp log", + "icon": "app.png", + "version": "0.01", + "description": "Conveniently record a series of date/time stamps", + "tags": "timestamp, log", + "supports": ["BANGLEJS2"], + "storage": [ + {"name": "timestamplog.app.js", "url": "app.js"}, + {"name": "timestamplog.img", "url": "app-icon.js", "evaluate":true} + ], + "data": [ + {"name": "timestamplog.settings", "url": "timestamplog.settings"}, + {"name": "timestamplog.json", "url": "timestamplog.json"} +} From 453a4a0697509b503e448aa743e3e81f8b96d1b2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 27 May 2024 14:59:25 -0500 Subject: [PATCH 044/258] Fix sanity-check errors --- apps/timestamplog/changelog.txt | 1 - apps/timestamplog/metadata.json | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 apps/timestamplog/changelog.txt diff --git a/apps/timestamplog/changelog.txt b/apps/timestamplog/changelog.txt deleted file mode 100644 index ec66c5568c..0000000000 --- a/apps/timestamplog/changelog.txt +++ /dev/null @@ -1 +0,0 @@ -0.01: Initial version diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index 8114b159da..98d57e4bc6 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -1,4 +1,5 @@ -{ "id": "timestamplog", +{ + "id": "timestamplog", "name": "Timestamp log", "shortName":"Timestamp log", "icon": "app.png", @@ -8,9 +9,10 @@ "supports": ["BANGLEJS2"], "storage": [ {"name": "timestamplog.app.js", "url": "app.js"}, - {"name": "timestamplog.img", "url": "app-icon.js", "evaluate":true} + {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true} ], "data": [ {"name": "timestamplog.settings", "url": "timestamplog.settings"}, {"name": "timestamplog.json", "url": "timestamplog.json"} + ] } From 73ac306b76a046197acb8d0c56183e93e2dd81e2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 27 May 2024 19:48:47 -0500 Subject: [PATCH 045/258] Fix metadata.json error (should not try to download data files) --- apps/timestamplog/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index 98d57e4bc6..2963953c67 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -12,7 +12,7 @@ {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true} ], "data": [ - {"name": "timestamplog.settings", "url": "timestamplog.settings"}, - {"name": "timestamplog.json", "url": "timestamplog.json"} + {"name": "timestamplog.settings"}, + {"name": "timestamplog.json"} ] } From df84f229e4fcfaa929962b70e95327a23500d963 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 27 May 2024 19:49:08 -0500 Subject: [PATCH 046/258] Give up on push-to-delete; Layout's cbl callback just doesn't work --- apps/timestamplog/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 9e4fa2273c..87f227f46c 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -554,8 +554,8 @@ class LogEntryScreen { {type: 'txt', font: this.defaultFont, id: 'date', label: '?'}, {type: 'txt', font: this.defaultFont, id: 'time', label: '?'}, {type: '', id: 'placeholder', fillx: 1, filly: 1}, - {type: 'btn', font: '6x15', label: 'Hold to delete', - cbl: this.delLogItem.bind(this)}, + {type: 'btn', font: '12x20', label: 'Delete', + cb: this.delLogItem.bind(this)}, ], }, { From d7066a081fcc5dd60bb446816abb2c5a4cb3290a Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Tue, 28 May 2024 21:42:04 -0400 Subject: [PATCH 047/258] Update metadata.json --- apps/gpssetup/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gpssetup/metadata.json b/apps/gpssetup/metadata.json index b8b6dfc238..15a7c1cf2b 100644 --- a/apps/gpssetup/metadata.json +++ b/apps/gpssetup/metadata.json @@ -6,7 +6,7 @@ "description": "Configure the GPS power options and store them in the GPS nvram", "icon": "gpssetup.png", "tags": "gps,tools,outdoors", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"gpssetup","url":"gpssetup.js"}, From 75a26826f3cd03c8ac1c082929d04e35f46c4959 Mon Sep 17 00:00:00 2001 From: poolitzer Date: Wed, 19 Jun 2024 18:11:28 +0200 Subject: [PATCH 048/258] Feat: Added battery estimate in hs --- apps/daisy/ChangeLog | 1 + apps/daisy/README.md | 3 ++- apps/daisy/app.js | 25 ++++++++++++++++++++++++- apps/daisy/metadata.json | 2 +- apps/daisy/settings.js | 11 ++++++++++- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/apps/daisy/ChangeLog b/apps/daisy/ChangeLog index e9568acfc1..af2e21ae8a 100644 --- a/apps/daisy/ChangeLog +++ b/apps/daisy/ChangeLog @@ -9,3 +9,4 @@ 0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps 0.10: Use widget_utils. 0.11: Minor code improvements +0.12: Added setting to change Battery estimate to hours diff --git a/apps/daisy/README.md b/apps/daisy/README.md index 491ed697fb..5599d313cc 100644 --- a/apps/daisy/README.md +++ b/apps/daisy/README.md @@ -10,7 +10,7 @@ Forum](http://forum.espruino.com/microcosms/1424/) * Derived from [The Ring](https://banglejs.com/apps/?id=thering) proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel) * Includes the [Lazybones](https://banglejs.com/apps/?q=lazybones) Idle warning timer -* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate) +* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate, Battery Estimate) * The heart rate monitor is turned on only when Heart rate is selected and will take a few seconds to settle * The heart value is displayed in RED if the confidence value is less than 50% * NOTE: The heart rate monitor of Bangle JS 2 is not very accurate when moving about. @@ -20,6 +20,7 @@ See [#1248](https://github.com/espruino/BangleApps/issues/1248) [MyLocation](https://banglejs.com/apps/?id=mylocation) * The screen is updated every minute to save battery power * Uses the [BloggerSansLight](https://www.1001fonts.com/rounded-fonts.html?page=3) font, which if free for commercial use +* You need to run >2V22 to show the battery estimate in hours ## Future Development * Use mini icons in the information line rather that text diff --git a/apps/daisy/app.js b/apps/daisy/app.js index cba3e762dc..d64c9b31ed 100644 --- a/apps/daisy/app.js +++ b/apps/daisy/app.js @@ -83,6 +83,7 @@ function loadSettings() { settings.gy = settings.gy||'#020'; settings.fg = settings.fg||'#0f0'; settings.idle_check = (settings.idle_check === undefined ? true : settings.idle_check); + settings.batt_hours = (settings.batt_hours === undefined ? true : settings.batt_hours); assignPalettes(); } @@ -112,13 +113,35 @@ function updateSunRiseSunSet(now, lat, lon, line){ sunSet = extractTime(times.sunset); } +function batteryString(){ + let stringToInsert; + if (settings.batt_hours) { + var batt_usage = 200000/E.getPowerUsage().total; + let rounded; + if (batt_usage > 99) { + rounded = Math.round(batt_usage); + } + else if (batt_usage > 9) { + rounded = Math.round(200000/E.getPowerUsage().total * 10) / 10; + } + else { + rounded = Math.round(200000/E.getPowerUsage().total * 100) / 100; + } + stringToInsert = '\n' + rounded + ' ' + ((batt_usage < 2) ? 'h' : 'hs'); + } + else{ + stringToInsert = ' ' + E.getBattery() + '%'; + } + return 'BATTERY' + stringToInsert; +} + const infoData = { ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} }, ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, ID_SR: { calc: () => 'SUNRISE ' + sunRise }, ID_SS: { calc: () => 'SUNSET ' + sunSet }, ID_STEP: { calc: () => 'STEPS ' + getSteps() }, - ID_BATT: { calc: () => 'BATTERY ' + E.getBattery() + '%' }, + ID_BATT: { calc: batteryString}, ID_HRM: { calc: () => hrmCurrent } }; diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json index a292ef7773..81a1e593b8 100644 --- a/apps/daisy/metadata.json +++ b/apps/daisy/metadata.json @@ -1,6 +1,6 @@ { "id": "daisy", "name": "Daisy", - "version": "0.11", + "version": "0.12", "dependencies": {"mylocation":"app"}, "description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times", "icon": "app.png", diff --git a/apps/daisy/settings.js b/apps/daisy/settings.js index 6397a81f45..b1cb6b4b32 100644 --- a/apps/daisy/settings.js +++ b/apps/daisy/settings.js @@ -5,7 +5,8 @@ let s = {'gy' : '#020', 'fg' : '#0f0', 'color': 'Green', - 'check_idle' : true}; + 'check_idle' : true, + 'batt_hours' : false}; // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -45,6 +46,14 @@ s.idle_check = v; save(); }, + }, + 'Expected Battery Life In Hours': { + value: !!s.batt_hours, + onchange: v => { + s.batt_hours = v; + save(); + }, } }); }) + From aabfb6b7dbb7ba3db43cfa32032d95ca35f01b5c Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 19 Jun 2024 18:38:45 +0100 Subject: [PATCH 049/258] hwid batt: update README --- apps/hwid_a_battery_widget/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/hwid_a_battery_widget/README.md b/apps/hwid_a_battery_widget/README.md index fd7bbec678..2a1bb14007 100644 --- a/apps/hwid_a_battery_widget/README.md +++ b/apps/hwid_a_battery_widget/README.md @@ -4,14 +4,18 @@ Show the current battery level and charging status in the top right of the clock * Works with Bangle 2 * Simple design, no settings - * Red when the batterly level is below 30% + * Red when the battery level is below 30% * Blue when charging * 40 pixels wide -The high-level marker (a little bar at the 100% point) can be toggled in settings. +This is a copy of `wid_a_battery_widget`, with the main changes being: +* Reduced constants in code +* Reduced `fillRect` calls / improved efficiency +* Removal of the high-level marker ![](a_battery_widget-pic.jpg) ## Creator [@alainsaas](https://github.com/alainsaas) Mod by Hank +Mod again by bobrippling From c765f38197998a0420105950f113f46a8ed00b97 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 19 Jun 2024 18:40:00 +0100 Subject: [PATCH 050/258] hwid batt: whitespace --- apps/hwid_a_battery_widget/widget.js | 42 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index 7c76364a00..84fa32e0ce 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -21,30 +21,30 @@ }; function draw() { - var s = width - 1; - var x = this.x; - var y = this.y; - if (x !== undefined && y !== undefined) { - g.setBgColor(COLORS.white); - g.clearRect(old_x, old_y, old_x + width, old_y + height); - - const l = E.getBattery(); // debug: Math.floor(Math.random() * 101); - let xl = x+4+l*(s-12)/100; - - g.setColor(levelColor(l)); - g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar - // Show percentage - g.setColor(COLORS.black); - g.setFontAlign(0,0); - g.setFont('Vector',16); - g.drawString(l, x + 14, y + 10); - - } + var s = width - 1; + var x = this.x; + var y = this.y; + if (x !== undefined && y !== undefined) { + g.setBgColor(COLORS.white); + g.clearRect(old_x, old_y, old_x + width, old_y + height); + + const l = E.getBattery(); // debug: Math.floor(Math.random() * 101); + let xl = x+4+l*(s-12)/100; + + g.setColor(levelColor(l)); + g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar + // Show percentage + g.setColor(COLORS.black); + g.setFontAlign(0,0); + g.setFont('Vector',16); + g.drawString(l, x + 14, y + 10); + + } old_x = this.x; old_y = this.y; - if (Bangle.isCharging()) changeInterval(id, intervalHigh); - else changeInterval(id, intervalLow); + if (Bangle.isCharging()) changeInterval(id, intervalHigh); + else changeInterval(id, intervalLow); } Bangle.on('charging',function(charging) { draw(); }); From 54b1f3fcd7ee705bdf7c13bbd9c9996c611fb86f Mon Sep 17 00:00:00 2001 From: poolitzer Date: Wed, 19 Jun 2024 21:46:04 +0200 Subject: [PATCH 051/258] Feat: Convert hours > 24 to days --- apps/daisy/app.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/daisy/app.js b/apps/daisy/app.js index d64c9b31ed..7f63700445 100644 --- a/apps/daisy/app.js +++ b/apps/daisy/app.js @@ -118,8 +118,10 @@ function batteryString(){ if (settings.batt_hours) { var batt_usage = 200000/E.getPowerUsage().total; let rounded; - if (batt_usage > 99) { - rounded = Math.round(batt_usage); + if (batt_usage > 24) { + var days = Math.floor(batt_usage/24); + var hours = Math.round((batt_usage/24 - days) * 24); + stringToInsert = '\n' + days + ((days < 2) ? 'd' : 'ds') + ' ' + hours + ((hours < 2) ? 'h' : 'hs'); } else if (batt_usage > 9) { rounded = Math.round(200000/E.getPowerUsage().total * 10) / 10; @@ -127,7 +129,9 @@ function batteryString(){ else { rounded = Math.round(200000/E.getPowerUsage().total * 100) / 100; } - stringToInsert = '\n' + rounded + ' ' + ((batt_usage < 2) ? 'h' : 'hs'); + if (batt_usage < 24) { + stringToInsert = '\n' + rounded + ' ' + ((batt_usage < 2) ? 'h' : 'hs'); + } } else{ stringToInsert = ' ' + E.getBattery() + '%'; From e542bed0a094edc28d43078a5096e71f5340dc20 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Wed, 19 Jun 2024 15:29:57 -0500 Subject: [PATCH 052/258] Implement log viewing in app loader --- apps/timestamplog/interface.html | 31 +++++++++++++++++++++++++++++++ apps/timestamplog/metadata.json | 1 + 2 files changed, 32 insertions(+) create mode 100644 apps/timestamplog/interface.html diff --git a/apps/timestamplog/interface.html b/apps/timestamplog/interface.html new file mode 100644 index 0000000000..6febe78498 --- /dev/null +++ b/apps/timestamplog/interface.html @@ -0,0 +1,31 @@ + + + + + + +
Loading...
+ + + diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index 2963953c67..858fab2375 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -7,6 +7,7 @@ "description": "Conveniently record a series of date/time stamps", "tags": "timestamp, log", "supports": ["BANGLEJS2"], + "interface": "interface.html", "storage": [ {"name": "timestamplog.app.js", "url": "app.js"}, {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true} From 573d95ee39259fced85b08a789bc5bf3fca12330 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:36:44 -0400 Subject: [PATCH 053/258] Create app.js --- apps/golf-gps/app.js | 301 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 apps/golf-gps/app.js diff --git a/apps/golf-gps/app.js b/apps/golf-gps/app.js new file mode 100644 index 0000000000..8d44f4eaca --- /dev/null +++ b/apps/golf-gps/app.js @@ -0,0 +1,301 @@ +var currentHole = 1, + totalShots = 0, + courseName = "", + latLast, + lonLast, + W = g.getWidth(), + H = g.getHeight(), + lat = Array(19).fill(0), + lon = Array(19).fill(0), + par = Array(19).fill(0), + score = Array(19).fill(0), + playON = false; + +require("Font7x11Numeric7Seg").add(Graphics); + +Graphics.prototype.setFontDroidSansMono52 = function() { + // Actual height 52 (53 - 2) + // 1 BPP + return this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('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'))), + 46, + atob("HCQnHCYmKCYmJyYmGw=="), + 70 | 65536 + ); +}; + +Graphics.prototype.setFontDroidSansMono35 = function() { + // Actual height 35 (37 - 3) + // 1 BPP + return this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('AH/wAgcB/AFDgf8ArYjFF4oAehAFEnAFELIoFEj4FEv4FDgP/8AFCh//wAFCn/+Cwf/LAcH//AE4f/E5EDE5UfE4kfQAkfE4YFBMIkYRjo8BIQd//49COoIABJIJTBAAI+BNQIABDAIcBAAJQBh4IBg4FBj4aBcYRTDAoMeAoV4AqQdDAoopCF4kPHwQCCI4hTFL4rQBAALcD//8YwivEAEVgAoj6DAoxiCIAb1Eg7JDAoJLEh59BAAUfMoSJCArgAbZYMDNAhTCNAQFCgbRCAoMHAoRfBj5BCYIQFEv4pBjwFB/wFDgP8AoYoBAocH+AFDh/gAoIjBn/AAod/wAFC4b6BAoMP//+Aog/BAoMH/7ABNYX/YAIFBgAcBAoZ9EApIAQgIrBTYUBG4MHRIIFCSoSnCeoYFHNYM8HYQFFQYIFRvgFOFIKPBGoWBAonH95TD//v44FD56dCUIPHToShBw//NAX+A4JiCOwKpCABxIBAAf8AgcDAokHAokfAok/Aon/PwI6C/wFDg/4AocP+AFDn/AAod/wAFCDgKiCH4YFCDgIFDj56BAof/AAKbCAqwACIIYFZAC8BLgJsB8DvCI4PwAosAKYYDBAoYLBvAFGj0AAsY1DAo/8JoQFBv7CCAoX/MoIFBn4FEJ4PAewf/wAFCg7rBADR4CFAYjDHQIvDdIQ7BgIFCI4MDAoQeBg/8nkHAoJiBvEBOIMP4B3Dh+AWYiPEAoqnPUJUDAoabBUJoABUI6DDLAQAYF4IFZhAFEnAFEMoaqBAokfAol/AobHBO4ZlBNYJ3CcYQFB/5rCj0HQQceQQJHDv/8RoiTFf4QFBE4QFCHAIFDUgbjCAD1+QAZrBHoMD/0DNYReBw5PCAoPPPoR7BAov/wJ4Bj4eBU4UfgIFBvCJCO4IFDB4IFDAYIRJDoYjDFIWDUIIFBHYpHFLIYFDYAYiBNYYsCWokAnifan7GEHIQABGYJLBuBBCNYZTBNYYFFj+An4FDAIQFDh6JEApo3BAoodCgJJBFIUDAoWAn0PRIICBvl/d4YABfYYABR4JlBAAJxDMwR9CPAkHMoIA/ABUPMYKJBPwPAgJgCAq4jFAAg'))), + 46, + atob("FBkbFBsaHBobGxsbEw=="), + 48 | 65536 + ); +}; + +var courseData = require("Storage").open("course-data", "r"); + +var lastFix = { + lat: 0, + lon: 0, + alt: 0, // altitude in m + speed: 0, // km/h + course: 0, // heading in degree + time: 0, + satellites: 0, + fix: 0, + hdop: 0, // x5 ~ meter accuracy +}; + +const zeroPad = (num, places) => String(num).padStart(places, '0'); + +function onGPS(fix) { + Object.assign(lastFix, fix); + if (lastFix.fix && playON) showPlayData(); +} + +function radians(degrees) { + return degrees * Math.PI / 180; +} + +function readOneCourseData() { + // Clear previous data + lat = []; + lon = []; + par = []; + let l = courseData.readLine(); + courseName = l; + // Read one course data & initialize score + for (let i = 1; i < 19; i++) { + l = courseData.readLine(); + if (l !== undefined) { + const parts = l.split(','); + lat[i] = parseFloat(parts[0]).toFixed(6); + lon[i] = parseFloat(parts[1]).toFixed(6); + par[i] = parseInt(parts[2]); + score[i] = 0; + } + } +} + +function distanceCalc(lat1, long1, lat2, long2) { + const distLat = (lat1 - lat2) * 111151.3; // 111151.3 = (2*6368500*pi)/360, 6368500 ~ Earth radius at latitude 42.3° + const averageLat = (lat1 + lat2) / 2; + const distLong = (long1 - long2) * 111151.3 * Math.cos(radians(averageLat)); + return Math.sqrt(distLat * distLat + distLong * distLong) / 0.9144; // in yards +} + +function mainMenu() { + E.showPrompt("Play now or\n\nView scores?", { + title: "Golf GPS", + buttons: { + "PLAY": 1, + "VIEW": 2 + } + }).then(choice => { + if (choice === 1) fixGPS(); + else browseScore(); + }); +} + +function browseScore() { + const scoreFiles = require("Storage").list(/^Scorecard-/); + if (scoreFiles.length === 0) { + E.showPrompt("No score file found.\n\nYou need to play at least a game.", { + title: `Error`, + buttons: { + "END": 1 + } + }).then(choice => { + if (choice === 1) load(); + }); + } + let fileIndx = scoreFiles.length - 1; + + function browseFiles() { + const browsefile = require("Storage").open(scoreFiles[fileIndx].substring(0, 18), "r"); + const l = browsefile.read(80); + + E.showPrompt(l, { + buttons: { + "<<": 1, + ">>": 2, + "End": 3 + } + }).then(choice => { + if (choice === 1) fileIndx = (fileIndx - 1 + scoreFiles.length) % scoreFiles.length; + else if (choice === 2) fileIndx = (fileIndx + 1) % scoreFiles.length; + else if (choice === 3) load(); + browseFiles(); + }); + } + browseFiles(); +} + +function fixGPS() { + Bangle.on('GPS', onGPS); + Bangle.setGPSPower(1, "golf-gps"); + E.showMessage("Golf GPS v0.1\n\nWaiting for GPS fix...\n\nwritten by\nJinseok Jeon\n\n "); + + let fixInterval = setInterval(() => { + let date = new Date(); + g.clearRect(0, 150, W, H); + g.setFontAlign(1, 1).setFont('6x8:3').drawString(E.getBattery(), W, H); + g.setFontAlign(-1, 1).setFont('6x8:3').drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), 2, H); + if (lastFix.fix && lastFix.hdop <= 5) { + clearInterval(fixInterval); + searchCourse(); + } + }, 1000); // Check every second +} + +function searchCourse() { + readOneCourseData(); + if (!courseName) { + courseData = require("Storage").open("course-data", "r"); + E.showPrompt("Scan again\nor quit?", { + title: "No course found", + buttons: { + "SCAN": 1, + "QUIT": 2 + } + }).then(choice => { + if (choice === 1) searchCourse(); + else load(); + }); + } else if (distanceCalc(lastFix.lat, lastFix.lon, lat[1], lon[1]) < 1000) { + Bangle.buzz(); + E.showPrompt(courseName + "\nIs this correct?", { + buttons: { + " YES ": 1, + " NO ": 2 + } + }).then(choice => { + if (choice == 1) { + E.showPrompt("Front or Back", { + title: courseName, + buttons: { + "FRONT": 1, + "BACK": 2 + } + }).then(choice => { + currentHole = (choice === 1) ? 1 : 10; + playON = true; + showPlayData(); + }); + } else searchCourse(); + }); + } else searchCourse(); +} + +function showPlayData() { + let date = new Date(); + let distanceToHole = distanceCalc(lastFix.lat, lastFix.lon, lat[currentHole], lon[currentHole]); + let distanceFromLast = distanceCalc(lastFix.lat, lastFix.lon, latLast, lonLast) || 0; + + g.reset().clearRect(Bangle.appRect); + + // distance to hole in yards + g.setColor(g.theme.fg).setFontAlign(1, -1).setFontDroidSansMono52(); + g.drawString(distanceToHole > 1000 ? ">1k" : distanceToHole.toFixed(0), W - 2, 1); + + // distance from last shot in yards + g.setFontDroidSansMono35().setColor('#00f').drawString(distanceFromLast.toFixed(0), W - 2, 70); + + // hole number and par color(red:3, green:4, blue:5) + g.setColor(par[currentHole] == 3 ? '#f00' : (par[currentHole] == 4 ? '#0f0' : '#00f')); + g.fillRect(3, 3, 47, 47); + g.setColor(par[currentHole] == 4 ? '#000' : '#fff').setFontAlign(0, -1).drawString(currentHole, 24, 5); + + // total shots and current shots + g.setColor(g.theme.fg).setFontAlign(-1, -1); + g.drawString(totalShots, 3, 70); // total shots + g.drawString(score[currentHole], 3, 128); // current shots + g.setFont("6x8:2").drawString('TOTAL', 3, 54); + g.drawString('THIS', 3, 112); + + // clock + g.setFontAlign(1, 1).setFont("7x11Numeric7Seg:4").drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 2, H - 4); + g.drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 1, H - 3); + + // battery level bar + g.setColor('#000').drawRect(59, H * 2 / 3 - 2, W - 5, H * 2 / 3 + 4); + g.drawRect(58, H * 2 / 3 - 1, W - 4, H * 2 / 3 + 5); + g.setColor(E.getBattery() > 30 ? '#03f' : 'f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 65), H * 2 / 3 + 3); + + // hdop level indicator + if (lastFix.hdop < 5) g.setColor('#0f0').fillRect(60, H / 3, 96, H / 3 + 5); + if (lastFix.hdop < 2) g.fillRect(100, H / 3, 136, H / 3 + 5); + if (lastFix.hdop < 1) g.fillRect(140, H / 3, W, H / 3 + 5); +} + +function finishGame() { + let date = new Date(); + let saveFileContents = ' '; // ALT 255 + let parTotal = 0; + + Bangle.setGPSPower(0); + const saveFilename = `Scorecard-${date.getFullYear()}${zeroPad(date.getMonth() + 1, 2)}${zeroPad(date.getDate(), 2)}`; + const saveFile = require("Storage").open(saveFilename, "w"); + for (let i = 1; i < 19; i++) { + saveFileContents += String(score[i] - par[i]).padStart(2, ' '); + saveFileContents += (i == 18 ? ' ' : (i % 6 == 0 ? ' \n ' : '')); + parTotal += par[i]; + } + saveFile.write(`${date.getFullYear()}_${zeroPad(date.getMonth() + 1, 2)}_${zeroPad(date.getDate(), 2)}\n${courseName}${totalShots}/${parTotal}\n${saveFileContents}`); + E.showPrompt(saveFileContents, { + title: `${totalShots}/${parTotal}`, + buttons: { + "END": 1 + } + }).then(choice => { + if (choice === 1) load(); + }); +} + +setWatch(() => { + playON = false; + E.showPrompt("Finish game?", { + title: courseName, + buttons: { + " YES ": 1, + " NO ": 2 + } + }).then(choice => { + if (choice === 1) { + finishGame(); + } else { + playON = true; + showPlayData(); + } + }); +}, (process.env.HWVERSION === 2) ? BTN1 : BTN2, { + repeat: true, + edge: "falling" +}); + +Bangle.on('swipe', function(directionLR, directionUD) { + if (playON) { + currentHole = (currentHole - directionLR) % 18 || 18; + score[currentHole] = Math.max(0, score[currentHole] - directionUD); + totalShots = Math.max(0, totalShots - directionUD); + if (directionUD === -1) { + latLast = lastFix.lat; + lonLast = lastFix.lon; + } + } +}); + +Bangle.loadWidgets(); +require("widget_utils").hide(); + +// keep unlocked +Bangle.setLCDTimeout(0); +Bangle.setLocked(false); + +// keep backlight off +Bangle.setLCDBrightness(0); + +mainMenu(); From a8ff16498531db0739385e7e29333fac0f9cfe65 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:39:15 -0400 Subject: [PATCH 054/258] Add files via upload --- apps/golf-gps/ChangeLog | 2 ++ apps/golf-gps/golf-gps-icon.js | 1 + apps/golf-gps/golf-gps.png | Bin 0 -> 2197 bytes 3 files changed, 3 insertions(+) create mode 100644 apps/golf-gps/ChangeLog create mode 100644 apps/golf-gps/golf-gps-icon.js create mode 100644 apps/golf-gps/golf-gps.png diff --git a/apps/golf-gps/ChangeLog b/apps/golf-gps/ChangeLog new file mode 100644 index 0000000000..03c5da842a --- /dev/null +++ b/apps/golf-gps/ChangeLog @@ -0,0 +1,2 @@ +0.01: First release + diff --git a/apps/golf-gps/golf-gps-icon.js b/apps/golf-gps/golf-gps-icon.js new file mode 100644 index 0000000000..cc0e3a552b --- /dev/null +++ b/apps/golf-gps/golf-gps-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AHeB1nOBhMsAAQve1gAB1WjvIuIhEIlYuc1YvC0ejlcsmVWBYNVFgIADMLgvC1XM4wKEqtVlkrp8rGAQvb1Wr1X+43NBYtUFoIwCYLvN5wsC5owFv1PAAIxBhFVY4MymQvdAgIxD415vNVp9OqgYFHAMyfKYoBFIYFCGwfG0YxBvwcKGYIvW44AB5uiy/B4PGfIowadYIBB5vH3e7y4AGF74wBF4XBFwedF8oxDLg4vQAC+dAAIvsL1yOvF/6+/d34v/F/4v+uYuHuYvlhErp9PuYACp8rF8wAGlksF9oABF1wvug8HR9guBF88sRISPpF+QwBF+AwqFwKQDAoQGDL8woCGFAvGAwQwmEwwwDYchWHNAdPq1PSERVGGAWXq0yuYvgAAYJFle7F4OXMEK2IF4ZfgYAIuBlYKGR4MsYEOXudzy6FGucyF8X+3YuBF4zxBNI4AllZdiAD4A==")) \ No newline at end of file diff --git a/apps/golf-gps/golf-gps.png b/apps/golf-gps/golf-gps.png new file mode 100644 index 0000000000000000000000000000000000000000..d8faaa7f6fdec15f315717c17499ec4088e92f1c GIT binary patch literal 2197 zcmV;G2x|9n%8=a%Z(mO&K!kVNhOPKEAQB>Ga{lhtavW9xb&` z2KH}^z4q!4)m3Aj;P*eO_7t2Ao4yob-sO-x=Rsn~24kD~Pm#_S^1uc3wpG7*;J^X& z^z@*!t@&TaE5q^kUxo%ec5SaCC8J_S?%WElaKUTovlf;4{bi5M&Sw84HRVI@n8!OW zvlK$C=XeV>rtf{=0`~5B_f%tJBX%~_U)=ZC6>nbrK#B`hLEA?6bEf)qWhL&ni#fyK z)T9DflG2c2R>G{)jtG#lz8eo*z@diudzxxrL`zNSuK{XY{{~Ul)rK%!Zw_BNy6@Td zC&&KSCkf0CZ2S#F5!Rr}{VK|Aoy` z-(VXJbSDJRrd&xxjr0XIu4*p)Q`Y&JG>hn!9x>FZe+};XDQd-4~?4qADvGFg3 zM$qPaC!kYRiH}k+*g>BjJ~mJv5&U_pvL9V)1lnm_*{;}qe7)kEr0rTm*tT;UW!UnH z5^t@b@%oZ5yuV8J$+2IFSA3QG$-=|a6_;v0X6*Wa>a;6|k9sBNG3+UekBUnRoTo#8;I3fKQ)z$MQVoOWw*!l}X0 zG!#1)XS`zAS;UZ79 zXGNlmi$29O-A)OSIvu0goInrmHVwu6Xy=Em#Y{g(36ZUO<@5DMn#4H& zr2mtEW+Sbogh;I}`oo?4)4x4*hONhV=LvjYsiEnKb*%DgqiLwpj1~J$76yG5`WKWC z*&a{3>I_QkzFG6X?ED^*xFmGUDm27}&!&XPRz3aP76Uz|fM$}oMlBjG47$zQAWDdA z(J4QuGtg)6BOa5il|6w5o8>MvTnLF*s;fquUgN28;TSln|-YsYB{? zN^E6^1ds(Aar&q;27OKqj*xYcJqDXhQ=2FulBY{_uG3FJy`Dxx0)rNd8a=ic9CF0q zh(m*q>>6}gWAJ+u`!OX%L@bv{Ue0xTFBYnGzx*9k)tq%7fJCzfAxyA|VGda{;kWDIp?ZGgp(ROiaRNjVVGDpN7e?$!(Mn z5gW40gKe`Rx6c6?3EpX)um0vQ#PR98iQ?0DQ9?vw$o>IwKEyR2GHcOr5`R~$=K{Bk zgpLzt8I>5aO30>dXi_QUjyVvsSrF;C#Uee|J1V3C-;U$HLkSHDo0&p(oWrJKpSqh2Bbrf4Vff51|W8R$5#6`S=B%#GDH>fcC`JHJJ zE^_z$KM#%23O-NxIm4JsO6xX>*8kYU_Me(;;KD+jB~W;t82NxFiRY{%9oW(da%ahV z3eV!9a_gLjh4y6R`23iY?7jGEs`2i(%Lek=FYa`{CvWXh{z$OB_;(@B zd5ALQqQxDDjJ$d9WaQx|F4Jo2VIwu=zf7JiNn#>Er1#zS3Q5zc6J_SYMvK9VPhA=; z&B@1fUjb??v1_P@jfl;0le*7W83UPteaFV?Hz^@gG{ca5^fR{>D>)9yzI?o3H{H=U zgL?~GIw8XBzce(FWrEhv{Jg=gvW?aClw0FDnHY_Q}^<)YN#XrY8at(|Rn$?n8j ziwVs(4cg4ixo(&CwkmVdsm`cK_3+{2^m6!~!}g)Nzw_00000NkvXXu0mjfe8ofN literal 0 HcmV?d00001 From c6aad0ed3587c714f3ce90641ccaa9b344c22a6d Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:39:49 -0400 Subject: [PATCH 055/258] Add files via upload --- apps/golf-gps/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 apps/golf-gps/README.md diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md new file mode 100644 index 0000000000..1bb6afd659 --- /dev/null +++ b/apps/golf-gps/README.md @@ -0,0 +1,26 @@ +# Golf GPS + +I have used Rebble clock since I bought my Banglejs 2, and wanted to make my own clock with much simpler features and to switch the time window and the feature window because I'm wearing my watch on my left wrist and about a half (left side) of the screen is covered by the sleeve of my jacket or shirts. Of course it won't happen during summer, but I decided to make my first Bagle app with these changes. See Features below for the items displayed on the screen. +- The layout is inspired by the Rebble clock. +- The big font KdamThmor is copied from the Rebble clock. + +## Features +- Single screen +- No settings +- Time on the right side with big font +- On the sidebar on the left + - Day of week + - Day + - Month + - Steps + - Bluetooth connection status + - Battery % +- Update time and status every 1 minute + +## Screenshots +![](jclock_screenshot_no_BT.png) +![](jclock_screenshot_BT.png) + +## Creator + +Written by [JeonLab](https://jeonlab.wordpress.com) From 9afe604976f6622f6cd77d7c0e1a09660933bb4f Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:40:36 -0400 Subject: [PATCH 056/258] Add files via upload --- apps/golf-gps/metadata.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 apps/golf-gps/metadata.json diff --git a/apps/golf-gps/metadata.json b/apps/golf-gps/metadata.json new file mode 100644 index 0000000000..bdd833b7cd --- /dev/null +++ b/apps/golf-gps/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "golf-gps", + "name": "Golf GPS", + "version": "0.01", + "description": "Golf GPS for Banglejs 2, save golf course data (latitudes and longitudes of holes) as json file, provides distance to center of green and distance from previous shot, keeps score", + "icon": "golf-gps.png", + "type": "app", + "src": "golf-gps.app.js", + "tags": "gps,outdoors,tools", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"golf-gps.app.js","url":"golf-gps.js"}, + {"name":"golf-gps.img","url":"golf-gps-icon.js","evaluate":true} + ] +} From c7de61132e3575a6de7da6b2e26fec1706397f3c Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:43:31 -0400 Subject: [PATCH 057/258] Delete apps/golf-gps/app.js --- apps/golf-gps/app.js | 301 ------------------------------------------- 1 file changed, 301 deletions(-) delete mode 100644 apps/golf-gps/app.js diff --git a/apps/golf-gps/app.js b/apps/golf-gps/app.js deleted file mode 100644 index 8d44f4eaca..0000000000 --- a/apps/golf-gps/app.js +++ /dev/null @@ -1,301 +0,0 @@ -var currentHole = 1, - totalShots = 0, - courseName = "", - latLast, - lonLast, - W = g.getWidth(), - H = g.getHeight(), - lat = Array(19).fill(0), - lon = Array(19).fill(0), - par = Array(19).fill(0), - score = Array(19).fill(0), - playON = false; - -require("Font7x11Numeric7Seg").add(Graphics); - -Graphics.prototype.setFontDroidSansMono52 = function() { - // Actual height 52 (53 - 2) - // 1 BPP - return this.setFontCustom( - E.toString(require('heatshrink').decompress(atob('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'))), - 46, - atob("HCQnHCYmKCYmJyYmGw=="), - 70 | 65536 - ); -}; - -Graphics.prototype.setFontDroidSansMono35 = function() { - // Actual height 35 (37 - 3) - // 1 BPP - return this.setFontCustom( - E.toString(require('heatshrink').decompress(atob('AH/wAgcB/AFDgf8ArYjFF4oAehAFEnAFELIoFEj4FEv4FDgP/8AFCh//wAFCn/+Cwf/LAcH//AE4f/E5EDE5UfE4kfQAkfE4YFBMIkYRjo8BIQd//49COoIABJIJTBAAI+BNQIABDAIcBAAJQBh4IBg4FBj4aBcYRTDAoMeAoV4AqQdDAoopCF4kPHwQCCI4hTFL4rQBAALcD//8YwivEAEVgAoj6DAoxiCIAb1Eg7JDAoJLEh59BAAUfMoSJCArgAbZYMDNAhTCNAQFCgbRCAoMHAoRfBj5BCYIQFEv4pBjwFB/wFDgP8AoYoBAocH+AFDh/gAoIjBn/AAod/wAFC4b6BAoMP//+Aog/BAoMH/7ABNYX/YAIFBgAcBAoZ9EApIAQgIrBTYUBG4MHRIIFCSoSnCeoYFHNYM8HYQFFQYIFRvgFOFIKPBGoWBAonH95TD//v44FD56dCUIPHToShBw//NAX+A4JiCOwKpCABxIBAAf8AgcDAokHAokfAok/Aon/PwI6C/wFDg/4AocP+AFDn/AAod/wAFCDgKiCH4YFCDgIFDj56BAof/AAKbCAqwACIIYFZAC8BLgJsB8DvCI4PwAosAKYYDBAoYLBvAFGj0AAsY1DAo/8JoQFBv7CCAoX/MoIFBn4FEJ4PAewf/wAFCg7rBADR4CFAYjDHQIvDdIQ7BgIFCI4MDAoQeBg/8nkHAoJiBvEBOIMP4B3Dh+AWYiPEAoqnPUJUDAoabBUJoABUI6DDLAQAYF4IFZhAFEnAFEMoaqBAokfAol/AobHBO4ZlBNYJ3CcYQFB/5rCj0HQQceQQJHDv/8RoiTFf4QFBE4QFCHAIFDUgbjCAD1+QAZrBHoMD/0DNYReBw5PCAoPPPoR7BAov/wJ4Bj4eBU4UfgIFBvCJCO4IFDB4IFDAYIRJDoYjDFIWDUIIFBHYpHFLIYFDYAYiBNYYsCWokAnifan7GEHIQABGYJLBuBBCNYZTBNYYFFj+An4FDAIQFDh6JEApo3BAoodCgJJBFIUDAoWAn0PRIICBvl/d4YABfYYABR4JlBAAJxDMwR9CPAkHMoIA/ABUPMYKJBPwPAgJgCAq4jFAAg'))), - 46, - atob("FBkbFBsaHBobGxsbEw=="), - 48 | 65536 - ); -}; - -var courseData = require("Storage").open("course-data", "r"); - -var lastFix = { - lat: 0, - lon: 0, - alt: 0, // altitude in m - speed: 0, // km/h - course: 0, // heading in degree - time: 0, - satellites: 0, - fix: 0, - hdop: 0, // x5 ~ meter accuracy -}; - -const zeroPad = (num, places) => String(num).padStart(places, '0'); - -function onGPS(fix) { - Object.assign(lastFix, fix); - if (lastFix.fix && playON) showPlayData(); -} - -function radians(degrees) { - return degrees * Math.PI / 180; -} - -function readOneCourseData() { - // Clear previous data - lat = []; - lon = []; - par = []; - let l = courseData.readLine(); - courseName = l; - // Read one course data & initialize score - for (let i = 1; i < 19; i++) { - l = courseData.readLine(); - if (l !== undefined) { - const parts = l.split(','); - lat[i] = parseFloat(parts[0]).toFixed(6); - lon[i] = parseFloat(parts[1]).toFixed(6); - par[i] = parseInt(parts[2]); - score[i] = 0; - } - } -} - -function distanceCalc(lat1, long1, lat2, long2) { - const distLat = (lat1 - lat2) * 111151.3; // 111151.3 = (2*6368500*pi)/360, 6368500 ~ Earth radius at latitude 42.3° - const averageLat = (lat1 + lat2) / 2; - const distLong = (long1 - long2) * 111151.3 * Math.cos(radians(averageLat)); - return Math.sqrt(distLat * distLat + distLong * distLong) / 0.9144; // in yards -} - -function mainMenu() { - E.showPrompt("Play now or\n\nView scores?", { - title: "Golf GPS", - buttons: { - "PLAY": 1, - "VIEW": 2 - } - }).then(choice => { - if (choice === 1) fixGPS(); - else browseScore(); - }); -} - -function browseScore() { - const scoreFiles = require("Storage").list(/^Scorecard-/); - if (scoreFiles.length === 0) { - E.showPrompt("No score file found.\n\nYou need to play at least a game.", { - title: `Error`, - buttons: { - "END": 1 - } - }).then(choice => { - if (choice === 1) load(); - }); - } - let fileIndx = scoreFiles.length - 1; - - function browseFiles() { - const browsefile = require("Storage").open(scoreFiles[fileIndx].substring(0, 18), "r"); - const l = browsefile.read(80); - - E.showPrompt(l, { - buttons: { - "<<": 1, - ">>": 2, - "End": 3 - } - }).then(choice => { - if (choice === 1) fileIndx = (fileIndx - 1 + scoreFiles.length) % scoreFiles.length; - else if (choice === 2) fileIndx = (fileIndx + 1) % scoreFiles.length; - else if (choice === 3) load(); - browseFiles(); - }); - } - browseFiles(); -} - -function fixGPS() { - Bangle.on('GPS', onGPS); - Bangle.setGPSPower(1, "golf-gps"); - E.showMessage("Golf GPS v0.1\n\nWaiting for GPS fix...\n\nwritten by\nJinseok Jeon\n\n "); - - let fixInterval = setInterval(() => { - let date = new Date(); - g.clearRect(0, 150, W, H); - g.setFontAlign(1, 1).setFont('6x8:3').drawString(E.getBattery(), W, H); - g.setFontAlign(-1, 1).setFont('6x8:3').drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), 2, H); - if (lastFix.fix && lastFix.hdop <= 5) { - clearInterval(fixInterval); - searchCourse(); - } - }, 1000); // Check every second -} - -function searchCourse() { - readOneCourseData(); - if (!courseName) { - courseData = require("Storage").open("course-data", "r"); - E.showPrompt("Scan again\nor quit?", { - title: "No course found", - buttons: { - "SCAN": 1, - "QUIT": 2 - } - }).then(choice => { - if (choice === 1) searchCourse(); - else load(); - }); - } else if (distanceCalc(lastFix.lat, lastFix.lon, lat[1], lon[1]) < 1000) { - Bangle.buzz(); - E.showPrompt(courseName + "\nIs this correct?", { - buttons: { - " YES ": 1, - " NO ": 2 - } - }).then(choice => { - if (choice == 1) { - E.showPrompt("Front or Back", { - title: courseName, - buttons: { - "FRONT": 1, - "BACK": 2 - } - }).then(choice => { - currentHole = (choice === 1) ? 1 : 10; - playON = true; - showPlayData(); - }); - } else searchCourse(); - }); - } else searchCourse(); -} - -function showPlayData() { - let date = new Date(); - let distanceToHole = distanceCalc(lastFix.lat, lastFix.lon, lat[currentHole], lon[currentHole]); - let distanceFromLast = distanceCalc(lastFix.lat, lastFix.lon, latLast, lonLast) || 0; - - g.reset().clearRect(Bangle.appRect); - - // distance to hole in yards - g.setColor(g.theme.fg).setFontAlign(1, -1).setFontDroidSansMono52(); - g.drawString(distanceToHole > 1000 ? ">1k" : distanceToHole.toFixed(0), W - 2, 1); - - // distance from last shot in yards - g.setFontDroidSansMono35().setColor('#00f').drawString(distanceFromLast.toFixed(0), W - 2, 70); - - // hole number and par color(red:3, green:4, blue:5) - g.setColor(par[currentHole] == 3 ? '#f00' : (par[currentHole] == 4 ? '#0f0' : '#00f')); - g.fillRect(3, 3, 47, 47); - g.setColor(par[currentHole] == 4 ? '#000' : '#fff').setFontAlign(0, -1).drawString(currentHole, 24, 5); - - // total shots and current shots - g.setColor(g.theme.fg).setFontAlign(-1, -1); - g.drawString(totalShots, 3, 70); // total shots - g.drawString(score[currentHole], 3, 128); // current shots - g.setFont("6x8:2").drawString('TOTAL', 3, 54); - g.drawString('THIS', 3, 112); - - // clock - g.setFontAlign(1, 1).setFont("7x11Numeric7Seg:4").drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 2, H - 4); - g.drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 1, H - 3); - - // battery level bar - g.setColor('#000').drawRect(59, H * 2 / 3 - 2, W - 5, H * 2 / 3 + 4); - g.drawRect(58, H * 2 / 3 - 1, W - 4, H * 2 / 3 + 5); - g.setColor(E.getBattery() > 30 ? '#03f' : 'f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 65), H * 2 / 3 + 3); - - // hdop level indicator - if (lastFix.hdop < 5) g.setColor('#0f0').fillRect(60, H / 3, 96, H / 3 + 5); - if (lastFix.hdop < 2) g.fillRect(100, H / 3, 136, H / 3 + 5); - if (lastFix.hdop < 1) g.fillRect(140, H / 3, W, H / 3 + 5); -} - -function finishGame() { - let date = new Date(); - let saveFileContents = ' '; // ALT 255 - let parTotal = 0; - - Bangle.setGPSPower(0); - const saveFilename = `Scorecard-${date.getFullYear()}${zeroPad(date.getMonth() + 1, 2)}${zeroPad(date.getDate(), 2)}`; - const saveFile = require("Storage").open(saveFilename, "w"); - for (let i = 1; i < 19; i++) { - saveFileContents += String(score[i] - par[i]).padStart(2, ' '); - saveFileContents += (i == 18 ? ' ' : (i % 6 == 0 ? ' \n ' : '')); - parTotal += par[i]; - } - saveFile.write(`${date.getFullYear()}_${zeroPad(date.getMonth() + 1, 2)}_${zeroPad(date.getDate(), 2)}\n${courseName}${totalShots}/${parTotal}\n${saveFileContents}`); - E.showPrompt(saveFileContents, { - title: `${totalShots}/${parTotal}`, - buttons: { - "END": 1 - } - }).then(choice => { - if (choice === 1) load(); - }); -} - -setWatch(() => { - playON = false; - E.showPrompt("Finish game?", { - title: courseName, - buttons: { - " YES ": 1, - " NO ": 2 - } - }).then(choice => { - if (choice === 1) { - finishGame(); - } else { - playON = true; - showPlayData(); - } - }); -}, (process.env.HWVERSION === 2) ? BTN1 : BTN2, { - repeat: true, - edge: "falling" -}); - -Bangle.on('swipe', function(directionLR, directionUD) { - if (playON) { - currentHole = (currentHole - directionLR) % 18 || 18; - score[currentHole] = Math.max(0, score[currentHole] - directionUD); - totalShots = Math.max(0, totalShots - directionUD); - if (directionUD === -1) { - latLast = lastFix.lat; - lonLast = lastFix.lon; - } - } -}); - -Bangle.loadWidgets(); -require("widget_utils").hide(); - -// keep unlocked -Bangle.setLCDTimeout(0); -Bangle.setLocked(false); - -// keep backlight off -Bangle.setLCDBrightness(0); - -mainMenu(); From 6e464a240d647b111f61295ef1961d4341024a4b Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:44:14 -0400 Subject: [PATCH 058/258] Add files via upload --- apps/golf-gps/golf-gps.js | 301 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 apps/golf-gps/golf-gps.js diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js new file mode 100644 index 0000000000..f593c877a2 --- /dev/null +++ b/apps/golf-gps/golf-gps.js @@ -0,0 +1,301 @@ +var currentHole = 1, + totalShots = 0, + courseName = "", + latLast, + lonLast, + W = g.getWidth(), + H = g.getHeight(), + lat = Array(19).fill(0), + lon = Array(19).fill(0), + par = Array(19).fill(0), + score = Array(19).fill(0), + playON = false; + +require("Font7x11Numeric7Seg").add(Graphics); + +Graphics.prototype.setFontDroidSansMono52 = function() { + // Actual height 52 (53 - 2) + // 1 BPP + return this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('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'))), + 46, + atob("HCQnHCYmKCYmJyYmGw=="), + 70 | 65536 + ); +}; + +Graphics.prototype.setFontDroidSansMono35 = function() { + // Actual height 35 (37 - 3) + // 1 BPP + return this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('AH/wAgcB/AFDgf8ArYjFF4oAehAFEnAFELIoFEj4FEv4FDgP/8AFCh//wAFCn/+Cwf/LAcH//AE4f/E5EDE5UfE4kfQAkfE4YFBMIkYRjo8BIQd//49COoIABJIJTBAAI+BNQIABDAIcBAAJQBh4IBg4FBj4aBcYRTDAoMeAoV4AqQdDAoopCF4kPHwQCCI4hTFL4rQBAALcD//8YwivEAEVgAoj6DAoxiCIAb1Eg7JDAoJLEh59BAAUfMoSJCArgAbZYMDNAhTCNAQFCgbRCAoMHAoRfBj5BCYIQFEv4pBjwFB/wFDgP8AoYoBAocH+AFDh/gAoIjBn/AAod/wAFC4b6BAoMP//+Aog/BAoMH/7ABNYX/YAIFBgAcBAoZ9EApIAQgIrBTYUBG4MHRIIFCSoSnCeoYFHNYM8HYQFFQYIFRvgFOFIKPBGoWBAonH95TD//v44FD56dCUIPHToShBw//NAX+A4JiCOwKpCABxIBAAf8AgcDAokHAokfAok/Aon/PwI6C/wFDg/4AocP+AFDn/AAod/wAFCDgKiCH4YFCDgIFDj56BAof/AAKbCAqwACIIYFZAC8BLgJsB8DvCI4PwAosAKYYDBAoYLBvAFGj0AAsY1DAo/8JoQFBv7CCAoX/MoIFBn4FEJ4PAewf/wAFCg7rBADR4CFAYjDHQIvDdIQ7BgIFCI4MDAoQeBg/8nkHAoJiBvEBOIMP4B3Dh+AWYiPEAoqnPUJUDAoabBUJoABUI6DDLAQAYF4IFZhAFEnAFEMoaqBAokfAol/AobHBO4ZlBNYJ3CcYQFB/5rCj0HQQceQQJHDv/8RoiTFf4QFBE4QFCHAIFDUgbjCAD1+QAZrBHoMD/0DNYReBw5PCAoPPPoR7BAov/wJ4Bj4eBU4UfgIFBvCJCO4IFDB4IFDAYIRJDoYjDFIWDUIIFBHYpHFLIYFDYAYiBNYYsCWokAnifan7GEHIQABGYJLBuBBCNYZTBNYYFFj+An4FDAIQFDh6JEApo3BAoodCgJJBFIUDAoWAn0PRIICBvl/d4YABfYYABR4JlBAAJxDMwR9CPAkHMoIA/ABUPMYKJBPwPAgJgCAq4jFAAg'))), + 46, + atob("FBkbFBsaHBobGxsbEw=="), + 48 | 65536 + ); +}; + +var courseData = require("Storage").open("course-data", "r"); + +var lastFix = { + lat: 0, + lon: 0, + alt: 0, // altitude in m + speed: 0, // km/h + course: 0, // heading in degree + time: 0, + satellites: 0, + fix: 0, + hdop: 0, // x5 ~ meter accuracy +}; + +const zeroPad = (num, places) => String(num).padStart(places, '0'); + +function onGPS(fix) { + Object.assign(lastFix, fix); + if (lastFix.fix && playON) showPlayData(); +} + +function radians(degrees) { + return degrees * Math.PI / 180; +} + +function readOneCourseData() { + // Clear previous data + lat = []; + lon = []; + par = []; + let l = courseData.readLine(); + courseName = l; + // Read one course data & initialize score + for (let i = 1; i < 19; i++) { + l = courseData.readLine(); + if (l !== undefined) { + const parts = l.split(','); + lat[i] = parseFloat(parts[0]).toFixed(6); + lon[i] = parseFloat(parts[1]).toFixed(6); + par[i] = parseInt(parts[2]); + score[i] = 0; + } + } +} + +function distanceCalc(lat1, long1, lat2, long2) { + const distLat = (lat1 - lat2) * 111151.3; // 111151.3 = (2*6368500*pi)/360, 6368500 ~ Earth radius at latitude 42.3° + const averageLat = (lat1 + lat2) / 2; + const distLong = (long1 - long2) * 111151.3 * Math.cos(radians(averageLat)); + return Math.sqrt(distLat * distLat + distLong * distLong) / 0.9144; // in yards +} + +function mainMenu() { + E.showPrompt("Play now or\n\nView scores?", { + title: "Golf GPS", + buttons: { + "PLAY": 1, + "VIEW": 2 + } + }).then(choice => { + if (choice === 1) fixGPS(); + else browseScore(); + }); +} + +function browseScore() { + const scoreFiles = require("Storage").list(/^Scorecard-/); + if (scoreFiles.length === 0) { + E.showPrompt("No score file found.\n\nYou need to play at least a game.", { + title: `Error`, + buttons: { + "END": 1 + } + }).then(choice => { + if (choice === 1) load(); + }); + } + let fileIndx = scoreFiles.length - 1; + + function browseFiles() { + const browsefile = require("Storage").open(scoreFiles[fileIndx].substring(0, 18), "r"); + const l = browsefile.read(80); + + E.showPrompt(l, { + buttons: { + "<<": 1, + ">>": 2, + "End": 3 + } + }).then(choice => { + if (choice === 1) fileIndx = (fileIndx - 1 + scoreFiles.length) % scoreFiles.length; + else if (choice === 2) fileIndx = (fileIndx + 1) % scoreFiles.length; + else if (choice === 3) load(); + browseFiles(); + }); + } + browseFiles(); +} + +function fixGPS() { + Bangle.on('GPS', onGPS); + Bangle.setGPSPower(1, "golf-gps"); + E.showMessage("Golf GPS v0.1\n\nWaiting for GPS fix...\n\nwritten by\nJinseok Jeon\n\n "); + + let fixInterval = setInterval(() => { + let date = new Date(); + g.clearRect(0, 150, W, H); + g.setFontAlign(1, 1).setFont('6x8:3').drawString(E.getBattery(), W, H); + g.setFontAlign(-1, 1).setFont('6x8:3').drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), 2, H); + if (lastFix.fix && lastFix.hdop <= 5) { + clearInterval(fixInterval); + searchCourse(); + } + }, 1000); // Check every second +} + +function searchCourse() { + readOneCourseData(); + if (!courseName) { + courseData = require("Storage").open("course-data", "r"); + E.showPrompt("Scan again\nor quit?", { + title: "No course found", + buttons: { + "SCAN": 1, + "QUIT": 2 + } + }).then(choice => { + if (choice === 1) searchCourse(); + else load(); + }); + } else if (distanceCalc(lastFix.lat, lastFix.lon, lat[1], lon[1]) < 1000) { + Bangle.buzz(); + E.showPrompt(courseName + "\nIs this correct?", { + buttons: { + " YES ": 1, + " NO ": 2 + } + }).then(choice => { + if (choice == 1) { + E.showPrompt("Front or Back", { + title: courseName, + buttons: { + "FRONT": 1, + "BACK": 2 + } + }).then(choice => { + currentHole = (choice === 1) ? 1 : 10; + playON = true; + showPlayData(); + }); + } else searchCourse(); + }); + } else searchCourse(); +} + +function showPlayData() { + let date = new Date(); + let distanceToHole = distanceCalc(lastFix.lat, lastFix.lon, lat[currentHole], lon[currentHole]); + let distanceFromLast = distanceCalc(lastFix.lat, lastFix.lon, latLast, lonLast) || 0; + + g.reset().clearRect(Bangle.appRect); + + // distance to hole in yards + g.setColor(g.theme.fg).setFontAlign(1, -1).setFontDroidSansMono52(); + g.drawString(distanceToHole > 1000 ? ">1k" : distanceToHole.toFixed(0), W - 2, 1); + + // distance from last shot in yards + g.setFontDroidSansMono35().setColor('#00f').drawString(distanceFromLast.toFixed(0), W - 2, 70); + + // hole number and par color(red:3, green:4, blue:5) + g.setColor(par[currentHole] == 3 ? '#f00' : (par[currentHole] == 4 ? '#0f0' : '#00f')); + g.fillRect(3, 3, 47, 47); + g.setColor(par[currentHole] == 4 ? '#000' : '#fff').setFontAlign(0, -1).drawString(currentHole, 24, 5); + + // total shots and current shots + g.setColor(g.theme.fg).setFontAlign(-1, -1); + g.drawString(totalShots, 3, 70); // total shots + g.drawString(score[currentHole], 3, 128); // current shots + g.setFont("6x8:2").drawString('TOTAL', 3, 54); + g.drawString('THIS', 3, 112); + + // clock + g.setFontAlign(1, 1).setFont("7x11Numeric7Seg:4").drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 2, H - 4); + g.drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), W - 1, H - 3); + + // battery level bar + g.setColor('#000').drawRect(59, H * 2 / 3 - 2, W - 5, H * 2 / 3 + 4); + g.drawRect(58, H * 2 / 3 - 1, W - 4, H * 2 / 3 + 5); + g.setColor(E.getBattery() > 30 ? '#03f' : 'f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 65), H * 2 / 3 + 3); + + // hdop level indicator + if (lastFix.hdop < 5) g.setColor('#0f0').fillRect(60, H / 3, 96, H / 3 + 5); + if (lastFix.hdop < 2) g.fillRect(100, H / 3, 136, H / 3 + 5); + if (lastFix.hdop < 1) g.fillRect(140, H / 3, W, H / 3 + 5); +} + +function finishGame() { + let date = new Date(); + let saveFileContents = ' '; // ALT 255 + let parTotal = 0; + + Bangle.setGPSPower(0); + const saveFilename = `Scorecard-${date.getFullYear()}${zeroPad(date.getMonth() + 1, 2)}${zeroPad(date.getDate(), 2)}`; + const saveFile = require("Storage").open(saveFilename, "w"); + for (let i = 1; i < 19; i++) { + saveFileContents += String(score[i] - par[i]).padStart(2, ' '); + saveFileContents += (i == 18 ? ' ' : (i % 6 == 0 ? ' \n ' : '')); + parTotal += par[i]; + } + saveFile.write(`${date.getFullYear()}_${zeroPad(date.getMonth() + 1, 2)}_${zeroPad(date.getDate(), 2)}\n${courseName}${totalShots}/${parTotal}\n${saveFileContents}`); + E.showPrompt(saveFileContents, { + title: `${totalShots}/${parTotal}`, + buttons: { + "END": 1 + } + }).then(choice => { + if (choice === 1) load(); + }); +} + +setWatch(() => { + playON = false; + E.showPrompt("Finish game?", { + title: courseName, + buttons: { + " YES ": 1, + " NO ": 2 + } + }).then(choice => { + if (choice === 1) { + finishGame(); + } else { + playON = true; + showPlayData(); + } + }); +}, (process.env.HWVERSION === 2) ? BTN1 : BTN2, { + repeat: true, + edge: "falling" +}); + +Bangle.on('swipe', function(directionLR, directionUD) { + if (playON) { + currentHole = (currentHole - directionLR) % 18 || 18; + score[currentHole] = Math.max(0, score[currentHole] - directionUD); + totalShots = Math.max(0, totalShots - directionUD); + if (directionUD === -1) { + latLast = lastFix.lat; + lonLast = lastFix.lon; + } + } +}); + +Bangle.loadWidgets(); +require("widget_utils").hide(); + +// keep unlocked +Bangle.setLCDTimeout(0); +Bangle.setLocked(false); + +// keep backlight off +Bangle.setLCDBrightness(0); + +mainMenu(); \ No newline at end of file From 5bc790cb159a8ded38a7abe80a30f2f76f5dda4f Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:19:52 -0400 Subject: [PATCH 059/258] Update metadata.json --- apps/golf-gps/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/golf-gps/metadata.json b/apps/golf-gps/metadata.json index bdd833b7cd..2e6abb4fd7 100644 --- a/apps/golf-gps/metadata.json +++ b/apps/golf-gps/metadata.json @@ -5,11 +5,11 @@ "description": "Golf GPS for Banglejs 2, save golf course data (latitudes and longitudes of holes) as json file, provides distance to center of green and distance from previous shot, keeps score", "icon": "golf-gps.png", "type": "app", - "src": "golf-gps.app.js", "tags": "gps,outdoors,tools", "supports": ["BANGLEJS2"], "storage": [ {"name":"golf-gps.app.js","url":"golf-gps.js"}, {"name":"golf-gps.img","url":"golf-gps-icon.js","evaluate":true} - ] + ], + "readme":"README.md" } From 6da69f04531eadd412ebc3e9f0329e25873890be Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:30:09 -0400 Subject: [PATCH 060/258] Update metadata.json --- apps/golf-gps/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/golf-gps/metadata.json b/apps/golf-gps/metadata.json index 2e6abb4fd7..760d673e5b 100644 --- a/apps/golf-gps/metadata.json +++ b/apps/golf-gps/metadata.json @@ -2,7 +2,7 @@ "id": "golf-gps", "name": "Golf GPS", "version": "0.01", - "description": "Golf GPS for Banglejs 2, save golf course data (latitudes and longitudes of holes) as json file, provides distance to center of green and distance from previous shot, keeps score", + "description": "Golf GPS for Banglejs 2, using manually saved golf course data (latitudes and longitudes of holes, see README for instructions), provides distance to center of green and distance from previous shot, keeps score", "icon": "golf-gps.png", "type": "app", "tags": "gps,outdoors,tools", From 24958555f36e65469e3a64e0b41678b99c15347b Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:01:35 -0400 Subject: [PATCH 061/258] Update README.md --- apps/golf-gps/README.md | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md index 1bb6afd659..507be62f5e 100644 --- a/apps/golf-gps/README.md +++ b/apps/golf-gps/README.md @@ -1,21 +1,26 @@ # Golf GPS -I have used Rebble clock since I bought my Banglejs 2, and wanted to make my own clock with much simpler features and to switch the time window and the feature window because I'm wearing my watch on my left wrist and about a half (left side) of the screen is covered by the sleeve of my jacket or shirts. Of course it won't happen during summer, but I decided to make my first Bagle app with these changes. See Features below for the items displayed on the screen. -- The layout is inspired by the Rebble clock. -- The big font KdamThmor is copied from the Rebble clock. +I have made a few watches for golfing. See this [LINK](https://jeonlab.wordpress.com/category/golf-gps-watch/) if you are interested. +Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfing. For my previous watches I have used TFT LCD or OLED displays and they all draw a lot of current and display information only when I press a button to wake up from the black screen. One of the best feature of the Bangle.js 2, I think, is the memory LCD which consumes very small power and it is always on! ## Features -- Single screen -- No settings -- Time on the right side with big font -- On the sidebar on the left - - Day of week - - Day - - Month - - Steps - - Bluetooth connection status - - Battery % -- Update time and status every 1 minute +- Play or view previously played scores +- Save your favourite course data (see below instruction) +- In play mode + - Hole number + - Par as background color of the hole number (red: 3, green: 4, blue: 5) + - Distance to the center of the green (coordinates you saved) + - Distance from the last shot (where you swiped up to add a shot) + - Number of shots on current hole + - Total number of shots + - Clock +- How to change holes and add/subtract shots + - Swipe left/right to change the hole to next/previous (you can move to any hole to update your shots in case you entered wrong number of shots by mistake) + - Swip up/down to add/substract the number of shots. This will update the total number of shots as well as current shots. +- After the game + - Press the button to either finish the game or go back to the play screen (if you pressed the button by accident). + - If you choose to finish the game, it will show the summary of the score with 3x6 matix, shots - par. For example, -1 is for birdie and 0 is for par, and +2 is for double bogey. It also shows the total shots and total par as well. + ## Screenshots ![](jclock_screenshot_no_BT.png) From 172594198397b0d93f8ebcb75bef7157a7e170a7 Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Mon, 8 Apr 2024 21:40:53 +0200 Subject: [PATCH 062/258] Add EAN & UPC --- apps/cards/EAN.js | 77 +++++++++++++++++ apps/cards/EAN13.js | 92 ++++++++++++++++++++ apps/cards/EAN2.js | 30 +++++++ apps/cards/EAN5.js | 40 +++++++++ apps/cards/EAN8.js | 57 +++++++++++++ apps/cards/UPC.js | 132 +++++++++++++++++++++++++++++ apps/cards/UPCE.js | 177 +++++++++++++++++++++++++++++++++++++++ apps/cards/app.js | 54 ++++++++++++ apps/cards/constants.js | 42 ++++++++++ apps/cards/encode.js | 43 ++++++++++ apps/cards/metadata.json | 9 ++ 11 files changed, 753 insertions(+) create mode 100644 apps/cards/EAN.js create mode 100644 apps/cards/EAN13.js create mode 100644 apps/cards/EAN2.js create mode 100644 apps/cards/EAN5.js create mode 100644 apps/cards/EAN8.js create mode 100644 apps/cards/UPC.js create mode 100644 apps/cards/UPCE.js create mode 100644 apps/cards/constants.js create mode 100644 apps/cards/encode.js diff --git a/apps/cards/EAN.js b/apps/cards/EAN.js new file mode 100644 index 0000000000..bb6a4d038d --- /dev/null +++ b/apps/cards/EAN.js @@ -0,0 +1,77 @@ +//import { SIDE_BIN, MIDDLE_BIN } from './constants'; + +const Barcode = require("cards.Barcode.js"); +const encode = require("cards.encode.js"); + +// Base class for EAN8 & EAN13 +class EAN extends Barcode { + + constructor(data, options) { + super(data, options); + + // Make sure the font is not bigger than the space between the guard bars + this.fontSize = !options.flat && options.fontSize > options.width * 10 + ? options.width * 10 + : options.fontSize; + + // Make the guard bars go down half the way of the text + this.guardHeight = options.height + this.fontSize / 2 + options.textMargin; + } + + encode() { + return this.options.flat + ? this.encodeFlat() + : this.encodeGuarded(); + } + + leftText(from, to) { + return this.text.substr(from, to); + } + + leftEncode(data, structure) { + return encode(data, structure); + } + + rightText(from, to) { + return this.text.substr(from, to); + } + + rightEncode(data, structure) { + return encode(data, structure); + } + + encodeGuarded() { + const textOptions = { fontSize: this.fontSize }; + const guardOptions = { height: this.guardHeight }; + const SIDE_BIN = '101'; + const MIDDLE_BIN = '01010'; + + return [ + { data: SIDE_BIN, options: guardOptions }, + { data: this.leftEncode(), text: this.leftText(), options: textOptions }, + { data: MIDDLE_BIN, options: guardOptions }, + { data: this.rightEncode(), text: this.rightText(), options: textOptions }, + { data: SIDE_BIN, options: guardOptions }, + ]; + } + + encodeFlat() { + const SIDE_BIN = '101'; + const MIDDLE_BIN = '01010'; + const data = [ + SIDE_BIN, + this.leftEncode(), + MIDDLE_BIN, + this.rightEncode(), + SIDE_BIN + ]; + + return { + data: data.join(''), + text: this.text + }; + } + +} + +module.exports = EAN; \ No newline at end of file diff --git a/apps/cards/EAN13.js b/apps/cards/EAN13.js new file mode 100644 index 0000000000..3a914ebd06 --- /dev/null +++ b/apps/cards/EAN13.js @@ -0,0 +1,92 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Binary_encoding_of_data_digits_into_EAN-13_barcode + +import { EAN13_STRUCTURE } from './constants'; + +const EAN = require("cards.EAN.js"); + + +// Calculate the checksum digit +// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit +const checksum = (number) => { + const res = number + .substr(0, 12) + .split('') + .map((n) => +n) + .reduce((sum, a, idx) => ( + idx % 2 ? sum + a * 3 : sum + a + ), 0); + + return (10 - (res % 10)) % 10; +}; + +class EAN13 extends EAN { + + constructor(data, options) { + // Add checksum if it does not exist + if (data.search(/^[0-9]{12}$/) !== -1) { + data += checksum(data); + } + + super(data, options); + + // Adds a last character to the end of the barcode + this.lastChar = options.lastChar; + } + + valid() { + return ( + this.data.search(/^[0-9]{13}$/) !== -1 && + +this.data[12] === checksum(this.data) + ); + } + + leftText() { + return super.leftText(1, 6); + } + + leftEncode() { + const data = this.data.substr(1, 6); + const structure = EAN13_STRUCTURE[this.data[0]]; + return super.leftEncode(data, structure); + } + + rightText() { + return super.rightText(7, 6); + } + + rightEncode() { + const data = this.data.substr(7, 6); + return super.rightEncode(data, 'RRRRRR'); + } + + // The "standard" way of printing EAN13 barcodes with guard bars + encodeGuarded() { + const data = super.encodeGuarded(); + + // Extend data with left digit & last character + if (this.options.displayValue) { + data.unshift({ + data: '000000000000', + text: this.text.substr(0, 1), + options: { textAlign: 'left', fontSize: this.fontSize } + }); + + if (this.options.lastChar) { + data.push({ + data: '00' + }); + data.push({ + data: '00000', + text: this.options.lastChar, + options: { fontSize: this.fontSize } + }); + } + } + + return data; + } + +} + +module.exports = EAN13; \ No newline at end of file diff --git a/apps/cards/EAN2.js b/apps/cards/EAN2.js new file mode 100644 index 0000000000..c7ceb059bf --- /dev/null +++ b/apps/cards/EAN2.js @@ -0,0 +1,30 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/EAN_2#Encoding + +const constants = require("cards.constants.js"); +const encode = require("cards.encode.js"); +const Barcode = require("cards.Barcode.js"); + +class EAN2 extends Barcode { + + constructor(data, options) { + super(data, options); + } + + valid() { + return this.data.search(/^[0-9]{2}$/) !== -1; + } + + encode(){ + // Choose the structure based on the number mod 4 + const structure = constants.EAN2_STRUCTURE[parseInt(this.data) % 4]; + return { + // Start bits + Encode the two digits with 01 in between + data: '1011' + encode(this.data, structure, '01'), + text: this.text + }; + } + +} + +module.exports = EAN2; diff --git a/apps/cards/EAN5.js b/apps/cards/EAN5.js new file mode 100644 index 0000000000..77217cd95f --- /dev/null +++ b/apps/cards/EAN5.js @@ -0,0 +1,40 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/EAN_5#Encoding + +const constants = require("cards.constants.js"); +const encode = require("cards.encode.js"); +const Barcode = require("cards.Barcode.js"); + +const checksum = (data) => { + const result = data + .split('') + .map(n => +n) + .reduce((sum, a, idx) => { + return idx % 2 + ? sum + a * 9 + : sum + a * 3; + }, 0); + return result % 10; +}; + +class EAN5 extends Barcode { + + constructor(data, options) { + super(data, options); + } + + valid() { + return this.data.search(/^[0-9]{5}$/) !== -1; + } + + encode() { + const structure = constants.EAN5_STRUCTURE[checksum(this.data)]; + return { + data: '1011' + encode(this.data, structure, '01'), + text: this.text + }; + } + +} + +module.exports = EAN5; diff --git a/apps/cards/EAN8.js b/apps/cards/EAN8.js new file mode 100644 index 0000000000..7ffb41c4ca --- /dev/null +++ b/apps/cards/EAN8.js @@ -0,0 +1,57 @@ +// Encoding documentation: +// http://www.barcodeisland.com/ean8.phtml + +const EAN = require("cards.EAN.js"); + +// Calculate the checksum digit +const checksum = (number) => { + const res = number + .substr(0, 7) + .split('') + .map((n) => +n) + .reduce((sum, a, idx) => ( + idx % 2 ? sum + a : sum + a * 3 + ), 0); + + return (10 - (res % 10)) % 10; +}; + +class EAN8 extends EAN { + + constructor(data, options) { + // Add checksum if it does not exist + if (data.search(/^[0-9]{7}$/) !== -1) { + data += checksum(data); + } + + super(data, options); + } + + valid() { + return ( + this.data.search(/^[0-9]{8}$/) !== -1 && + +this.data[7] === checksum(this.data) + ); + } + + leftText() { + return super.leftText(0, 4); + } + + leftEncode() { + const data = this.data.substr(0, 4); + return super.leftEncode(data, 'LLLL'); + } + + rightText() { + return super.rightText(4, 4); + } + + rightEncode() { + const data = this.data.substr(4, 4); + return super.rightEncode(data, 'RRRR'); + } + +} + +module.exports = EAN8; diff --git a/apps/cards/UPC.js b/apps/cards/UPC.js new file mode 100644 index 0000000000..3344a0785c --- /dev/null +++ b/apps/cards/UPC.js @@ -0,0 +1,132 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding + +const encode = require("cards.encode.js"); +const Barcode = require("cards.Barcode.js"); + +class UPC extends Barcode{ + constructor(data, options){ + // Add checksum if it does not exist + if(data.search(/^[0-9]{11}$/) !== -1){ + data += checksum(data); + } + + super(data, options); + + this.displayValue = options.displayValue; + + // Make sure the font is not bigger than the space between the guard bars + if(options.fontSize > options.width * 10){ + this.fontSize = options.width * 10; + } + else{ + this.fontSize = options.fontSize; + } + + // Make the guard bars go down half the way of the text + this.guardHeight = options.height + this.fontSize / 2 + options.textMargin; + } + + valid(){ + return this.data.search(/^[0-9]{12}$/) !== -1 && + this.data[11] == checksum(this.data); + } + + encode(){ + if(this.options.flat){ + return this.flatEncoding(); + } + else{ + return this.guardedEncoding(); + } + } + + flatEncoding(){ + var result = ""; + + result += "101"; + result += encode(this.data.substr(0, 6), "LLLLLL"); + result += "01010"; + result += encode(this.data.substr(6, 6), "RRRRRR"); + result += "101"; + + return { + data: result, + text: this.text + }; + } + + guardedEncoding(){ + var result = []; + + // Add the first digit + if(this.displayValue){ + result.push({ + data: "00000000", + text: this.text.substr(0, 1), + options: {textAlign: "left", fontSize: this.fontSize} + }); + } + + // Add the guard bars + result.push({ + data: "101" + encode(this.data[0], "L"), + options: {height: this.guardHeight} + }); + + // Add the left side + result.push({ + data: encode(this.data.substr(1, 5), "LLLLL"), + text: this.text.substr(1, 5), + options: {fontSize: this.fontSize} + }); + + // Add the middle bits + result.push({ + data: "01010", + options: {height: this.guardHeight} + }); + + // Add the right side + result.push({ + data: encode(this.data.substr(6, 5), "RRRRR"), + text: this.text.substr(6, 5), + options: {fontSize: this.fontSize} + }); + + // Add the end bits + result.push({ + data: encode(this.data[11], "R") + "101", + options: {height: this.guardHeight} + }); + + // Add the last digit + if(this.displayValue){ + result.push({ + data: "00000000", + text: this.text.substr(11, 1), + options: {textAlign: "right", fontSize: this.fontSize} + }); + } + + return result; + } +} + +// Calulate the checksum digit +// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit +function checksum(number){ + var result = 0; + + var i; + for(i = 1; i < 11; i += 2){ + result += parseInt(number[i]); + } + for(i = 0; i < 11; i += 2){ + result += parseInt(number[i]) * 3; + } + + return (10 - (result % 10)) % 10; +} + +module.exports = { UPC, checksum }; diff --git a/apps/cards/UPCE.js b/apps/cards/UPCE.js new file mode 100644 index 0000000000..1b40153221 --- /dev/null +++ b/apps/cards/UPCE.js @@ -0,0 +1,177 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding +// +// UPC-E documentation: +// https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E + +const encode = require("cards.encode.js"); +const Barcode = require("cards.Barcode.js"); +const upc = require("cards.UPC.js"); + +const EXPANSIONS = [ + "XX00000XXX", + "XX10000XXX", + "XX20000XXX", + "XXX00000XX", + "XXXX00000X", + "XXXXX00005", + "XXXXX00006", + "XXXXX00007", + "XXXXX00008", + "XXXXX00009" +]; + +const PARITIES = [ + ["EEEOOO", "OOOEEE"], + ["EEOEOO", "OOEOEE"], + ["EEOOEO", "OOEEOE"], + ["EEOOOE", "OOEEEO"], + ["EOEEOO", "OEOOEE"], + ["EOOEEO", "OEEOOE"], + ["EOOOEE", "OEEEOO"], + ["EOEOEO", "OEOEOE"], + ["EOEOOE", "OEOEEO"], + ["EOOEOE", "OEEOEO"] +]; + +class UPCE extends Barcode{ + constructor(data, options){ + // Code may be 6 or 8 digits; + // A 7 digit code is ambiguous as to whether the extra digit + // is a UPC-A check or number system digit. + super(data, options); + this.isValid = false; + if(data.search(/^[0-9]{6}$/) !== -1){ + this.middleDigits = data; + this.upcA = expandToUPCA(data, "0"); + this.text = options.text || + `${this.upcA[0]}${data}${this.upcA[this.upcA.length - 1]}`; + this.isValid = true; + } + else if(data.search(/^[01][0-9]{7}$/) !== -1){ + this.middleDigits = data.substring(1, data.length - 1); + this.upcA = expandToUPCA(this.middleDigits, data[0]); + + if(this.upcA[this.upcA.length - 1] === data[data.length - 1]){ + this.isValid = true; + } + else{ + // checksum mismatch + return; + } + } + else{ + return; + } + + this.displayValue = options.displayValue; + + // Make sure the font is not bigger than the space between the guard bars + if(options.fontSize > options.width * 10){ + this.fontSize = options.width * 10; + } + else{ + this.fontSize = options.fontSize; + } + + // Make the guard bars go down half the way of the text + this.guardHeight = options.height + this.fontSize / 2 + options.textMargin; + } + + valid(){ + return this.isValid; + } + + encode(){ + if(this.options.flat){ + return this.flatEncoding(); + } + else{ + return this.guardedEncoding(); + } + } + + flatEncoding(){ + var result = ""; + + result += "101"; + result += this.encodeMiddleDigits(); + result += "010101"; + + return { + data: result, + text: this.text + }; + } + + guardedEncoding(){ + var result = []; + + // Add the UPC-A number system digit beneath the quiet zone + if(this.displayValue){ + result.push({ + data: "00000000", + text: this.text[0], + options: {textAlign: "left", fontSize: this.fontSize} + }); + } + + // Add the guard bars + result.push({ + data: "101", + options: {height: this.guardHeight} + }); + + // Add the 6 UPC-E digits + result.push({ + data: this.encodeMiddleDigits(), + text: this.text.substring(1, 7), + options: {fontSize: this.fontSize} + }); + + // Add the end bits + result.push({ + data: "010101", + options: {height: this.guardHeight} + }); + + // Add the UPC-A check digit beneath the quiet zone + if(this.displayValue){ + result.push({ + data: "00000000", + text: this.text[7], + options: {textAlign: "right", fontSize: this.fontSize} + }); + } + + return result; + } + + encodeMiddleDigits() { + const numberSystem = this.upcA[0]; + const checkDigit = this.upcA[this.upcA.length - 1]; + const parity = PARITIES[parseInt(checkDigit)][parseInt(numberSystem)]; + return encode(this.middleDigits, parity); + } +} + +function expandToUPCA(middleDigits, numberSystem) { + const lastUpcE = parseInt(middleDigits[middleDigits.length - 1]); + const expansion = EXPANSIONS[lastUpcE]; + + let result = ""; + let digitIndex = 0; + for(let i = 0; i < expansion.length; i++) { + let c = expansion[i]; + if (c === 'X') { + result += middleDigits[digitIndex++]; + } else { + result += c; + } + } + + result = `${numberSystem}${result}`; + return `${result}${upc.checksum(result)}`; +} + +module.exports = UPCE; \ No newline at end of file diff --git a/apps/cards/app.js b/apps/cards/app.js index c0bacc9180..25af661966 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -132,6 +132,60 @@ function showCode(card) { printLinearCode(code.encode().data); break; } + case "EAN_2": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const EAN2 = require("cards.EAN2.js"); + let code = new EAN2(card.value, {}); + printLinearCode(code.encode().data); + break; + } + case "EAN_5": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const EAN5 = require("cards.EAN5.js"); + let code = new EAN5(card.value, {}); + printLinearCode(code.encode().data); + break; + } + case "EAN_8": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const EAN8 = require("cards.EAN8.js"); + let code = new EAN8(card.value, {}); + printLinearCode(code.encode().data); + break; + } + case "EAN_13": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const EAN13 = require("cards.EAN13.js"); + let code = new EAN13(card.value, {}); + printLinearCode(code.encode().data); + break; + } + case "UPC_A": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const UPC = require("cards.UPC.js"); + let code = new UPC.UPC(card.value, {}); + printLinearCode(code.encode().data); + break; + } + case "UPC_E": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const UPCE = require("cards.UPCE.js"); + let code = new UPCE(card.value, {}); + printLinearCode(code.encode().data); + break; + } default: g.clear(true); g.setFont("Vector:30"); diff --git a/apps/cards/constants.js b/apps/cards/constants.js new file mode 100644 index 0000000000..0d9f624158 --- /dev/null +++ b/apps/cards/constants.js @@ -0,0 +1,42 @@ +// Standard start end and middle bits + +export const SIDE_BIN = '101'; +export const MIDDLE_BIN = '01010'; + +export const BINARIES = { + 'L': [ // The L (left) type of encoding + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'G': [ // The G type of encoding + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ], + 'R': [ // The R (right) type of encoding + '1110010', '1100110', '1101100', '1000010', '1011100', + '1001110', '1010000', '1000100', '1001000', '1110100' + ], + 'O': [ // The O (odd) encoding for UPC-E + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'E': [ // The E (even) encoding for UPC-E + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ] +}; + +// Define the EAN-2 structure +export const EAN2_STRUCTURE = ['LL', 'LG', 'GL', 'GG']; + +// Define the EAN-5 structure +export const EAN5_STRUCTURE = [ + 'GGLLL', 'GLGLL', 'GLLGL', 'GLLLG', 'LGGLL', + 'LLGGL', 'LLLGG', 'LGLGL', 'LGLLG', 'LLGLG' +]; + +// Define the EAN-13 structure +export const EAN13_STRUCTURE = [ + 'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', + 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL' +]; \ No newline at end of file diff --git a/apps/cards/encode.js b/apps/cards/encode.js new file mode 100644 index 0000000000..960bf49918 --- /dev/null +++ b/apps/cards/encode.js @@ -0,0 +1,43 @@ +//import { BINARIES } from './constants'; + +const BINARIES = { + 'L': [ // The L (left) type of encoding + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'G': [ // The G type of encoding + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ], + 'R': [ // The R (right) type of encoding + '1110010', '1100110', '1101100', '1000010', '1011100', + '1001110', '1010000', '1000100', '1001000', '1110100' + ], + 'O': [ // The O (odd) encoding for UPC-E + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'E': [ // The E (even) encoding for UPC-E + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ] +}; + +// Encode data string +const encode = (data, structure, separator) => { + let encoded = data + .split('') + .map((val, idx) => BINARIES[structure[idx]]) + .map((val, idx) => val ? val[data[idx]] : ''); + + if (separator) { + const last = data.length - 1; + encoded = encoded.map((val, idx) => ( + idx < last ? val + separator : val + )); + } + + return encoded.join(''); +}; + +module.exports = encode; \ No newline at end of file diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index 4bc3c9ae1e..fe5630438f 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -15,6 +15,15 @@ {"name":"cards.qrcode.js","url":"qrcode.js"}, {"name":"cards.codabar.js","url":"codabar.js"}, {"name":"cards.code39.js","url":"code39.js"}, + {"name":"cards.constants.js","url":"constants.js"}, + {"name":"cards.EAN.js","url":"EAN.js"}, + {"name":"cards.EAN2.js","url":"EAN2.js"}, + {"name":"cards.EAN5.js","url":"EAN5.js"}, + {"name":"cards.EAN8.js","url":"EAN8.js"}, + {"name":"cards.EAN13.js","url":"EAN13.js"}, + {"name":"cards.UPC.js","url":"UPC.js"}, + {"name":"cards.UPCE.js","url":"UPCE.js"}, + {"name":"cards.encode.js","url":"encode.js"}, {"name":"cards.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"cards.settings.json"}] From 1f596995e295031cb7fd49b3560dd98236ac7782 Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Mon, 8 Apr 2024 22:37:42 +0200 Subject: [PATCH 063/258] allow emulator --- apps/cards/metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index fe5630438f..bc6bedce65 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -7,6 +7,7 @@ "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"], + "allow_emulator": true, "readme": "README.md", "storage": [ {"name":"cards.app.js","url":"app.js"}, From 50517444aa9bcdd1fb21f85ae65d21315c85e031 Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Tue, 9 Apr 2024 09:19:15 +0200 Subject: [PATCH 064/258] Only need flat encoding. --- apps/cards/EAN.js | 38 ++-------------------- apps/cards/EAN13.js | 27 ---------------- apps/cards/UPC.js | 78 --------------------------------------------- apps/cards/UPCE.js | 68 --------------------------------------- 4 files changed, 3 insertions(+), 208 deletions(-) diff --git a/apps/cards/EAN.js b/apps/cards/EAN.js index bb6a4d038d..17b4cbaefc 100644 --- a/apps/cards/EAN.js +++ b/apps/cards/EAN.js @@ -1,27 +1,12 @@ -//import { SIDE_BIN, MIDDLE_BIN } from './constants'; +import { SIDE_BIN, MIDDLE_BIN } from './constants'; -const Barcode = require("cards.Barcode.js"); const encode = require("cards.encode.js"); +const Barcode = require("cards.Barcode.js"); // Base class for EAN8 & EAN13 class EAN extends Barcode { - constructor(data, options) { super(data, options); - - // Make sure the font is not bigger than the space between the guard bars - this.fontSize = !options.flat && options.fontSize > options.width * 10 - ? options.width * 10 - : options.fontSize; - - // Make the guard bars go down half the way of the text - this.guardHeight = options.height + this.fontSize / 2 + options.textMargin; - } - - encode() { - return this.options.flat - ? this.encodeFlat() - : this.encodeGuarded(); } leftText(from, to) { @@ -40,24 +25,7 @@ class EAN extends Barcode { return encode(data, structure); } - encodeGuarded() { - const textOptions = { fontSize: this.fontSize }; - const guardOptions = { height: this.guardHeight }; - const SIDE_BIN = '101'; - const MIDDLE_BIN = '01010'; - - return [ - { data: SIDE_BIN, options: guardOptions }, - { data: this.leftEncode(), text: this.leftText(), options: textOptions }, - { data: MIDDLE_BIN, options: guardOptions }, - { data: this.rightEncode(), text: this.rightText(), options: textOptions }, - { data: SIDE_BIN, options: guardOptions }, - ]; - } - - encodeFlat() { - const SIDE_BIN = '101'; - const MIDDLE_BIN = '01010'; + encode() { const data = [ SIDE_BIN, this.leftEncode(), diff --git a/apps/cards/EAN13.js b/apps/cards/EAN13.js index 3a914ebd06..323daeef85 100644 --- a/apps/cards/EAN13.js +++ b/apps/cards/EAN13.js @@ -60,33 +60,6 @@ class EAN13 extends EAN { return super.rightEncode(data, 'RRRRRR'); } - // The "standard" way of printing EAN13 barcodes with guard bars - encodeGuarded() { - const data = super.encodeGuarded(); - - // Extend data with left digit & last character - if (this.options.displayValue) { - data.unshift({ - data: '000000000000', - text: this.text.substr(0, 1), - options: { textAlign: 'left', fontSize: this.fontSize } - }); - - if (this.options.lastChar) { - data.push({ - data: '00' - }); - data.push({ - data: '00000', - text: this.options.lastChar, - options: { fontSize: this.fontSize } - }); - } - } - - return data; - } - } module.exports = EAN13; \ No newline at end of file diff --git a/apps/cards/UPC.js b/apps/cards/UPC.js index 3344a0785c..b7488b12c3 100644 --- a/apps/cards/UPC.js +++ b/apps/cards/UPC.js @@ -12,19 +12,6 @@ class UPC extends Barcode{ } super(data, options); - - this.displayValue = options.displayValue; - - // Make sure the font is not bigger than the space between the guard bars - if(options.fontSize > options.width * 10){ - this.fontSize = options.width * 10; - } - else{ - this.fontSize = options.fontSize; - } - - // Make the guard bars go down half the way of the text - this.guardHeight = options.height + this.fontSize / 2 + options.textMargin; } valid(){ @@ -33,15 +20,6 @@ class UPC extends Barcode{ } encode(){ - if(this.options.flat){ - return this.flatEncoding(); - } - else{ - return this.guardedEncoding(); - } - } - - flatEncoding(){ var result = ""; result += "101"; @@ -55,62 +33,6 @@ class UPC extends Barcode{ text: this.text }; } - - guardedEncoding(){ - var result = []; - - // Add the first digit - if(this.displayValue){ - result.push({ - data: "00000000", - text: this.text.substr(0, 1), - options: {textAlign: "left", fontSize: this.fontSize} - }); - } - - // Add the guard bars - result.push({ - data: "101" + encode(this.data[0], "L"), - options: {height: this.guardHeight} - }); - - // Add the left side - result.push({ - data: encode(this.data.substr(1, 5), "LLLLL"), - text: this.text.substr(1, 5), - options: {fontSize: this.fontSize} - }); - - // Add the middle bits - result.push({ - data: "01010", - options: {height: this.guardHeight} - }); - - // Add the right side - result.push({ - data: encode(this.data.substr(6, 5), "RRRRR"), - text: this.text.substr(6, 5), - options: {fontSize: this.fontSize} - }); - - // Add the end bits - result.push({ - data: encode(this.data[11], "R") + "101", - options: {height: this.guardHeight} - }); - - // Add the last digit - if(this.displayValue){ - result.push({ - data: "00000000", - text: this.text.substr(11, 1), - options: {textAlign: "right", fontSize: this.fontSize} - }); - } - - return result; - } } // Calulate the checksum digit diff --git a/apps/cards/UPCE.js b/apps/cards/UPCE.js index 1b40153221..bd082dd4d2 100644 --- a/apps/cards/UPCE.js +++ b/apps/cards/UPCE.js @@ -60,22 +60,6 @@ class UPCE extends Barcode{ return; } } - else{ - return; - } - - this.displayValue = options.displayValue; - - // Make sure the font is not bigger than the space between the guard bars - if(options.fontSize > options.width * 10){ - this.fontSize = options.width * 10; - } - else{ - this.fontSize = options.fontSize; - } - - // Make the guard bars go down half the way of the text - this.guardHeight = options.height + this.fontSize / 2 + options.textMargin; } valid(){ @@ -83,15 +67,6 @@ class UPCE extends Barcode{ } encode(){ - if(this.options.flat){ - return this.flatEncoding(); - } - else{ - return this.guardedEncoding(); - } - } - - flatEncoding(){ var result = ""; result += "101"; @@ -104,49 +79,6 @@ class UPCE extends Barcode{ }; } - guardedEncoding(){ - var result = []; - - // Add the UPC-A number system digit beneath the quiet zone - if(this.displayValue){ - result.push({ - data: "00000000", - text: this.text[0], - options: {textAlign: "left", fontSize: this.fontSize} - }); - } - - // Add the guard bars - result.push({ - data: "101", - options: {height: this.guardHeight} - }); - - // Add the 6 UPC-E digits - result.push({ - data: this.encodeMiddleDigits(), - text: this.text.substring(1, 7), - options: {fontSize: this.fontSize} - }); - - // Add the end bits - result.push({ - data: "010101", - options: {height: this.guardHeight} - }); - - // Add the UPC-A check digit beneath the quiet zone - if(this.displayValue){ - result.push({ - data: "00000000", - text: this.text[7], - options: {textAlign: "right", fontSize: this.fontSize} - }); - } - - return result; - } - encodeMiddleDigits() { const numberSystem = this.upcA[0]; const checkDigit = this.upcA[this.upcA.length - 1]; From 67e7e71df1a8765d63e4b09ddf98bc40ebf3bc5c Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Tue, 18 Jun 2024 14:37:07 +0200 Subject: [PATCH 065/258] Use require instead of unsupported import/export --- apps/cards/EAN.js | 8 ++--- apps/cards/EAN13.js | 4 +-- apps/cards/constants.js | 77 +++++++++++++++++++++-------------------- apps/cards/encode.js | 27 ++------------- 4 files changed, 47 insertions(+), 69 deletions(-) diff --git a/apps/cards/EAN.js b/apps/cards/EAN.js index 17b4cbaefc..d9d857c0e4 100644 --- a/apps/cards/EAN.js +++ b/apps/cards/EAN.js @@ -1,4 +1,4 @@ -import { SIDE_BIN, MIDDLE_BIN } from './constants'; +const constants = require("cards.constants.js"); const encode = require("cards.encode.js"); const Barcode = require("cards.Barcode.js"); @@ -27,11 +27,11 @@ class EAN extends Barcode { encode() { const data = [ - SIDE_BIN, + constants.SIDE_BIN, this.leftEncode(), - MIDDLE_BIN, + constants.MIDDLE_BIN, this.rightEncode(), - SIDE_BIN + constants.SIDE_BIN ]; return { diff --git a/apps/cards/EAN13.js b/apps/cards/EAN13.js index 323daeef85..01be1c8df4 100644 --- a/apps/cards/EAN13.js +++ b/apps/cards/EAN13.js @@ -1,7 +1,7 @@ // Encoding documentation: // https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Binary_encoding_of_data_digits_into_EAN-13_barcode -import { EAN13_STRUCTURE } from './constants'; +const constants = require("cards.constants.js"); const EAN = require("cards.EAN.js"); @@ -47,7 +47,7 @@ class EAN13 extends EAN { leftEncode() { const data = this.data.substr(1, 6); - const structure = EAN13_STRUCTURE[this.data[0]]; + const structure = constants.EAN13_STRUCTURE[this.data[0]]; return super.leftEncode(data, structure); } diff --git a/apps/cards/constants.js b/apps/cards/constants.js index 0d9f624158..0f7244ce1c 100644 --- a/apps/cards/constants.js +++ b/apps/cards/constants.js @@ -1,42 +1,43 @@ -// Standard start end and middle bits +module.exports = { + // Standard start end and middle bits + SIDE_BIN : '101', + MIDDLE_BIN : '01010', -export const SIDE_BIN = '101'; -export const MIDDLE_BIN = '01010'; + BINARIES : { + 'L': [ // The L (left) type of encoding + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'G': [ // The G type of encoding + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ], + 'R': [ // The R (right) type of encoding + '1110010', '1100110', '1101100', '1000010', '1011100', + '1001110', '1010000', '1000100', '1001000', '1110100' + ], + 'O': [ // The O (odd) encoding for UPC-E + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'E': [ // The E (even) encoding for UPC-E + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ] + }, -export const BINARIES = { - 'L': [ // The L (left) type of encoding - '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' - ], - 'G': [ // The G type of encoding - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ], - 'R': [ // The R (right) type of encoding - '1110010', '1100110', '1101100', '1000010', '1011100', - '1001110', '1010000', '1000100', '1001000', '1110100' - ], - 'O': [ // The O (odd) encoding for UPC-E - '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' - ], - 'E': [ // The E (even) encoding for UPC-E - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ] -}; - -// Define the EAN-2 structure -export const EAN2_STRUCTURE = ['LL', 'LG', 'GL', 'GG']; + // Define the EAN-2 structure + EAN2_STRUCTURE : ['LL', 'LG', 'GL', 'GG'], -// Define the EAN-5 structure -export const EAN5_STRUCTURE = [ - 'GGLLL', 'GLGLL', 'GLLGL', 'GLLLG', 'LGGLL', - 'LLGGL', 'LLLGG', 'LGLGL', 'LGLLG', 'LLGLG' -]; + // Define the EAN-5 structure + EAN5_STRUCTURE : [ + 'GGLLL', 'GLGLL', 'GLLGL', 'GLLLG', 'LGGLL', + 'LLGGL', 'LLLGG', 'LGLGL', 'LGLLG', 'LLGLG' + ], -// Define the EAN-13 structure -export const EAN13_STRUCTURE = [ - 'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', - 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL' -]; \ No newline at end of file + // Define the EAN-13 structure + EAN13_STRUCTURE : [ + 'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', + 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL' + ] +} \ No newline at end of file diff --git a/apps/cards/encode.js b/apps/cards/encode.js index 960bf49918..b484964c71 100644 --- a/apps/cards/encode.js +++ b/apps/cards/encode.js @@ -1,33 +1,10 @@ -//import { BINARIES } from './constants'; - -const BINARIES = { - 'L': [ // The L (left) type of encoding - '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' - ], - 'G': [ // The G type of encoding - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ], - 'R': [ // The R (right) type of encoding - '1110010', '1100110', '1101100', '1000010', '1011100', - '1001110', '1010000', '1000100', '1001000', '1110100' - ], - 'O': [ // The O (odd) encoding for UPC-E - '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' - ], - 'E': [ // The E (even) encoding for UPC-E - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ] -}; +const constants = require("cards.constants.js"); // Encode data string const encode = (data, structure, separator) => { let encoded = data .split('') - .map((val, idx) => BINARIES[structure[idx]]) + .map((val, idx) => constants.BINARIES[structure[idx]]) .map((val, idx) => val ? val[data[idx]] : ''); if (separator) { From fd2b3473492919ca6a22d2106fba63b6cdb54f66 Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Tue, 9 Apr 2024 09:17:10 +0200 Subject: [PATCH 066/258] Espruino doesn't support search or {} Regex quantifier, replace with test --- apps/cards/EAN13.js | 4 ++-- apps/cards/EAN2.js | 2 +- apps/cards/EAN5.js | 2 +- apps/cards/EAN8.js | 4 ++-- apps/cards/UPC.js | 4 ++-- apps/cards/UPCE.js | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/cards/EAN13.js b/apps/cards/EAN13.js index 01be1c8df4..f04417b6ae 100644 --- a/apps/cards/EAN13.js +++ b/apps/cards/EAN13.js @@ -24,7 +24,7 @@ class EAN13 extends EAN { constructor(data, options) { // Add checksum if it does not exist - if (data.search(/^[0-9]{12}$/) !== -1) { + if (/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)) { data += checksum(data); } @@ -36,7 +36,7 @@ class EAN13 extends EAN { valid() { return ( - this.data.search(/^[0-9]{13}$/) !== -1 && + /^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) && +this.data[12] === checksum(this.data) ); } diff --git a/apps/cards/EAN2.js b/apps/cards/EAN2.js index c7ceb059bf..840a98ce0f 100644 --- a/apps/cards/EAN2.js +++ b/apps/cards/EAN2.js @@ -12,7 +12,7 @@ class EAN2 extends Barcode { } valid() { - return this.data.search(/^[0-9]{2}$/) !== -1; + return /^[0-9][0-9]$/.test(this.data); } encode(){ diff --git a/apps/cards/EAN5.js b/apps/cards/EAN5.js index 77217cd95f..241f422e15 100644 --- a/apps/cards/EAN5.js +++ b/apps/cards/EAN5.js @@ -24,7 +24,7 @@ class EAN5 extends Barcode { } valid() { - return this.data.search(/^[0-9]{5}$/) !== -1; + return /^[0-9][0-9][0-9][0-9][0-9]$/.test(this.data); } encode() { diff --git a/apps/cards/EAN8.js b/apps/cards/EAN8.js index 7ffb41c4ca..abcf141fc4 100644 --- a/apps/cards/EAN8.js +++ b/apps/cards/EAN8.js @@ -20,7 +20,7 @@ class EAN8 extends EAN { constructor(data, options) { // Add checksum if it does not exist - if (data.search(/^[0-9]{7}$/) !== -1) { + if (/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)) { data += checksum(data); } @@ -29,7 +29,7 @@ class EAN8 extends EAN { valid() { return ( - this.data.search(/^[0-9]{8}$/) !== -1 && + /^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) && +this.data[7] === checksum(this.data) ); } diff --git a/apps/cards/UPC.js b/apps/cards/UPC.js index b7488b12c3..dd18f25d91 100644 --- a/apps/cards/UPC.js +++ b/apps/cards/UPC.js @@ -7,7 +7,7 @@ const Barcode = require("cards.Barcode.js"); class UPC extends Barcode{ constructor(data, options){ // Add checksum if it does not exist - if(data.search(/^[0-9]{11}$/) !== -1){ + if(/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){ data += checksum(data); } @@ -15,7 +15,7 @@ class UPC extends Barcode{ } valid(){ - return this.data.search(/^[0-9]{12}$/) !== -1 && + return /^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) && this.data[11] == checksum(this.data); } diff --git a/apps/cards/UPCE.js b/apps/cards/UPCE.js index bd082dd4d2..898abfad0e 100644 --- a/apps/cards/UPCE.js +++ b/apps/cards/UPCE.js @@ -41,14 +41,14 @@ class UPCE extends Barcode{ // is a UPC-A check or number system digit. super(data, options); this.isValid = false; - if(data.search(/^[0-9]{6}$/) !== -1){ + if(/^[0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){ this.middleDigits = data; this.upcA = expandToUPCA(data, "0"); this.text = options.text || `${this.upcA[0]}${data}${this.upcA[this.upcA.length - 1]}`; this.isValid = true; } - else if(data.search(/^[01][0-9]{7}$/) !== -1){ + else if(/^[01][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){ this.middleDigits = data.substring(1, data.length - 1); this.upcA = expandToUPCA(this.middleDigits, data[0]); From c2792f667a6b598176232c8de9399e356ea1a146 Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Tue, 9 Apr 2024 10:52:51 +0200 Subject: [PATCH 067/258] Also add padding for linear code, otherwise black watch border prevents scanning. --- apps/cards/app.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/cards/app.js b/apps/cards/app.js index 25af661966..2e001760fe 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -82,16 +82,17 @@ function printSquareCode(binary, size) { } } function printLinearCode(binary) { + var padding = 5; var yFrom = 15; var yTo = 28; - var width = g.getWidth()/binary.length; + var width = (g.getWidth()-(2*padding))/binary.length; for(var b = 0; b < binary.length; b++){ var x = b * width; if(binary[b] === "1"){ - g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); + g.setColor(BLACK).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } else if(binary[b]){ - g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); + g.setColor(WHITE).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } } } From dfa3417e682cfb43823ad112f4cdb17942bd28e3 Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Mon, 8 Apr 2024 22:41:53 +0200 Subject: [PATCH 068/258] update version --- apps/cards/ChangeLog | 1 + apps/cards/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/cards/ChangeLog b/apps/cards/ChangeLog index e7e13b8e73..b35947cdad 100644 --- a/apps/cards/ChangeLog +++ b/apps/cards/ChangeLog @@ -2,3 +2,4 @@ 0.02: Hiding widgets while showing the code 0.03: Added option to use max brightness when showing code 0.04: Minor code improvements +0.05: Add EAN & UPC codes diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index bc6bedce65..bdffeba71d 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -1,7 +1,7 @@ { "id": "cards", "name": "Cards", - "version": "0.04", + "version": "0.05", "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"}], From 5f18f47d8a1d30ad4dde2bd806c18800924e3e0a Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Thu, 20 Jun 2024 14:47:36 +0200 Subject: [PATCH 069/258] Add license --- apps/cards/EAN.js | 26 ++++++++++++++++++++++++++ apps/cards/EAN13.js | 25 +++++++++++++++++++++++++ apps/cards/EAN2.js | 25 +++++++++++++++++++++++++ apps/cards/EAN5.js | 25 +++++++++++++++++++++++++ apps/cards/EAN8.js | 25 +++++++++++++++++++++++++ apps/cards/UPC.js | 25 +++++++++++++++++++++++++ apps/cards/UPCE.js | 25 +++++++++++++++++++++++++ apps/cards/constants.js | 26 ++++++++++++++++++++++++++ apps/cards/encode.js | 26 ++++++++++++++++++++++++++ 9 files changed, 228 insertions(+) diff --git a/apps/cards/EAN.js b/apps/cards/EAN.js index d9d857c0e4..8ac1132922 100644 --- a/apps/cards/EAN.js +++ b/apps/cards/EAN.js @@ -1,3 +1,29 @@ +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + const constants = require("cards.constants.js"); const encode = require("cards.encode.js"); diff --git a/apps/cards/EAN13.js b/apps/cards/EAN13.js index f04417b6ae..9e36356b6e 100644 --- a/apps/cards/EAN13.js +++ b/apps/cards/EAN13.js @@ -1,5 +1,30 @@ // Encoding documentation: // https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Binary_encoding_of_data_digits_into_EAN-13_barcode +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ const constants = require("cards.constants.js"); diff --git a/apps/cards/EAN2.js b/apps/cards/EAN2.js index 840a98ce0f..323d6dc9a3 100644 --- a/apps/cards/EAN2.js +++ b/apps/cards/EAN2.js @@ -1,5 +1,30 @@ // Encoding documentation: // https://en.wikipedia.org/wiki/EAN_2#Encoding +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ const constants = require("cards.constants.js"); const encode = require("cards.encode.js"); diff --git a/apps/cards/EAN5.js b/apps/cards/EAN5.js index 241f422e15..68545d3607 100644 --- a/apps/cards/EAN5.js +++ b/apps/cards/EAN5.js @@ -1,5 +1,30 @@ // Encoding documentation: // https://en.wikipedia.org/wiki/EAN_5#Encoding +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ const constants = require("cards.constants.js"); const encode = require("cards.encode.js"); diff --git a/apps/cards/EAN8.js b/apps/cards/EAN8.js index abcf141fc4..382ee647a9 100644 --- a/apps/cards/EAN8.js +++ b/apps/cards/EAN8.js @@ -1,5 +1,30 @@ // Encoding documentation: // http://www.barcodeisland.com/ean8.phtml +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ const EAN = require("cards.EAN.js"); diff --git a/apps/cards/UPC.js b/apps/cards/UPC.js index dd18f25d91..6f581ca18d 100644 --- a/apps/cards/UPC.js +++ b/apps/cards/UPC.js @@ -1,5 +1,30 @@ // Encoding documentation: // https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ const encode = require("cards.encode.js"); const Barcode = require("cards.Barcode.js"); diff --git a/apps/cards/UPCE.js b/apps/cards/UPCE.js index 898abfad0e..9aaa464b73 100644 --- a/apps/cards/UPCE.js +++ b/apps/cards/UPCE.js @@ -3,6 +3,31 @@ // // UPC-E documentation: // https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ const encode = require("cards.encode.js"); const Barcode = require("cards.Barcode.js"); diff --git a/apps/cards/constants.js b/apps/cards/constants.js index 0f7244ce1c..86635691b6 100644 --- a/apps/cards/constants.js +++ b/apps/cards/constants.js @@ -1,3 +1,29 @@ +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + module.exports = { // Standard start end and middle bits SIDE_BIN : '101', diff --git a/apps/cards/encode.js b/apps/cards/encode.js index b484964c71..6390431f1f 100644 --- a/apps/cards/encode.js +++ b/apps/cards/encode.js @@ -1,3 +1,29 @@ +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + const constants = require("cards.constants.js"); // Encode data string From 39af2474d2694c9680089adf5a71ec0bdc0f4f25 Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Fri, 21 Jun 2024 09:34:28 +0200 Subject: [PATCH 070/258] Remove EAN 2 and 5 as they are not supported by the loyalty cards app --- apps/cards/EAN2.js | 55 ---------------------------------- apps/cards/EAN5.js | 65 ---------------------------------------- apps/cards/app.js | 18 ----------- apps/cards/constants.js | 9 ------ apps/cards/metadata.json | 2 -- 5 files changed, 149 deletions(-) delete mode 100644 apps/cards/EAN2.js delete mode 100644 apps/cards/EAN5.js diff --git a/apps/cards/EAN2.js b/apps/cards/EAN2.js deleted file mode 100644 index 323d6dc9a3..0000000000 --- a/apps/cards/EAN2.js +++ /dev/null @@ -1,55 +0,0 @@ -// Encoding documentation: -// https://en.wikipedia.org/wiki/EAN_2#Encoding -/* - * JS source adapted from https://github.com/lindell/JsBarcode - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Johan Lindell (johan@lindell.me) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -const constants = require("cards.constants.js"); -const encode = require("cards.encode.js"); -const Barcode = require("cards.Barcode.js"); - -class EAN2 extends Barcode { - - constructor(data, options) { - super(data, options); - } - - valid() { - return /^[0-9][0-9]$/.test(this.data); - } - - encode(){ - // Choose the structure based on the number mod 4 - const structure = constants.EAN2_STRUCTURE[parseInt(this.data) % 4]; - return { - // Start bits + Encode the two digits with 01 in between - data: '1011' + encode(this.data, structure, '01'), - text: this.text - }; - } - -} - -module.exports = EAN2; diff --git a/apps/cards/EAN5.js b/apps/cards/EAN5.js deleted file mode 100644 index 68545d3607..0000000000 --- a/apps/cards/EAN5.js +++ /dev/null @@ -1,65 +0,0 @@ -// Encoding documentation: -// https://en.wikipedia.org/wiki/EAN_5#Encoding -/* - * JS source adapted from https://github.com/lindell/JsBarcode - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Johan Lindell (johan@lindell.me) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -const constants = require("cards.constants.js"); -const encode = require("cards.encode.js"); -const Barcode = require("cards.Barcode.js"); - -const checksum = (data) => { - const result = data - .split('') - .map(n => +n) - .reduce((sum, a, idx) => { - return idx % 2 - ? sum + a * 9 - : sum + a * 3; - }, 0); - return result % 10; -}; - -class EAN5 extends Barcode { - - constructor(data, options) { - super(data, options); - } - - valid() { - return /^[0-9][0-9][0-9][0-9][0-9]$/.test(this.data); - } - - encode() { - const structure = constants.EAN5_STRUCTURE[checksum(this.data)]; - return { - data: '1011' + encode(this.data, structure, '01'), - text: this.text - }; - } - -} - -module.exports = EAN5; diff --git a/apps/cards/app.js b/apps/cards/app.js index 2e001760fe..45e72831cd 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -133,24 +133,6 @@ function showCode(card) { printLinearCode(code.encode().data); break; } - case "EAN_2": { - g.setFont("Vector:20"); - g.setFontAlign(0,1).setColor(BLACK); - g.drawString(card.value, g.getWidth()/2, g.getHeight()); - const EAN2 = require("cards.EAN2.js"); - let code = new EAN2(card.value, {}); - printLinearCode(code.encode().data); - break; - } - case "EAN_5": { - g.setFont("Vector:20"); - g.setFontAlign(0,1).setColor(BLACK); - g.drawString(card.value, g.getWidth()/2, g.getHeight()); - const EAN5 = require("cards.EAN5.js"); - let code = new EAN5(card.value, {}); - printLinearCode(code.encode().data); - break; - } case "EAN_8": { g.setFont("Vector:20"); g.setFontAlign(0,1).setColor(BLACK); diff --git a/apps/cards/constants.js b/apps/cards/constants.js index 86635691b6..ead651fddf 100644 --- a/apps/cards/constants.js +++ b/apps/cards/constants.js @@ -52,15 +52,6 @@ module.exports = { ] }, - // Define the EAN-2 structure - EAN2_STRUCTURE : ['LL', 'LG', 'GL', 'GG'], - - // Define the EAN-5 structure - EAN5_STRUCTURE : [ - 'GGLLL', 'GLGLL', 'GLLGL', 'GLLLG', 'LGGLL', - 'LLGGL', 'LLLGG', 'LGLGL', 'LGLLG', 'LLGLG' - ], - // Define the EAN-13 structure EAN13_STRUCTURE : [ 'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index bdffeba71d..d17ff4a514 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -18,8 +18,6 @@ {"name":"cards.code39.js","url":"code39.js"}, {"name":"cards.constants.js","url":"constants.js"}, {"name":"cards.EAN.js","url":"EAN.js"}, - {"name":"cards.EAN2.js","url":"EAN2.js"}, - {"name":"cards.EAN5.js","url":"EAN5.js"}, {"name":"cards.EAN8.js","url":"EAN8.js"}, {"name":"cards.EAN13.js","url":"EAN13.js"}, {"name":"cards.UPC.js","url":"UPC.js"}, From 333fd0562ae8f4dc1a8641cee8e3f27f9abf8d48 Mon Sep 17 00:00:00 2001 From: Eric de Boer Date: Fri, 21 Jun 2024 11:06:05 +0200 Subject: [PATCH 071/258] Move constants to files they are used. --- apps/cards/EAN.js | 12 ++++---- apps/cards/EAN13.js | 8 ++++-- apps/cards/constants.js | 60 ---------------------------------------- apps/cards/encode.js | 25 +++++++++++++++-- apps/cards/metadata.json | 1 - 5 files changed, 35 insertions(+), 71 deletions(-) delete mode 100644 apps/cards/constants.js diff --git a/apps/cards/EAN.js b/apps/cards/EAN.js index 8ac1132922..177874494d 100644 --- a/apps/cards/EAN.js +++ b/apps/cards/EAN.js @@ -24,11 +24,13 @@ * IN THE SOFTWARE. */ -const constants = require("cards.constants.js"); - const encode = require("cards.encode.js"); const Barcode = require("cards.Barcode.js"); +// Standard start end and middle bits +const SIDE_BIN = '101'; +const MIDDLE_BIN = '01010'; + // Base class for EAN8 & EAN13 class EAN extends Barcode { constructor(data, options) { @@ -53,11 +55,11 @@ class EAN extends Barcode { encode() { const data = [ - constants.SIDE_BIN, + SIDE_BIN, this.leftEncode(), - constants.MIDDLE_BIN, + MIDDLE_BIN, this.rightEncode(), - constants.SIDE_BIN + SIDE_BIN ]; return { diff --git a/apps/cards/EAN13.js b/apps/cards/EAN13.js index 9e36356b6e..c91e385e3e 100644 --- a/apps/cards/EAN13.js +++ b/apps/cards/EAN13.js @@ -26,10 +26,12 @@ * IN THE SOFTWARE. */ -const constants = require("cards.constants.js"); - const EAN = require("cards.EAN.js"); +const EAN13_STRUCTURE = [ + 'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', + 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL' +]; // Calculate the checksum digit // https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit @@ -72,7 +74,7 @@ class EAN13 extends EAN { leftEncode() { const data = this.data.substr(1, 6); - const structure = constants.EAN13_STRUCTURE[this.data[0]]; + const structure = EAN13_STRUCTURE[this.data[0]]; return super.leftEncode(data, structure); } diff --git a/apps/cards/constants.js b/apps/cards/constants.js deleted file mode 100644 index ead651fddf..0000000000 --- a/apps/cards/constants.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * JS source adapted from https://github.com/lindell/JsBarcode - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Johan Lindell (johan@lindell.me) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -module.exports = { - // Standard start end and middle bits - SIDE_BIN : '101', - MIDDLE_BIN : '01010', - - BINARIES : { - 'L': [ // The L (left) type of encoding - '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' - ], - 'G': [ // The G type of encoding - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ], - 'R': [ // The R (right) type of encoding - '1110010', '1100110', '1101100', '1000010', '1011100', - '1001110', '1010000', '1000100', '1001000', '1110100' - ], - 'O': [ // The O (odd) encoding for UPC-E - '0001101', '0011001', '0010011', '0111101', '0100011', - '0110001', '0101111', '0111011', '0110111', '0001011' - ], - 'E': [ // The E (even) encoding for UPC-E - '0100111', '0110011', '0011011', '0100001', '0011101', - '0111001', '0000101', '0010001', '0001001', '0010111' - ] - }, - - // Define the EAN-13 structure - EAN13_STRUCTURE : [ - 'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', - 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL' - ] -} \ No newline at end of file diff --git a/apps/cards/encode.js b/apps/cards/encode.js index 6390431f1f..7cb1fcc870 100644 --- a/apps/cards/encode.js +++ b/apps/cards/encode.js @@ -24,13 +24,34 @@ * IN THE SOFTWARE. */ -const constants = require("cards.constants.js"); +const BINARIES = { + 'L': [ // The L (left) type of encoding + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'G': [ // The G type of encoding + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ], + 'R': [ // The R (right) type of encoding + '1110010', '1100110', '1101100', '1000010', '1011100', + '1001110', '1010000', '1000100', '1001000', '1110100' + ], + 'O': [ // The O (odd) encoding for UPC-E + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'E': [ // The E (even) encoding for UPC-E + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ] +}; // Encode data string const encode = (data, structure, separator) => { let encoded = data .split('') - .map((val, idx) => constants.BINARIES[structure[idx]]) + .map((val, idx) => BINARIES[structure[idx]]) .map((val, idx) => val ? val[data[idx]] : ''); if (separator) { diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index d17ff4a514..c8d19a3753 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -16,7 +16,6 @@ {"name":"cards.qrcode.js","url":"qrcode.js"}, {"name":"cards.codabar.js","url":"codabar.js"}, {"name":"cards.code39.js","url":"code39.js"}, - {"name":"cards.constants.js","url":"constants.js"}, {"name":"cards.EAN.js","url":"EAN.js"}, {"name":"cards.EAN8.js","url":"EAN8.js"}, {"name":"cards.EAN13.js","url":"EAN13.js"}, From ffb9bb20c58eaed25d5ce1cb66331cf1dde9191d Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 07:20:16 -0400 Subject: [PATCH 072/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index f593c877a2..1d1ce90c3b 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -199,7 +199,7 @@ function showPlayData() { // distance to hole in yards g.setColor(g.theme.fg).setFontAlign(1, -1).setFontDroidSansMono52(); - g.drawString(distanceToHole > 1000 ? ">1k" : distanceToHole.toFixed(0), W - 2, 1); + g.drawString(distanceToHole > 1000 ? "000" : distanceToHole.toFixed(0), W - 2, 1); // distance from last shot in yards g.setFontDroidSansMono35().setColor('#00f').drawString(distanceFromLast.toFixed(0), W - 2, 70); @@ -298,4 +298,4 @@ Bangle.setLocked(false); // keep backlight off Bangle.setLCDBrightness(0); -mainMenu(); \ No newline at end of file +mainMenu(); From e2a0aa07ba7c240e24763928be1c4acebcb97349 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:40:53 -0400 Subject: [PATCH 073/258] Update README.md --- apps/golf-gps/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md index 507be62f5e..d07431768b 100644 --- a/apps/golf-gps/README.md +++ b/apps/golf-gps/README.md @@ -16,7 +16,7 @@ Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfi - Clock - How to change holes and add/subtract shots - Swipe left/right to change the hole to next/previous (you can move to any hole to update your shots in case you entered wrong number of shots by mistake) - - Swip up/down to add/substract the number of shots. This will update the total number of shots as well as current shots. + - Swip up/down to add/subtract the number of shots. This will update the total number of shots as well as current shots. - After the game - Press the button to either finish the game or go back to the play screen (if you pressed the button by accident). - If you choose to finish the game, it will show the summary of the score with 3x6 matix, shots - par. For example, -1 is for birdie and 0 is for par, and +2 is for double bogey. It also shows the total shots and total par as well. From 3114c8c9c9f26b0c40a46c25cdc7be1ce9441834 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:45:05 -0400 Subject: [PATCH 074/258] Add files via upload --- apps/golf-gps/Play screen.png | Bin 0 -> 48173 bytes apps/golf-gps/par3.png | Bin 0 -> 3189 bytes apps/golf-gps/par4.png | Bin 0 -> 3313 bytes apps/golf-gps/par5.png | Bin 0 -> 3713 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/golf-gps/Play screen.png create mode 100644 apps/golf-gps/par3.png create mode 100644 apps/golf-gps/par4.png create mode 100644 apps/golf-gps/par5.png diff --git a/apps/golf-gps/Play screen.png b/apps/golf-gps/Play screen.png new file mode 100644 index 0000000000000000000000000000000000000000..0b157f74559011f6517177b9345329593d576ee4 GIT binary patch literal 48173 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 literal 0 HcmV?d00001 diff --git a/apps/golf-gps/par5.png b/apps/golf-gps/par5.png new file mode 100644 index 0000000000000000000000000000000000000000..aa32b5e8bdb3690c649f5a1a70599758b2518f27 GIT binary patch literal 3713 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 literal 0 HcmV?d00001 From 98091e7335707a3713860176c3c0a11d402e42ce Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:48:37 -0400 Subject: [PATCH 075/258] Update README.md --- apps/golf-gps/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md index d07431768b..deae0b1749 100644 --- a/apps/golf-gps/README.md +++ b/apps/golf-gps/README.md @@ -14,6 +14,7 @@ Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfi - Number of shots on current hole - Total number of shots - Clock + ![](playScreen.png) - How to change holes and add/subtract shots - Swipe left/right to change the hole to next/previous (you can move to any hole to update your shots in case you entered wrong number of shots by mistake) - Swip up/down to add/subtract the number of shots. This will update the total number of shots as well as current shots. @@ -23,8 +24,9 @@ Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfi ## Screenshots -![](jclock_screenshot_no_BT.png) -![](jclock_screenshot_BT.png) +![](par3.png) +![](par4.png) +![](par5.png) ## Creator From b01c0bc1d61a0e23262bfa3316d96ddeb9e8503b Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:49:10 -0400 Subject: [PATCH 076/258] Delete apps/golf-gps/Play screen.png --- apps/golf-gps/Play screen.png | Bin 48173 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/golf-gps/Play screen.png diff --git a/apps/golf-gps/Play screen.png b/apps/golf-gps/Play screen.png deleted file mode 100644 index 0b157f74559011f6517177b9345329593d576ee4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48173 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 Date: Fri, 21 Jun 2024 12:49:24 -0400 Subject: [PATCH 077/258] Add files via upload --- apps/golf-gps/playScreen.png | Bin 0 -> 48173 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/golf-gps/playScreen.png diff --git a/apps/golf-gps/playScreen.png b/apps/golf-gps/playScreen.png new file mode 100644 index 0000000000000000000000000000000000000000..0b157f74559011f6517177b9345329593d576ee4 GIT binary patch literal 48173 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 Date: Fri, 21 Jun 2024 13:13:15 -0400 Subject: [PATCH 078/258] Update README.md --- apps/golf-gps/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md index deae0b1749..9781249317 100644 --- a/apps/golf-gps/README.md +++ b/apps/golf-gps/README.md @@ -14,7 +14,8 @@ Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfi - Number of shots on current hole - Total number of shots - Clock - ![](playScreen.png) + +![](playScreen.png) - How to change holes and add/subtract shots - Swipe left/right to change the hole to next/previous (you can move to any hole to update your shots in case you entered wrong number of shots by mistake) - Swip up/down to add/subtract the number of shots. This will update the total number of shots as well as current shots. @@ -24,9 +25,13 @@ Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfi ## Screenshots +![](startUp.png) +![](fixGPS.png) ![](par3.png) ![](par4.png) ![](par5.png) +![](finishGame.png) +![](scoreView.png) ## Creator From 21500719100c71e490b71aa10140cdedbac0a6aa Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:13:39 -0400 Subject: [PATCH 079/258] Add files via upload --- apps/golf-gps/finishGame.png | Bin 0 -> 1389 bytes apps/golf-gps/fixGPS.png | Bin 0 -> 2741 bytes apps/golf-gps/scoreView.png | Bin 0 -> 2996 bytes apps/golf-gps/startUp.png | Bin 0 -> 2547 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/golf-gps/finishGame.png create mode 100644 apps/golf-gps/fixGPS.png create mode 100644 apps/golf-gps/scoreView.png create mode 100644 apps/golf-gps/startUp.png diff --git a/apps/golf-gps/finishGame.png b/apps/golf-gps/finishGame.png new file mode 100644 index 0000000000000000000000000000000000000000..5f05f4b740041a20d0fc0ee319e82c1de1c85866 GIT binary patch literal 1389 zcmeAS@N?(olHy`uVBq!ia0vp^8$g(Y4M?uv{v-}aF%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}2EbxddW?&F10b#~_Y4Q~e46IC^E{-7;ac}QjEP7 z1*geK*G&39J8{d($0`~8v-HHP_tw7uV%Y6eSuf6H1_-`qR@#?9CFUc9c{Jiq+?#_Lh$H$Sfe28Wr>f!pe@pL@63 zzBf~p+Z$q2_3f?csm0m(>&2||!}NeEzHQ4_^vVAJ=BwEL`fq=GV%0DGwyEw9Kg`m$ zC3PRW**~kbuial?eeLdMdtT$}_v|+{`}ddTY`Xq^(d*trKs9%TC5l~y`}eQA4>ao- zOPdMMMWh7y<$vjNHLJJ$==pm`_+TvKy;*yW|1Y2L_HyL!ozH8_|8pgpZ>@CfeVsl( z?{C$!Gk?!~QNLu`_`N3SoX#uhzke=WZvVuxZMMyCmq7XJ=l9+H^WP~d| zN}YUXU$r(WjVb^7U6j9 ze!e-#%*|Irt}9Uwap0T3QuX}z`@`y^UM~*$t!Ur4_;ao4 z&ClsqJ<960Rb8wqxnorywl?ZrbjO>N4fCtlroX;x-t~UIz1-37YpdRDz2<)NUGUE1 zMfaU%ytwiI&gbX7x6D_8ZTlkkJ8RL`z^z#~-#5nF3f?nKO#5nFW&f=>`SsfG_oL*O z-s(@?&u+H&_0_CR&%a*-SvTv&^JS%VWwjw|U-50IbxRAqy!+qtKeyF?f4*F6|7UZZ z_}cze6W{#HF|EIrfBEh6mh5@;>(!#9dH z%eb$7@A9$l`C;~(|3>e%*Zm~CLv^?QuJbRzYJ+3@U9L;UuAVmcfcw+m?)LT8XISjA zum^Y&(|!rzAC_t#9+^(MTmL00blw5!Wp49V?JvJe?L*n*w`sGlsdrVa_y1S) z;`!RLx{lpy8(!{xc{AmA$+fz7mG^fu?YWuP_gqWYF4_LOOF?-zopr0Po|9{r~^~ literal 0 HcmV?d00001 diff --git a/apps/golf-gps/fixGPS.png b/apps/golf-gps/fixGPS.png new file mode 100644 index 0000000000000000000000000000000000000000..201a6c3b5c85983ed70367dd730bc778e6fec6d1 GIT binary patch literal 2741 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 DJdZ&Q literal 0 HcmV?d00001 diff --git a/apps/golf-gps/startUp.png b/apps/golf-gps/startUp.png new file mode 100644 index 0000000000000000000000000000000000000000..18a4da846dfb454ac1a38506efd0715db910ce9b GIT binary patch literal 2547 zcmdUx=~oi?8pnT#igJ@obICAsIyYO!oFQW8n+O4(J8gua|sd`=sMG_44WyA zWiBO}1_BnEx0H4gk>e@^lYS*Sfz#M_ZlQjeka}3nV=N?FLvq$XNh%nO^R$7((QH=}6j6`z`J? zmFj#I^0LAG!-CzU9jR|QHep8Nr1t3BW)T}J@@!Pzq6K^uLxurieOkT}4FM<@!g21N zXs5fw7dyTkTqKzW020Jb(@!p12iepCbICqfgJKE$u{ecY?x0({M4aTEM}kJTu$T3U zCF6d$DLBYj#O}UOVzT`ppaCNJN!Q_GwpjR#u{mgGHG7N)C2GM$CO?7j7J&x@tEA1( z-{8(cPrsRVIy`LGy0gXfs;a+y(|#dP%Umnn_E5qn^mL$-I1dt;o^?2xPGYqavOVm5 z2n72eM%YQseVG=!l_xa+82Wy^j|HzBM!Q-t@8a7OgF9L$f|o41C6$}Y(BLshAf#eG z2#V|ux@nQ*tm`hT2XodBhIORZv$<7CPOcu4e#*e+W9-}Q-CI*7gx2Pit?mh`aR=|BD>X;_eM|N5>}iUQN0yAyBh1NW`$2`>*#dyDn`v$bcjTLp8k&5Gq}pi8GhB7)(nMxmJW9Z-#7xpC^mc}vO~EmX1PFd7Uc_MO!)N?mK4`y< zOtIz8vh)v-{au-O2id@tIRbj=40ycC`nNi_3Cdtxks}hc>RUsKaNk#A_9of$bBnq<@C+Dsr#{K3 ziLDp_&#d!u*!~^OC^sz9ACQ~{U&aOK*N;-T8Y7Sr8_Ofu1AtEzb1nr#0OHhJO)4Cu zR;|^VV*#nn;Ct694G{hqm2lMvi0blB`#lCAh~7IKtp)79wKT;1M`#y>l51jJ3k*j? zA%L5?s)O26m*yw%(H+Ey=(1fJ3&k6E;u?KGlbj)#EZ5Accvcl@tbqQ~krpAPSzqzSXrh#g*B;s)r4~!B# z_jfVE=o(wE)a}`_c>!v~i!T%(DK5Wmqj(Da=18<9<%KDO(mdVs=s&>Mz!Q9qNPKKN#If~3;#ih&y~?r9 zW{bx|k}m`(X4`ALwgZtD+|?AN1wK~qr0rF&+G@XM^vZS+Ri0c>><$5CmHPRg&Hz$Q z)8TjnEkO9qP5+nw4s53vC6Azceeu<6TYEE5c(Tt5y>nMQ*w@Rm<&RI8@kbm_5&La@ z3Q*nGN;0}n!&b}+@8?kmW4@1w+nmeH56P;8wv3fF;yXIXLZ0?3viuNxjtu1mS-{Ga zv)YIPXpqDCj3lHfjJuqE4oUbChD~`GtIVExq7w8HJw}MA$;_yf<4zGwUe+Iw)jCidW4BMn3#yAVk#1**td+Yq`xaKBc2~ zf{#Xe+Xzs_s+nxuM2UeY5sg7C^gwWJp^U?uWkAo#M z%d?cT`I@xan%i)egCt~>^=)cTt8}AQeWq$qksTZ5qVjUHeG!13?tpmrgEpxvVVM_Zxw4!$GH15u?m47)LF1xSKZKW8RzXCYL_NR-aK~n)^@K zKRsj7=#R0llWSOHZkgIgo5wzE?Ip*hi+`*t6N>$956O7DjeJ`}EMyh;D1TUrIg}yZA>IGYwPxFW! zawX@DlnCQCb3!LO$QMP;FQh|+i&4nCDd%c8-g$7GeqQ;Q60%(E=qPhEe?J}g$7%Ea zGPnAz3scpsT6|}-e6q3c(2MoWDQgKwUnb4N<1q;v*tjPO4vScn8~10al#eT2IrJ*_ z%>^Z?|6)NblUQ`w^l2SeG|VJ=)0(`MXBldn5G4uw`gkW ztBtoueE3;71RSz~K-|@~{AX5ybr2xEJLLlYAJ7%O=;!{O_w9Ko^%n=cPWrgB+@h}h E3#~7?qyPW_ literal 0 HcmV?d00001 From 90fc6ac194e3ea879a3598f87a9b02c263e4feba Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:14:58 -0400 Subject: [PATCH 080/258] Update README.md --- apps/golf-gps/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md index 9781249317..1c056a1994 100644 --- a/apps/golf-gps/README.md +++ b/apps/golf-gps/README.md @@ -27,9 +27,11 @@ Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfi ## Screenshots ![](startUp.png) ![](fixGPS.png) + ![](par3.png) ![](par4.png) ![](par5.png) + ![](finishGame.png) ![](scoreView.png) From af50b2fdcd628d659cd385c96aff7f22328526d9 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:28:01 -0400 Subject: [PATCH 081/258] Add files via upload --- apps/golf-gps/storage-write.js | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 apps/golf-gps/storage-write.js diff --git a/apps/golf-gps/storage-write.js b/apps/golf-gps/storage-write.js new file mode 100644 index 0000000000..f90b6f87f2 --- /dev/null +++ b/apps/golf-gps/storage-write.js @@ -0,0 +1,44 @@ +f = require("Storage").open("course-data","a"); +f.write( +"course name\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"course name\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n"+ +"00.000000,00.000000,0\n" +); + +// SHFT-ALT drag all line, hit HOME and type " and hit END and type \n"+ +// run in RAM mode \ No newline at end of file From 9b029afd6a5f88966dfffd12a10e49e345db21fa Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:38:59 -0400 Subject: [PATCH 082/258] Update storage-write.js --- apps/golf-gps/storage-write.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/golf-gps/storage-write.js b/apps/golf-gps/storage-write.js index f90b6f87f2..fa1dfb2933 100644 --- a/apps/golf-gps/storage-write.js +++ b/apps/golf-gps/storage-write.js @@ -1,4 +1,15 @@ -f = require("Storage").open("course-data","a"); +/* + A script to create, overwrite or append golf course GPS data + Format: latitude (6 decimal places), longitude (6 decimal places), par + There are two course data in below code, but you can put any number of course data. + + Save this file to your computer and open it from Espruino Web IDE (https://www.espruino.com/ide/) + and run in RAM mode + + Tip: In the Web IDE editor, enter GPS cordinates and par per hole, and then SHFT-ALT drag all line, hit HOME and type " and hit END and type \n"+ +*/ + +f = require("Storage").open("course-data","a"); // "r" to create or overwrite, "a" to append f.write( "course name\n"+ "00.000000,00.000000,0\n"+ @@ -39,6 +50,3 @@ f.write( "00.000000,00.000000,0\n"+ "00.000000,00.000000,0\n" ); - -// SHFT-ALT drag all line, hit HOME and type " and hit END and type \n"+ -// run in RAM mode \ No newline at end of file From 6ee5b7305231c03ea0ebe2e58965a59a43233593 Mon Sep 17 00:00:00 2001 From: Brian Whelan Date: Fri, 21 Jun 2024 18:44:44 +0100 Subject: [PATCH 083/258] Add new module "reply" for canned responses Adds a new module that enables replying to messages --- apps/reply/ChangeLog | 1 + apps/reply/README.md | 23 +++++++ apps/reply/app.png | Bin 0 -> 720 bytes apps/reply/interface.html | 125 ++++++++++++++++++++++++++++++++++++++ apps/reply/lib.js | 75 +++++++++++++++++++++++ apps/reply/metadata.json | 16 +++++ 6 files changed, 240 insertions(+) create mode 100644 apps/reply/ChangeLog create mode 100644 apps/reply/README.md create mode 100644 apps/reply/app.png create mode 100644 apps/reply/interface.html create mode 100644 apps/reply/lib.js create mode 100644 apps/reply/metadata.json diff --git a/apps/reply/ChangeLog b/apps/reply/ChangeLog new file mode 100644 index 0000000000..f3c7b0d2c3 --- /dev/null +++ b/apps/reply/ChangeLog @@ -0,0 +1 @@ +0.01: New Library! \ No newline at end of file diff --git a/apps/reply/README.md b/apps/reply/README.md new file mode 100644 index 0000000000..dc874d183b --- /dev/null +++ b/apps/reply/README.md @@ -0,0 +1,23 @@ +# Canned Replies Library + +A library that handles replying to messages received from Gadgetbridge/Messages apps. + +## Replying to a message +The user can define a set of canned responses via the customise page after installing the app, or alternatively if they have a keyboard installed, they can type a response back. The requesting app will receive either an object containing the full reply for GadgetBridge, or a string with the response from the user, depending on how they wish to handle the response. + +## Integrating in your app +To use this in your app, simply call + +```js +require("reply").reply(/*options*/{...}).then(result => ...); +``` + +The ```options``` object can contain the following: + +- ```msg```: A message object containing a field ```id```, the ID to respond to. If this is included in options, the result of the promise will be an object as follows: ```{t: "notify", id: msg.id, n: "REPLY", msg: "USER REPLY"}```. If not included, the result of the promise will be an object, ```{msg: "USER REPLY"}``` +- ```shouldReply```: Whether or not the library should send the response over Bluetooth with ```Bluetooth.println(...```. Useful if the calling app wants to handle the response a different way. Default is true. +- ```title```: The title to show at the top of the menu. Defaults to ```"Reply with:"```. +- ```fileOverride```: An override file to read canned responses from, which is an array of objects each with a ```text``` property. Default is ```replies.json```. Useful for apps which might want to make use of custom canned responses. + +## Known Issues +Emojis are currently not supported. \ No newline at end of file diff --git a/apps/reply/app.png b/apps/reply/app.png new file mode 100644 index 0000000000000000000000000000000000000000..bef8338cfda0a069115cd86d0978301ec2a01156 GIT binary patch literal 720 zcmV;>0x$iEP)Ou!sS{ zQUwVs+6*>cr3f}!iG`qtjTT9xjfjF0NUNYAVxvJ5BpO0o8JLA-oY|Y*o6XLJ{oz5l zoq6y7ax-&p7HZY1RjXF5|1}J>fkhy$I2M5?zu22oJagSVp0yKr-~&L$58xuOv3%cb049KM7B3x@wTWH;2g0d51^JR08u2w>Tver|y4z<0yvUx0BB=0|{!mSua>2S5`z4Lk+@S~mIa z0>*b)HmR)f0yv{;djUw|67az=<`)nFn;i1XgWx={V94_cn9^!`1&}cY+yj0a#?Asq zGIAdTUK{2u0}p`{hI-xsk~nKQF;;=QKz}OdR-mO00Db&h;9N$X(g2cZS-RKH>f0Jq zf9ucXoR&lW(g9?gR9)<g|rFG4d z@dfptx0>$+kv=jHsU>H6>2!kt6lDPDul-(X?+5Je6EQdcy9 z=LDq40idA)d?hf64$rr10P^bCbBZ1JOt6Zp1o#KcD)dHzb{Sp(0000 + + + + + + + +
+ + + +
+
+
+ +
+
+
+
+
+
+

Loading

+

Syncing custom replies with your watch

+
+
+
+
+
+ +
+

No custom replies

+

Use the field above to add a custom reply

+
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/apps/reply/lib.js b/apps/reply/lib.js new file mode 100644 index 0000000000..7bd4780a55 --- /dev/null +++ b/apps/reply/lib.js @@ -0,0 +1,75 @@ +exports.reply = function (options) { + var keyboard = "textinput"; + try { + keyboard = require(keyboard); + } catch (e) { + keyboard = null; + } + + function constructReply(msg, replyText, resolve) { + var responseMessage = {msg: replyText}; + if (msg.id) { + responseMessage = { t: "notify", id: msg.id, n: "REPLY", msg: replyText }; + } + E.showMenu(); + layout.setUI(); + layout.render(); + if (options.sendReply == null || options.sendReply) { + Bluetooth.println(JSON.stringify(result)); + } + resolve(responseMessage); + } + + return new Promise((resolve, reject) => { + var menu = { + "": { + title: options.title || /*LANG*/ "Reply with:", + back: function () { + E.showMenu(); + layout.setUI(); + layout.render(); + reject("User pressed back"); + }, + }, // options + /*LANG*/ "Compose": function () { + keyboard.input().then((result) => { + constructReply(options.msg ?? {}, result, resolve); + }); + }, + }; + var replies = + require("Storage").readJSON( + options.fileOverride || "replies.json", + true + ) || {}; + replies.forEach((reply) => { + menu = Object.defineProperty(menu, reply.text, { + value: () => constructReply(options.msg ?? {}, reply.text, resolve), + }); + }); + if (!keyboard) delete menu[/*LANG*/ "Compose"]; + + if (replies.length == 0) { + if (!keyboard) { + E.showPrompt( + /*LANG*/ "Please install a keyboard app, or set a custom reply via the app loader!", + { + buttons: { Ok: true }, + remove: function () { + layout.setUI(); + layout.render(); + reject( + "Please install a keyboard app, or set a custom reply via the app loader!" + ); + }, + } + ); + } else { + keyboard.input().then((result) => { + constructReply(options.msg.id, result, resolve); + }); + } + } + E.showMenu(menu); + }); +}; diff --git a/apps/reply/metadata.json b/apps/reply/metadata.json new file mode 100644 index 0000000000..34843edd44 --- /dev/null +++ b/apps/reply/metadata.json @@ -0,0 +1,16 @@ +{ "id": "reply", + "name": "Reply Library", + "version": "0.01", + "description": "A library for replying to text messages via predefined responses or keyboard", + "icon": "app.png", + "type": "module", + "provides_modules" : ["reply"], + "tags": "", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"reply","url":"lib.js"} + ], + "data": [{"name":"replies.json"}] +} \ No newline at end of file From 468509f1040e285f2d5b48af617fa7a73cfd329b Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:09:42 -0400 Subject: [PATCH 084/258] Update README.md --- apps/golf-gps/README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md index 1c056a1994..9aa0cd19fe 100644 --- a/apps/golf-gps/README.md +++ b/apps/golf-gps/README.md @@ -1,5 +1,4 @@ # Golf GPS - I have made a few watches for golfing. See this [LINK](https://jeonlab.wordpress.com/category/golf-gps-watch/) if you are interested. Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfing. For my previous watches I have used TFT LCD or OLED displays and they all draw a lot of current and display information only when I press a button to wake up from the black screen. One of the best feature of the Bangle.js 2, I think, is the memory LCD which consumes very small power and it is always on! @@ -23,6 +22,28 @@ Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfi - Press the button to either finish the game or go back to the play screen (if you pressed the button by accident). - If you choose to finish the game, it will show the summary of the score with 3x6 matix, shots - par. For example, -1 is for birdie and 0 is for par, and +2 is for double bogey. It also shows the total shots and total par as well. +## Course data +Before you run Golf GPS, you need to save your favourite golf course data into the storage area. +Download `storage-write.js` and open it from Espruino Web IDE (https://www.espruino.com/ide/) and enter your course data and run in RAM mode. +With this script, you can create, overwrite or append golf course GPS data. +``` +f = require("Storage").open("course-data","a"); // "w" to create or overwrite, "a" to append +``` +There must be other ways to get the GPS coordinates for your course, but I use Google Maps. In satellite mode, click on the middle of each green will show you the coordinate, latitude and longitude. copy and paste it into the script for the holes 1 through 18 and add par per each hole. You will need 6 decimal places for the coordinate. Here is a format of the course data to put in the script. + +``` +"course name\n"+ +"00.000000,00.000000,0\n"+ +. +. +"00.000000,00.000000,0\n"+ +"next course name\n"+ +. +. +``` +You can put any number of course data. Once the data file is created in the storage area, you can add more data to the same file using append ("a" ) mode with the same script. Just replace the data and change the mode to "a" and run in RAM mode. + +One tip to wrap each line with `"----\n"+` is using the built-in edit feature of the Web IDE. In the Web IDE editor, enter GPS cordinates and par per hole, and then SHFT-ALT drag all lines including the course name, and hit HOME and type `"` and hit END and type `\n"+`. ## Screenshots ![](startUp.png) From 147f655c5d37a355e90a4df331354b1a9131844e Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:11:56 -0400 Subject: [PATCH 085/258] Update README.md --- apps/golf-gps/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/golf-gps/README.md b/apps/golf-gps/README.md index 9aa0cd19fe..b67a2255d1 100644 --- a/apps/golf-gps/README.md +++ b/apps/golf-gps/README.md @@ -17,7 +17,7 @@ Now that I have a Bangle.js 2 watch, I wanted to port my program to it for golfi ![](playScreen.png) - How to change holes and add/subtract shots - Swipe left/right to change the hole to next/previous (you can move to any hole to update your shots in case you entered wrong number of shots by mistake) - - Swip up/down to add/subtract the number of shots. This will update the total number of shots as well as current shots. + - Swipe up/down to add/subtract the number of shots. This will update the total number of shots as well as current shots. - After the game - Press the button to either finish the game or go back to the play screen (if you pressed the button by accident). - If you choose to finish the game, it will show the summary of the score with 3x6 matix, shots - par. For example, -1 is for birdie and 0 is for par, and +2 is for double bogey. It also shows the total shots and total par as well. From e3160a1106ca38816ba5de952664238782186a28 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:20:58 -0400 Subject: [PATCH 086/258] Update storage-write.js --- apps/golf-gps/storage-write.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/golf-gps/storage-write.js b/apps/golf-gps/storage-write.js index fa1dfb2933..fbd800c215 100644 --- a/apps/golf-gps/storage-write.js +++ b/apps/golf-gps/storage-write.js @@ -1,15 +1,18 @@ /* - A script to create, overwrite or append golf course GPS data - Format: latitude (6 decimal places), longitude (6 decimal places), par - There are two course data in below code, but you can put any number of course data. - - Save this file to your computer and open it from Espruino Web IDE (https://www.espruino.com/ide/) - and run in RAM mode - - Tip: In the Web IDE editor, enter GPS cordinates and par per hole, and then SHFT-ALT drag all line, hit HOME and type " and hit END and type \n"+ -*/ + Before you run Golf GPS, you need to save your favourite golf course data into the storage area. + Download this script and open it from Espruino Web IDE (https://www.espruino.com/ide/) and enter your course data and run in RAM mode. + With this script, you can create, overwrite or append golf course GPS data. -f = require("Storage").open("course-data","a"); // "r" to create or overwrite, "a" to append + You can put any number of course data. + Once the data file is created in the storage area, you can add more data to the same file using append ("a" ) mode with the same script. + Just replace the data and change the mode to "a" and run in RAM mode. + + One tip to wrap each line with "----\n"+ is using the built-in edit feature of the Web IDE. + In the Web IDE editor, enter GPS cordinates and par per hole, and then SHFT-ALT drag all lines including the course name, + and hit HOME and type " and hit END and type \n"+. +*/ + +f = require("Storage").open("course-data","w"); // "w" to create or overwrite, "a" to append f.write( "course name\n"+ "00.000000,00.000000,0\n"+ From 936b6aabee9352ca552c2abf618248d33bed55c7 Mon Sep 17 00:00:00 2001 From: Brian Whelan Date: Fri, 21 Jun 2024 19:29:44 +0100 Subject: [PATCH 087/258] Fix bluetooth stringify param --- apps/reply/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/reply/lib.js b/apps/reply/lib.js index 7bd4780a55..0e2fd86f74 100644 --- a/apps/reply/lib.js +++ b/apps/reply/lib.js @@ -15,7 +15,7 @@ exports.reply = function (options) { layout.setUI(); layout.render(); if (options.sendReply == null || options.sendReply) { - Bluetooth.println(JSON.stringify(result)); + Bluetooth.println(JSON.stringify(responseMessage)); } resolve(responseMessage); } From 231fae855ff4d1f553f62e568c2bf81d377292bb Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 22 Jun 2024 14:39:04 +0200 Subject: [PATCH 088/258] feat: initial commit of Measure Time clock face --- apps/measure_time/ChangeLog | 1 + apps/measure_time/README.md | 7 + apps/measure_time/measuretime-icon.js | 1 + apps/measure_time/measuretime.js | 187 ++++++++++++++++++++++++ apps/measure_time/measuretime.png | Bin 0 -> 5005 bytes apps/measure_time/metadata.json | 21 +++ apps/measure_time/small_measuretime.png | Bin 0 -> 5456 bytes apps/measure_time/test.json | 15 ++ 8 files changed, 232 insertions(+) create mode 100644 apps/measure_time/ChangeLog create mode 100644 apps/measure_time/README.md create mode 100644 apps/measure_time/measuretime-icon.js create mode 100644 apps/measure_time/measuretime.js create mode 100644 apps/measure_time/measuretime.png create mode 100644 apps/measure_time/metadata.json create mode 100644 apps/measure_time/small_measuretime.png create mode 100644 apps/measure_time/test.json diff --git a/apps/measure_time/ChangeLog b/apps/measure_time/ChangeLog new file mode 100644 index 0000000000..81fba8e158 --- /dev/null +++ b/apps/measure_time/ChangeLog @@ -0,0 +1 @@ +0.1: Initial release \ No newline at end of file diff --git a/apps/measure_time/README.md b/apps/measure_time/README.md new file mode 100644 index 0000000000..0b7fc4416b --- /dev/null +++ b/apps/measure_time/README.md @@ -0,0 +1,7 @@ +# Measure Time + +Measure time in a fancy way. Inspired by a Watchface I had on my first Pebble Watch. + +Written by [prefectAtEarth](https://www.github.com/prefectAtEarth) + +![](measuretime.png) \ No newline at end of file diff --git a/apps/measure_time/measuretime-icon.js b/apps/measure_time/measuretime-icon.js new file mode 100644 index 0000000000..ddbd9cd690 --- /dev/null +++ b/apps/measure_time/measuretime-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("sLCDA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAP////////////+T///////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAP////////////+Sf////wAAAP/////////////////////////////////////////////wP///////////////wP////////////+eT///+AAAAB/////////////////////////////////////////////wP///////////////wP////////////+fyf//+B//+B/////////////////////////////////////////////wP0kkkkkv////////wP////////////+f+T//+B//+B/////////////////////////////////////////////wP0kkkkkv////////wP//////////yf+f+T//+B//+B/////////////////////////////////////////////wP0kkkkkv////////wP//////////+T+fyf//+B//+B/////////////////////////////////////////////wP0kkkkkv////////wP///////////yeeT///wAAAAAP////////////////////////////////////////////wP0kkkkkv////////wAAP/////////+SSf//+AAAAAAB////////////////////////////////////////////wP0kkkkkv////////wAAP//////////yT///+AAP/wAB////////////////////////////////////////////wP0kkkkkv////////wAAP//////////yT///+AAP/wAB////////////////////////////////////////////wP0kkkkkv////////wAAP/////////+SSf//+AAP/wAB////////////////////////////////////////////wP0kkkkkv////////wAAP/////////yeeT//+AAB+AAB////////////////////////////////////////////wP0kkkkkv////////wP//////////+T+fyf/+AAB+AAB////////////////////////////////////////////wP0kkkkkv////////wP//////////yf+f+T/+AAB+AAB////////////////////////////////////////////wP0kkkkkv////////wP////////////+f+T/+AAAAAAB////////////////////////////////////////////wP///////////////wP////////////+fyf//wAAAAAP////////////////////////////////////////////wP///////////////wP////////////+eT//////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAP////////////+Sf//////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAP////////////+T///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAP//////////////////////////////////+AAAAAAAAAAAAB///////////////////////////////////wAAAP////////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP/////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP//////////////////////////////////////////////////////////////////////////////////wAAAP//////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/////wAAAP////////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP/////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////wP/////wP/////////////////////x/////////////////////////////////////////////////////////wP/////wP/////////////////////wP////////////////////////////////////////////////////////wP/////wP////////////////////+AP////////////////////////////////////////////////////////wP/////wP////////////////////+AP///////////////////////////////////////////////////////////wAAAP/////////////////////+AB//////////+AAAAAAAAAAAAB///////////////////////////////////wAAAP/////////////////////wAB/////////////////////////////////////////////////////////////////////////////////////wAAP///////////////////////////////////////////////////////////////////////////////////+AAAB///////////////////////////////////////////////////////////////////////////////////wAAAAP/////////////////////////////////////////////////////////////////////////////////+AAAAAB////////+AAAAAAAAAAAAAAAAAAAAAAB/////////////////////////////////////////////////wAAAAAAB///////////////////////////////////////////////////////////////////////////////wAAAAAAAAAP///////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////+AAAAAAAAAB///////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////wAAAAAAB/////////////////////////////////////////////////////////////////////////////////wAAAAAP/////////////////////////////////////////////////////////////////////////////////+AAAAB///////////////////////////////////////////////////////////////////////////////////wAAAP///////////////////////////////////////////////////////////////////////////////////+AAB//////////+AAAAAAAAAAAAB/////////////////////////////////////////////////////////////wAB/////////////////////////////////////////////////////////////////////////////////////wAP/////////////////////////////////////////////////////////////////////////////////////+AP/////////////////////////////////////////////////////////////////////////////////////+AP/////////////////////////////////////////////////////////////////////////////////////+B///////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP/////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP//////////////////////////////////////////////////////////////////////////////////wAAAP//////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/////wAAAP////////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP/////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB////////////////////////////////////////////////////w==")) \ No newline at end of file diff --git a/apps/measure_time/measuretime.js b/apps/measure_time/measuretime.js new file mode 100644 index 0000000000..fc15239204 --- /dev/null +++ b/apps/measure_time/measuretime.js @@ -0,0 +1,187 @@ +{ + require("Font7x11Numeric7Seg").add(Graphics); + g.setFont("7x11Numeric7Seg"); + g.setFontAlign(0,0); + + const centerX = g.getWidth() / 2, //88 + centerY = g.getHeight() / 2; //88 + const lineStart = 25; + const lineEndFull = 110; + const lineEndHalf = 90; + const lineEndQuarter = 70; + const lineEndDefault = 50; + + let drawTimeout; + + let queueDrawTime = function () { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + drawTime(); + }, 60000 - (Date.now() % 60000)); + }; + + let drawCenterLine = function () { + // center line + g.drawLineAA(0, centerY, g.getWidth(), centerY); + // left decoration + var steps = [0,1,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1]; + var stepsReversed = steps.slice(); + stepsReversed.reverse(); + var polyLeftTop = []; + var polyLeftBottom = []; + var polyRightTop = []; + var polyRightBottom = []; + let xL = 0; + let xR = g.getWidth(); + let yT = centerY - 13; + let yB = centerY + 13; + + for(i = 0; i < steps.length; i++) { + xL += steps[i]; + xR -= steps[i]; + yT += stepsReversed[i]; + yB -= stepsReversed[i]; + + // Left Top + polyLeftTop.push(xL); + polyLeftTop.push(yT); + + // Left Bottom + polyLeftBottom.push(xL); + polyLeftBottom.push(yB); + + // Right Top + polyRightTop.push(xR); + polyRightTop.push(yT); + + // Right Bottom + polyRightBottom.push(xR); + polyRightBottom.push(yB); + } + + polyLeftTop.push(0,88); + polyLeftBottom.push(0,88); + polyRightTop.push(g.getWidth(),88); + polyRightBottom.push(g.getWidth(),88); + + g.fillPolyAA(polyLeftTop,true); + g.fillPolyAA(polyLeftBottom,true); + g.fillPolyAA(polyRightTop,true); + g.fillPolyAA(polyRightBottom,true); + }; + + let drawTime = function () { + g.clear(); + var d = new Date(); + var mins = d.getMinutes(); + + var offset = mins % 5; + var yTopLines = centerY - offset; + var topReached = false; + + var yBottomLines = centerY - offset + 5; + var bottomReached = false; + + if (g.theme.dark) { + g.setColor(1,1,1); + } else { + g.setColor(0,0,0); + } + drawCenterLine(); + + var lineEnd = lineEndDefault; + g.setFont("7x11Numeric7Seg", 2); + g.setFontAlign(0,0); + + // gone + do { + switch (yTopLines - 88 + mins) { + case -60: + lineEnd = lineEndFull; + g.drawString(d.getHours() - 1, lineEnd + 10, yTopLines, true); + break; + case 0: + case 60: + lineEnd = lineEndFull; + g.drawString(d.getHours(), lineEnd + 10, yTopLines, true); + break; + case 45: + case -45: + case 15: + case -15: + case -75: + lineEnd = lineEndQuarter; + break; + case 30: + case -30: + lineEnd = lineEndHalf; + break; + default: + lineEnd = lineEndDefault; + } + g.drawLineAA(lineStart, yTopLines, lineEnd, yTopLines); + + yTopLines -= 5; + if (yTopLines < -4) { + topReached = true; + } + } while (!topReached); + + // upcoming + do { + switch (yBottomLines - 88 + mins) { + case 0: + case 60: + lineEnd = lineEndFull; + g.drawString(d.getHours() + 1, lineEnd + 10, yBottomLines, true); + break; + case 120: + lineEnd = lineEndFull; + g.drawString(d.getHours() + 2, lineEnd + 10, yBottomLines, true); + break; + case 15: + case 75: + case 135: + case 45: + case 105: + case 165: + lineEnd = lineEndQuarter; + break; + case 30: + case 90: + case 150: + lineEnd = lineEndHalf; + break; + default: + lineEnd = lineEndDefault; + } + g.drawLineAA(lineStart, yBottomLines, lineEnd, yBottomLines); + + yBottomLines += 5; + if (yBottomLines > 176) { + bottomReached = true; + } + } while (!bottomReached); + + Bangle.drawWidgets(); + queueDrawTime(); + }; + + + g.clear(); + drawTime(); + + Bangle.setUI( + { + mode: "clock", + remove: function () { + if (drawTimeout) clearTimeout(drawTimeout); + require("widget_utils").show(); + } + } + ); + + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} diff --git a/apps/measure_time/measuretime.png b/apps/measure_time/measuretime.png new file mode 100644 index 0000000000000000000000000000000000000000..67425e1dc50b48638ab462ed71b245a1dc8898b7 GIT binary patch literal 5005 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 literal 0 HcmV?d00001 diff --git a/apps/measure_time/test.json b/apps/measure_time/test.json new file mode 100644 index 0000000000..1b41234115 --- /dev/null +++ b/apps/measure_time/test.json @@ -0,0 +1,15 @@ +{ + "app" : "measuretime", + "tests" : [{ + "description": "Check memory usage after setUI", + "steps" : [ + {"t":"cmd", "js": "Bangle.loadWidgets()"}, + {"t":"cmd", "js": "eval(require('Storage').read('measuretime.app.js'))"}, + {"t":"cmd", "js": "Bangle.setUI()"}, + {"t":"saveMemoryUsage"}, + {"t":"cmd", "js": "eval(require('Storage').read('measuretime.app.js'))"}, + {"t":"cmd", "js":"Bangle.setUI()"}, + {"t":"checkMemoryUsage"} + ] + }] +} From 5268a0d86c8c1f460ddfc7c07cf881a3389bb0cb Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 22 Jun 2024 14:49:39 +0200 Subject: [PATCH 089/258] fix: format of app icon fixed --- apps/measure_time/measuretime-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/measure_time/measuretime-icon.js b/apps/measure_time/measuretime-icon.js index ddbd9cd690..4f48c56bd9 100644 --- a/apps/measure_time/measuretime-icon.js +++ b/apps/measure_time/measuretime-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("sLCDA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAP////////////+T///////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAP////////////+Sf////wAAAP/////////////////////////////////////////////wP///////////////wP////////////+eT///+AAAAB/////////////////////////////////////////////wP///////////////wP////////////+fyf//+B//+B/////////////////////////////////////////////wP0kkkkkv////////wP////////////+f+T//+B//+B/////////////////////////////////////////////wP0kkkkkv////////wP//////////yf+f+T//+B//+B/////////////////////////////////////////////wP0kkkkkv////////wP//////////+T+fyf//+B//+B/////////////////////////////////////////////wP0kkkkkv////////wP///////////yeeT///wAAAAAP////////////////////////////////////////////wP0kkkkkv////////wAAP/////////+SSf//+AAAAAAB////////////////////////////////////////////wP0kkkkkv////////wAAP//////////yT///+AAP/wAB////////////////////////////////////////////wP0kkkkkv////////wAAP//////////yT///+AAP/wAB////////////////////////////////////////////wP0kkkkkv////////wAAP/////////+SSf//+AAP/wAB////////////////////////////////////////////wP0kkkkkv////////wAAP/////////yeeT//+AAB+AAB////////////////////////////////////////////wP0kkkkkv////////wP//////////+T+fyf/+AAB+AAB////////////////////////////////////////////wP0kkkkkv////////wP//////////yf+f+T/+AAB+AAB////////////////////////////////////////////wP0kkkkkv////////wP////////////+f+T/+AAAAAAB////////////////////////////////////////////wP///////////////wP////////////+fyf//wAAAAAP////////////////////////////////////////////wP///////////////wP////////////+eT//////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAP////////////+Sf//////////////////////////////////////////////////////wAAAAAAAAAAAAAAAAAP////////////+T///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAP//////////////////////////////////+AAAAAAAAAAAAB///////////////////////////////////wAAAP////////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP/////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP//////////////////////////////////////////////////////////////////////////////////wAAAP//////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/////wAAAP////////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP/////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////wP/////wP/////////////////////x/////////////////////////////////////////////////////////wP/////wP/////////////////////wP////////////////////////////////////////////////////////wP/////wP////////////////////+AP////////////////////////////////////////////////////////wP/////wP////////////////////+AP///////////////////////////////////////////////////////////wAAAP/////////////////////+AB//////////+AAAAAAAAAAAAB///////////////////////////////////wAAAP/////////////////////wAB/////////////////////////////////////////////////////////////////////////////////////wAAP///////////////////////////////////////////////////////////////////////////////////+AAAB///////////////////////////////////////////////////////////////////////////////////wAAAAP/////////////////////////////////////////////////////////////////////////////////+AAAAAB////////+AAAAAAAAAAAAAAAAAAAAAAB/////////////////////////////////////////////////wAAAAAAB///////////////////////////////////////////////////////////////////////////////wAAAAAAAAAP///////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////////////////////////////////////////////////////////////////+AAAAAAAAAB///////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////wAAAAAAB/////////////////////////////////////////////////////////////////////////////////wAAAAAP/////////////////////////////////////////////////////////////////////////////////+AAAAB///////////////////////////////////////////////////////////////////////////////////wAAAP///////////////////////////////////////////////////////////////////////////////////+AAB//////////+AAAAAAAAAAAAB/////////////////////////////////////////////////////////////wAB/////////////////////////////////////////////////////////////////////////////////////wAP/////////////////////////////////////////////////////////////////////////////////////+AP/////////////////////////////////////////////////////////////////////////////////////+AP/////////////////////////////////////////////////////////////////////////////////////+B///////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP/////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP///////////////////////////////////////////////////////////////////////////////wPwP///wP//////////////////////////////////////////////////////////////////////////////////wAAAP//////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/////wAAAP////////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP/////////////////////////////////+AAAAAAAAAAAAB////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP///////////////////////////////////////////////////////////////////////////////wP/////wP/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAB//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+AAAAAAAAAAAAAAAAAAAAAAB////////////////////////////////////////////////////w==")) \ No newline at end of file +require("heatshrink").decompress(atob("2GwwcD/4A/AH4ApXZkPCxH5n5B//MnAgXwCJYAYgYLK/hBK8gECwBBBgJB5/mfAgOBAQhBg+mSpMkyRBR/+TIPiABIP4+BYv//+V58gOCgAACn5BtGAIVG/1Jk4EB8BBDg5B2//JPYRBBn4RBIP5B5YoxB6+V58hBDg4CCINsPCo+T/P8z5B8HwP5IgKD9/5BFAARBiBZRBLYoX8IIc/IPHkHMAAHM4YAIIJUnIP/5XsIA/AH4Av8DqMAAUHKP4A/AH4A/AG/ggAAHv5K/AH4A/AH4A+8EAAA9/JX4A/AH4A/AH3ggAAWv5Z/AH4A/AH4Av8EAAA9/JX4A/AH4A/AH3ggAAHv5K/AH4A/AH4A+8EAAB0HKP4A/AH4A/ADP8gEAgYdZ8AdBAA1/IOwAh+E/IgcPIP5B/IP5Be8EAAA9/Qf7F/IP5B/AEH8gEAgYdZ8AdBADkHIMDF/IP5B/IP4AH8EAAA9/INsfQf//gZB//BB/INg+CgEAF6H4gF/BI3gDoIAGCI5Bl4AvaAEpBBa54Au/yWBgJB9+D2Cn5B8wBBCg4KF8AKCABgXGADv8FQkfQXRtGh4/3/y3PAHKD/gEfBongKBF/IdQwEg6B3AAf8IIc/IPf/wBABgJA8//wIIMPIPv+IIN/BQ3gaIYAECI4Al4AvuIKUDIH3//BB/IP5BDj4JH8EAAC1/Mf4A/AH4A/AF/ggAAHv5K/AH4A/AH4A+8EAAA9/JX4A/AH4A/AH3ggAAOg5R/AH4A/AH4A38EAAA9/JX4AY+E/gYEB/kPIP5B/IP5Be8EAAA9/Qf7F/IP5B/AEH8gEAIgQAX8AdBADkHIMDFjIgbF7IP5B/IMfggAAHv6D/Yv5B/IP4A+8EAAA9/JX4A/AH4A/AH3ggAAOg5R/AH4A/AH4A38EAAA9/JX4A/AH4A/AH3ggAAHv5K/AH4A/AH4A+8EAAC1/LP4A/AH4A/AF/ggAAHv5K/AH4A/AH4A+8EAAA9/JX4A/AH4A/AH3ggAAOg44m")) \ No newline at end of file From be09edea231235af271a53a4a35d8f637550651e Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 22 Jun 2024 14:55:17 +0200 Subject: [PATCH 090/258] fix: rename app js file, update metadata --- apps/measure_time/{measuretime.js => measuretime.app.js} | 0 apps/measure_time/metadata.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename apps/measure_time/{measuretime.js => measuretime.app.js} (100%) diff --git a/apps/measure_time/measuretime.js b/apps/measure_time/measuretime.app.js similarity index 100% rename from apps/measure_time/measuretime.js rename to apps/measure_time/measuretime.app.js diff --git a/apps/measure_time/metadata.json b/apps/measure_time/metadata.json index fdcfcddfae..176cc23cc3 100644 --- a/apps/measure_time/metadata.json +++ b/apps/measure_time/metadata.json @@ -11,7 +11,7 @@ "readme": "README.md", "allow_emulator": true, "storage": [ - { "name": "measuretime.app.js", "url": "measuretime.js" }, + { "name": "measuretime.app.js", "url": "measuretime.app.js" }, { "name": "measuretime.img", "url": "measuretime-icon.js", From 500d38122026b36554aad1b72ef451f8f004be14 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 22 Jun 2024 21:29:52 +0200 Subject: [PATCH 091/258] fix: right poly X start fixed, reformat app js file --- apps/measure_time/README.md | 2 +- apps/measure_time/measuretime.app.js | 34 ++++++++++++++-------------- apps/measure_time/metadata.json | 6 +---- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/apps/measure_time/README.md b/apps/measure_time/README.md index 0b7fc4416b..78d04f30d4 100644 --- a/apps/measure_time/README.md +++ b/apps/measure_time/README.md @@ -2,6 +2,6 @@ Measure time in a fancy way. Inspired by a Watchface I had on my first Pebble Watch. -Written by [prefectAtEarth](https://www.github.com/prefectAtEarth) +Written by [prefectAtEarth](https://www.github.com/prefectAtEarth/) ![](measuretime.png) \ No newline at end of file diff --git a/apps/measure_time/measuretime.app.js b/apps/measure_time/measuretime.app.js index fc15239204..b02e6d8e38 100644 --- a/apps/measure_time/measuretime.app.js +++ b/apps/measure_time/measuretime.app.js @@ -1,7 +1,7 @@ { require("Font7x11Numeric7Seg").add(Graphics); g.setFont("7x11Numeric7Seg"); - g.setFontAlign(0,0); + g.setFontAlign(0, 0); const centerX = g.getWidth() / 2, //88 centerY = g.getHeight() / 2; //88 @@ -20,12 +20,12 @@ drawTime(); }, 60000 - (Date.now() % 60000)); }; - + let drawCenterLine = function () { // center line g.drawLineAA(0, centerY, g.getWidth(), centerY); // left decoration - var steps = [0,1,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1]; + var steps = [0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; var stepsReversed = steps.slice(); stepsReversed.reverse(); var polyLeftTop = []; @@ -33,16 +33,16 @@ var polyRightTop = []; var polyRightBottom = []; let xL = 0; - let xR = g.getWidth(); + let xR = g.getWidth() - 1; let yT = centerY - 13; let yB = centerY + 13; - for(i = 0; i < steps.length; i++) { + for (i = 0; i < steps.length; i++) { xL += steps[i]; xR -= steps[i]; yT += stepsReversed[i]; yB -= stepsReversed[i]; - + // Left Top polyLeftTop.push(xL); polyLeftTop.push(yT); @@ -60,15 +60,15 @@ polyRightBottom.push(yB); } - polyLeftTop.push(0,88); - polyLeftBottom.push(0,88); - polyRightTop.push(g.getWidth(),88); - polyRightBottom.push(g.getWidth(),88); + polyLeftTop.push(0, 88); + polyLeftBottom.push(0, 88); + polyRightTop.push(g.getWidth(), 88); + polyRightBottom.push(g.getWidth(), 88); - g.fillPolyAA(polyLeftTop,true); - g.fillPolyAA(polyLeftBottom,true); - g.fillPolyAA(polyRightTop,true); - g.fillPolyAA(polyRightBottom,true); + g.fillPolyAA(polyLeftTop, true); + g.fillPolyAA(polyLeftBottom, true); + g.fillPolyAA(polyRightTop, true); + g.fillPolyAA(polyRightBottom, true); }; let drawTime = function () { @@ -84,15 +84,15 @@ var bottomReached = false; if (g.theme.dark) { - g.setColor(1,1,1); + g.setColor(1, 1, 1); } else { - g.setColor(0,0,0); + g.setColor(0, 0, 0); } drawCenterLine(); var lineEnd = lineEndDefault; g.setFont("7x11Numeric7Seg", 2); - g.setFontAlign(0,0); + g.setFontAlign(0, 0); // gone do { diff --git a/apps/measure_time/metadata.json b/apps/measure_time/metadata.json index 176cc23cc3..4c0db8b327 100644 --- a/apps/measure_time/metadata.json +++ b/apps/measure_time/metadata.json @@ -12,10 +12,6 @@ "allow_emulator": true, "storage": [ { "name": "measuretime.app.js", "url": "measuretime.app.js" }, - { - "name": "measuretime.img", - "url": "measuretime-icon.js", - "evaluate": true - } + { "name": "measuretime.img", "url": "measuretime-icon.js", "evaluate": true } ] } From ad59d81601071924473c6c256710c3edc4b1805e Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sat, 22 Jun 2024 21:33:11 +0200 Subject: [PATCH 092/258] fix: rename folder measure_time to measuretime --- apps/{measure_time => measuretime}/ChangeLog | 0 apps/{measure_time => measuretime}/README.md | 0 .../measuretime-icon.js | 0 .../measuretime.app.js | 0 apps/{measure_time => measuretime}/measuretime.png | Bin apps/{measure_time => measuretime}/metadata.json | 0 .../small_measuretime.png | Bin apps/{measure_time => measuretime}/test.json | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename apps/{measure_time => measuretime}/ChangeLog (100%) rename apps/{measure_time => measuretime}/README.md (100%) rename apps/{measure_time => measuretime}/measuretime-icon.js (100%) rename apps/{measure_time => measuretime}/measuretime.app.js (100%) rename apps/{measure_time => measuretime}/measuretime.png (100%) rename apps/{measure_time => measuretime}/metadata.json (100%) rename apps/{measure_time => measuretime}/small_measuretime.png (100%) rename apps/{measure_time => measuretime}/test.json (100%) diff --git a/apps/measure_time/ChangeLog b/apps/measuretime/ChangeLog similarity index 100% rename from apps/measure_time/ChangeLog rename to apps/measuretime/ChangeLog diff --git a/apps/measure_time/README.md b/apps/measuretime/README.md similarity index 100% rename from apps/measure_time/README.md rename to apps/measuretime/README.md diff --git a/apps/measure_time/measuretime-icon.js b/apps/measuretime/measuretime-icon.js similarity index 100% rename from apps/measure_time/measuretime-icon.js rename to apps/measuretime/measuretime-icon.js diff --git a/apps/measure_time/measuretime.app.js b/apps/measuretime/measuretime.app.js similarity index 100% rename from apps/measure_time/measuretime.app.js rename to apps/measuretime/measuretime.app.js diff --git a/apps/measure_time/measuretime.png b/apps/measuretime/measuretime.png similarity index 100% rename from apps/measure_time/measuretime.png rename to apps/measuretime/measuretime.png diff --git a/apps/measure_time/metadata.json b/apps/measuretime/metadata.json similarity index 100% rename from apps/measure_time/metadata.json rename to apps/measuretime/metadata.json diff --git a/apps/measure_time/small_measuretime.png b/apps/measuretime/small_measuretime.png similarity index 100% rename from apps/measure_time/small_measuretime.png rename to apps/measuretime/small_measuretime.png diff --git a/apps/measure_time/test.json b/apps/measuretime/test.json similarity index 100% rename from apps/measure_time/test.json rename to apps/measuretime/test.json From c39a56da40f1e89f8602da93188f47c1781b901c Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sun, 23 Jun 2024 13:18:02 +0200 Subject: [PATCH 093/258] fix: update app icon to fit into menu --- apps/measuretime/measuretime-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/measuretime/measuretime-icon.js b/apps/measuretime/measuretime-icon.js index 4f48c56bd9..4592548a76 100644 --- a/apps/measuretime/measuretime-icon.js +++ b/apps/measuretime/measuretime-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("2GwwcD/4A/AH4ApXZkPCxH5n5B//MnAgXwCJYAYgYLK/hBK8gECwBBBgJB5/mfAgOBAQhBg+mSpMkyRBR/+TIPiABIP4+BYv//+V58gOCgAACn5BtGAIVG/1Jk4EB8BBDg5B2//JPYRBBn4RBIP5B5YoxB6+V58hBDg4CCINsPCo+T/P8z5B8HwP5IgKD9/5BFAARBiBZRBLYoX8IIc/IPHkHMAAHM4YAIIJUnIP/5XsIA/AH4Av8DqMAAUHKP4A/AH4A/AG/ggAAHv5K/AH4A/AH4A+8EAAA9/JX4A/AH4A/AH3ggAAWv5Z/AH4A/AH4Av8EAAA9/JX4A/AH4A/AH3ggAAHv5K/AH4A/AH4A+8EAAB0HKP4A/AH4A/ADP8gEAgYdZ8AdBAA1/IOwAh+E/IgcPIP5B/IP5Be8EAAA9/Qf7F/IP5B/AEH8gEAgYdZ8AdBADkHIMDF/IP5B/IP4AH8EAAA9/INsfQf//gZB//BB/INg+CgEAF6H4gF/BI3gDoIAGCI5Bl4AvaAEpBBa54Au/yWBgJB9+D2Cn5B8wBBCg4KF8AKCABgXGADv8FQkfQXRtGh4/3/y3PAHKD/gEfBongKBF/IdQwEg6B3AAf8IIc/IPf/wBABgJA8//wIIMPIPv+IIN/BQ3gaIYAECI4Al4AvuIKUDIH3//BB/IP5BDj4JH8EAAC1/Mf4A/AH4A/AF/ggAAHv5K/AH4A/AH4A+8EAAA9/JX4A/AH4A/AH3ggAAOg5R/AH4A/AH4A38EAAA9/JX4AY+E/gYEB/kPIP5B/IP5Be8EAAA9/Qf7F/IP5B/AEH8gEAIgQAX8AdBADkHIMDFjIgbF7IP5B/IMfggAAHv6D/Yv5B/IP4A+8EAAA9/JX4A/AH4A/AH3ggAAOg5R/AH4A/AH4A38EAAA9/JX4A/AH4A/AH3ggAAHv5K/AH4A/AH4A+8EAAC1/LP4A/AH4A/AF/ggAAHv5K/AH4A/AH4A+8EAAA9/JX4A/AH4A/AH3ggAAOg44m")) \ No newline at end of file +require("heatshrink").decompress(atob("mEw4n/AAIHB/fe8EHrvv333xVS221jnnlFC7//9NP997zXWjHGn+EGJsu9wAC0AHBgugq99C5d0kUq1WtoAHBgnaw8nC5d9mdwgEN7QHBxvQ5nhGwQXNiQHB19A41xC5dy3YXCwAHBwkqx3tI5d3AAV8L4UIDYRkBogADpTOQhWqAAZOLAAuoxAABfyYXXI4pKRO4oACqBHl0QXWAC8IF4QABwpHRkUilALHgutvwvMBY8NoEHKakCqtHR5gAH1FY7wUFcYS/LI5Fwd4r7IqXuJ4uUAYMK1QABKhEKIAQAC1kW7SnDAAUlPxnBiN9xEnu93vx6KAAeHyMdI5wAGox3OS5GAU4oAEoAXJhTXGfigAWhAvWX6QvcT5nog5HJF5QXLX5AAC0levwXId5cNoAvJhWqAAILHgVAhxHMQaZfFwoXQI5YALO5ZHPC6bXDAAmADqYARhBHXkUilC/oA=")) \ No newline at end of file From a56ac40c49d6df4160392c583c3ae670824c47b8 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sun, 23 Jun 2024 13:38:02 +0200 Subject: [PATCH 094/258] feat: hidable widgets by swipe --- apps/measuretime/measuretime.app.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/measuretime/measuretime.app.js b/apps/measuretime/measuretime.app.js index b02e6d8e38..2a45dc779d 100644 --- a/apps/measuretime/measuretime.app.js +++ b/apps/measuretime/measuretime.app.js @@ -83,11 +83,6 @@ var yBottomLines = centerY - offset + 5; var bottomReached = false; - if (g.theme.dark) { - g.setColor(1, 1, 1); - } else { - g.setColor(0, 0, 0); - } drawCenterLine(); var lineEnd = lineEndDefault; @@ -164,7 +159,6 @@ } } while (!bottomReached); - Bangle.drawWidgets(); queueDrawTime(); }; @@ -183,5 +177,5 @@ ); Bangle.loadWidgets(); - Bangle.drawWidgets(); + require("widget_utils").swipeOn(); } From 5428c6fc4bab31aa6afd2963b3a544972b966110 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 08:09:20 -0400 Subject: [PATCH 095/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index 1d1ce90c3b..6f73f4f5d7 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -81,10 +81,9 @@ function readOneCourseData() { } function distanceCalc(lat1, long1, lat2, long2) { - const distLat = (lat1 - lat2) * 111151.3; // 111151.3 = (2*6368500*pi)/360, 6368500 ~ Earth radius at latitude 42.3° - const averageLat = (lat1 + lat2) / 2; - const distLong = (long1 - long2) * 111151.3 * Math.cos(radians(averageLat)); - return Math.sqrt(distLat * distLat + distLong * distLong) / 0.9144; // in yards + const delLat = Math.abs(lat1 - lat2) * 111151.3; // 111151.3 = (2*6368500*pi)/360, 6368500 ~ Earth radius at latitude 42.3° + const delLong = Math.abs(long1 - long2) * 111151.3 * Math.cos(radians((lat1 + lat2)/2)); + return Math.sqrt(delLat * delLat + delLong * delLong) / 0.9144; // in yards } function mainMenu() { From b37271ed2a9d492a75e043b391c5959c01f39210 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 08:16:28 -0400 Subject: [PATCH 096/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index 6f73f4f5d7..e44a6fa676 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -222,7 +222,7 @@ function showPlayData() { // battery level bar g.setColor('#000').drawRect(59, H * 2 / 3 - 2, W - 5, H * 2 / 3 + 4); g.drawRect(58, H * 2 / 3 - 1, W - 4, H * 2 / 3 + 5); - g.setColor(E.getBattery() > 30 ? '#03f' : 'f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 65), H * 2 / 3 + 3); + g.setColor(E.getBattery() > 30 ? '#03f' : 'f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 60), H * 2 / 3 + 3); // hdop level indicator if (lastFix.hdop < 5) g.setColor('#0f0').fillRect(60, H / 3, 96, H / 3 + 5); From 0937aeec7cbd1139bc945b4f43b088a93efa3c11 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:32:55 -0400 Subject: [PATCH 097/258] Update app.js --- apps/jclock/app.js | 69 +++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/apps/jclock/app.js b/apps/jclock/app.js index 5c4d709d6d..f07b6b2839 100644 --- a/apps/jclock/app.js +++ b/apps/jclock/app.js @@ -17,7 +17,7 @@ var drawTimeout; // schedule a draw for the next minute function queueDraw() { if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { + drawTimeout = setTimeout(() => { drawTimeout = undefined; draw(); }, 60000 - (Date.now() % 60000)); @@ -26,43 +26,56 @@ function queueDraw() { const zeroPad = (num, places) => String(num).padStart(places, '0'); function draw() { - let barWidth = 64; + const barWidth = 64; let date = new Date(); - + let battColor = '#0ff'; + // queue next draw in one minute queueDraw(); - + // clean screen g.reset().clearRect(Bangle.appRect); - + // draw side bar in blue - g.setColor('#00f'); - g.fillRect(0, 0, barWidth, g.getHeight()); - + g.setColor('#000').fillRect(0, 0, barWidth, g.getHeight()); + // show time on the right - g.setColor(g.theme.fg); - g.setFontKdamThmor().setFontAlign(0,-1).drawString(zeroPad(date.getHours(),2), 120, 10); - g.setFontKdamThmor().setFontAlign(0,-1).drawString(zeroPad(date.getMinutes(),2), 120, g.getHeight()/2+10); - - // show date - g.setFont('Vector', 20).setFontAlign(0, -1).setColor('#fff'); - g.drawString(require("date_utils").dow(date.getDay(),1).toUpperCase(), barWidth/2, 3); - g.drawString(date.getDate(), barWidth/2, 28); - g.drawString(require("date_utils").month(date.getMonth()+1,1).toUpperCase(), barWidth/2, 53); - - // divider, place holder for any other info - g.drawString('=====', barWidth/2, 78); - + g.setColor(g.theme.fg).setFontAlign(0, 0).setFontKdamThmor(); + g.setColor('#11f').drawString(zeroPad(date.getHours(), 2), 120, g.getHeight() / 4 + 12); + g.setColor('#000').drawString(zeroPad(date.getMinutes(), 2), 120, g.getHeight() * 3 / 4 + 12); + + // day of week + g.setColor('#fff').setFontAlign(0, -1).setFont('Vector', 28).drawString(require("date_utils").dow(date.getDay(), 1).toUpperCase(), barWidth / 2 + 1, 3); + //g.setFont('Vector', 30).drawString(twoCharsDayOfWeek[date.getDay()], barWidth/2, 3); + + // month + g.drawString(require("date_utils").month(date.getMonth() + 1, 1).toUpperCase(), barWidth / 2 + 1, 84); + + // date + g.setColor('#0ff').setFont('Vector', 48).drawString(date.getDate(), barWidth / 2 + 5, 36); + // show daily steps - g.drawString(Bangle.getHealthStatus("day").steps, barWidth/2, 103); - + g.setFontAlign(1, -1).setColor('#fff').setFont('Vector', 24).drawString((Bangle.getHealthStatus("day").steps / 1000).toFixed(1) + 'k', barWidth, 125); + + // Bluetooth/GPS/Compass connection status + if (NRF.getSecurityStatus().connected) g.setColor('#0ff').fillRect(5, 115, barWidth - 5, 120); + // show battery remaining percentage - g.drawString(E.getBattery() + '%', barWidth/2, 153); - - // Bluetooth connection status - if (NRF.getSecurityStatus().connected) g.drawString('>BT<', barWidth/2, 128); + battColor = Bangle.isCharging() ? '#f00' : (E.getBattery() < 30 ? '#ff0' : '#0f0'); + g.setColor(battColor).drawString(E.getBattery() + '%', barWidth, 153); } -draw(); +function handleEvent() { + draw(); +} + +Bangle.on('charging', handleEvent); +NRF.on('connect', handleEvent); +NRF.on('disconnect', handleEvent); Bangle.setUI("clock"); + +// Load widgets +Bangle.loadWidgets(); +require("widget_utils").hide(); +draw(); From 971729d65bc016baff973ca2162d6038e2d681c8 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:33:10 -0400 Subject: [PATCH 098/258] Delete apps/jclock/jclock_screenshot_BT.png --- apps/jclock/jclock_screenshot_BT.png | Bin 3694 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/jclock/jclock_screenshot_BT.png diff --git a/apps/jclock/jclock_screenshot_BT.png b/apps/jclock/jclock_screenshot_BT.png deleted file mode 100644 index e4eac99d0f30bb233278216128c63419c4e09a18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3694 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 JzZ{E?_zx60#QOjM From d08cc3aa8f3919c9c005efa81e08cb69af9ba277 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:33:23 -0400 Subject: [PATCH 099/258] Delete apps/jclock/jclock_screenshot_no_BT.png --- apps/jclock/jclock_screenshot_no_BT.png | Bin 2280 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/jclock/jclock_screenshot_no_BT.png diff --git a/apps/jclock/jclock_screenshot_no_BT.png b/apps/jclock/jclock_screenshot_no_BT.png deleted file mode 100644 index a312d23edb47d4b889dec25f853961ac44c6bbd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2280 zcmY+GeLT}^AIIlsvY9d~a-%giA`gqWDbITiGnqmpG3ri5Hj%KshMx#UMmnUGyO3!f zE2CPhPDRI4;XXZJb5tA;Lm2XS|D5x>|G59S9=_N2`?{{z>-v5_@9WAsOmp9=qOAgh z!M1u($Ue|mx7iR|puV>8TL?5L#QM0CU^QL3V~_zqPDa8yQUrjD^9r zz1nOF8b;I<7!1MjAiMY_gvf4#jPFLOu68RM8v}2O@0Ax2g!L~C7bzd6CN9G}-f;*$ zt3Q{PR#y`a@SQ`}R~G3o9{_ZT!0n^tB&eAV64#9b_7&SQdRNZ@&=&W< z2$V(YV{4qP404Wsw4^K4p$PjZTj5(OJsQ%r!s@GwYQ@&caK+Vm-8Lt}*DMMhPpGpX zP}r3B3+(;qPl(7LILll=pqI7K$`H9;Zd*LBp77G2i8OhW`uY^fAlk4#9+7Uf-uX|E z9jHg#OZ;c}LB9o3uuY9jV0v$Evs#qm4 zt2h70_0P8(lmNSX>M?F7J&i3DK<0a@1%7zUqpET&*b5i!Oe_3^L7eZP{k+e_SM?UA zh<8r@qF~&i2Xd!Qo@hk#bZUd+Bcxc3COv!)G4sqa#vE7#Vze4+NAvDZ5V(_zMJRi`PW4gt zBy5FlC+aPX9wbq;a@ZAZIOQ;Smuk{T@?(8Y)?uKR*Ix=ai8aFD{sVPGSMIQX9v$-x zMJf0wf}pWUZy^F7?Ub>wU$}GV>isJ+JZABHb%8fWq_m#+xXKH??aSKacq85}Vy8Yh zJt8wBwt0Ox^&?6fO_;HNK7J*`maC0Ween6GKnZ-g>RC9Rbe$f5vio&! z_L$jMvXA8MrmCr?5zK$tp{KYS&Ba5oAYT^0Ta!Qp<5-R5Iq?!tLMc6RrT* z;cef%TJ(K^#yFK7JAyKWHct#sE@KRl1Knn9SmDXf`~oUGr7ypA%K;7u-T7`r5h8X~#ml zmi*f_EYgu#a7xIo@`!89TPgNO#nhVh@R@_trhz?BwJ6b)?i-e6Slxoto-2oRUVro1 z#V`?xFvS(WW2EG!9C0IS z`975a{^1sy8Z)d;Aqms@sdV5lx+yeJ!6XlF$ROjQUkU)9TYA;Hh!f6Av#Eb4&}Xh8 zeP})V_o(271+#%>9F`KwEzWmu+wF%VLQ^Qkn-N?`_-Ur}l?*iuLW(v{Oj~t5tiro+@nrV!q0lE~yRiTQwxrR$T6|$!5 zqnmA}t7q@FZ$IYi^m4f@MU(V6`2|V8{Cgb!aCpt_BQn>9iSB(5&-6do2$>xW4l-L? zVu$-)!*^bdB$EzG1wZ4cGk?sFw=VUav8wn|k@WHIshG}|1Z?3HNxL$f+@)V1emx)- zHHWq-_Dz!?GbZM_zA`F89Eu#kYP>r>AJyMY%@A6c+!JmbV%5H~*!>>q4zs*PPqBosa2 z7~%%4v16WxWT45#JFF9WQEIS^N_g}0N;j!T|Fl~^pCXMEK{7kz_Pt!=?9SJ|C%V(= zS)=y9w4~cA>}5EJ9V9k-f2Ggm10lf5|2&ABaS>hcgus9jV4d|Ks(o^-0)XA5GTE;W z=M8Yt`?$@PxETeJH3x~%MU>U*B6Q)^A|{Ov0Lrj;0%c&&h-|YOJ9%|+pOA1#oAK-v z6>>6%3pmqLiOxe?J4~06LCWn1oQK}<2+TCPIzq&4+(Qg=P(z3^d%xvuVj={VzevQ! zg5oZo5wid~w+>H{FhwJZ;2qne63V6ql)0jb6!BZ0Y3(J(-MLip6r%Z&RxZyotVs^Im{bQ=5i7EUyeWHaN-FjzFgCHZ# zBrzb*D@6Zm(U~I5Cr|!fwgbz*>O()7|1T;Xdg<)kwd2`-wsG7i%zohTg+w*en*pho zIM`)~0ZoWitCDp#W4R`OvARDan-6Cwl{GMO?f6pu^sX0^;tYrhhYV&0c`J$Xl5+V& z*_gc?#{O(Z{Nqu2LaGUqUQ*HRoMFhpw|N%iwY&E$oEv}!FV From a2966a0b632ac1da9993ed445501b2918daeaa8b Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:33:51 -0400 Subject: [PATCH 100/258] Add files via upload --- apps/jclock/screenshot_BT.png | Bin 0 -> 3968 bytes apps/jclock/screenshot_noBT.png | Bin 0 -> 2697 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/jclock/screenshot_BT.png create mode 100644 apps/jclock/screenshot_noBT.png diff --git a/apps/jclock/screenshot_BT.png b/apps/jclock/screenshot_BT.png new file mode 100644 index 0000000000000000000000000000000000000000..0a68e9703f8748f6fd9acdac35b3c7280bd60d10 GIT binary patch literal 3968 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 DZ1y8; literal 0 HcmV?d00001 From 009fee314e3f406a1a9c5f1529eab958544ae8f1 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:34:55 -0400 Subject: [PATCH 101/258] Update README.md --- apps/jclock/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/jclock/README.md b/apps/jclock/README.md index 7ddb346dcc..2d8c11838a 100644 --- a/apps/jclock/README.md +++ b/apps/jclock/README.md @@ -18,8 +18,8 @@ I have used Rebble clock since I bought my Banglejs 2, and wanted to make my own - Update time and status every 1 minute ## Screenshots -![](jclock_screenshot_no_BT.png) -![](jclock_screenshot_BT.png) +![](screenshot_noBT.png) +![](screenshot_BT.png) ## Creator From adaeb1561d92304bfe6b48c79b43789ced6cd4df Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:36:26 -0400 Subject: [PATCH 102/258] Update ChangeLog --- apps/jclock/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/jclock/ChangeLog b/apps/jclock/ChangeLog index c530930a63..e5146766cf 100644 --- a/apps/jclock/ChangeLog +++ b/apps/jclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: Created 0.02: Changed side bar color to blue for better clarity when it's locked +0.03: Minor updates on font size/color and Bluetooth indicator From 2ba7c3d2d9a38fefd92e6784e99372d351c35529 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:37:03 -0400 Subject: [PATCH 103/258] Update metadata.json --- apps/jclock/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/jclock/metadata.json b/apps/jclock/metadata.json index 9a7ddd3375..830f5f2bad 100644 --- a/apps/jclock/metadata.json +++ b/apps/jclock/metadata.json @@ -2,12 +2,12 @@ "name":"jclock", "shortName":"jclock", "icon":"app.png", - "version":"0.02", + "version":"0.03", "description":"Similar layout to Rebble clock, but much simpler features with switched time and the feature window. The time is on the right side. This is my first Bangle app.", "type": "clock", "tags": "clock", "supports" : ["BANGLEJS2"], - "screenshots": [{"url":"jclock_screenshot_no_BT.png"},{"url":"jclock_screenshot_BT.png"}], + "screenshots": [{"url":"screenshot_noBT.png"},{"url":"screenshot_BT.png"}], "storage": [ {"name":"jclock.app.js","url":"app.js"}, {"name":"jclock.img","url":"app-icon.js","evaluate":true} From ae539835da5bc718b17daab454f0ed2083d64406 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 24 Jun 2024 16:52:59 +0100 Subject: [PATCH 104/258] dfu version --- apps/fwupdate/custom.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 11ba50717a..6c47cf3f2d 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -105,6 +105,7 @@ if (crcs[0] == 1787004733) version = "2v20"; // check 6 page CRCs - the 7th page isn't used in 2v20+ else if (crcs[0] == 3816337552) version = "2v21"; else if (crcs[0] == 3329616485) version = "2v22"; + else if (crcs[0] == 1569433504) version = "2v23"; else { // for other versions all 7 pages are used, check those var crc = crcs[1]; if (crc==1339551013) { version = "2v10.219"; ok = false; } From 912354f96b8521ed9ca10d7f694192da62493c25 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:45:28 -0400 Subject: [PATCH 105/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index e44a6fa676..6359ba9e2a 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -99,6 +99,11 @@ function mainMenu() { }); } +function backToClock() { + NRF.wake(); + load(); +} + function browseScore() { const scoreFiles = require("Storage").list(/^Scorecard-/); if (scoreFiles.length === 0) { @@ -108,7 +113,7 @@ function browseScore() { "END": 1 } }).then(choice => { - if (choice === 1) load(); + if (choice === 1) backToClock(); }); } let fileIndx = scoreFiles.length - 1; @@ -126,7 +131,7 @@ function browseScore() { }).then(choice => { if (choice === 1) fileIndx = (fileIndx - 1 + scoreFiles.length) % scoreFiles.length; else if (choice === 2) fileIndx = (fileIndx + 1) % scoreFiles.length; - else if (choice === 3) load(); + else if (choice === 3) backToClock(); browseFiles(); }); } @@ -134,6 +139,7 @@ function browseScore() { } function fixGPS() { + NRF.sleep(); Bangle.on('GPS', onGPS); Bangle.setGPSPower(1, "golf-gps"); E.showMessage("Golf GPS v0.1\n\nWaiting for GPS fix...\n\nwritten by\nJinseok Jeon\n\n "); From beff82a2f953f73c61accd325ee54eab39eb0efa Mon Sep 17 00:00:00 2001 From: Brian Whelan Date: Mon, 24 Jun 2024 18:22:09 +0100 Subject: [PATCH 106/258] Return empty array instead of object if JSON read fails --- apps/reply/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/reply/lib.js b/apps/reply/lib.js index 0e2fd86f74..e390669e83 100644 --- a/apps/reply/lib.js +++ b/apps/reply/lib.js @@ -41,7 +41,7 @@ exports.reply = function (options) { require("Storage").readJSON( options.fileOverride || "replies.json", true - ) || {}; + ) || []; replies.forEach((reply) => { menu = Object.defineProperty(menu, reply.text, { value: () => constructReply(options.msg ?? {}, reply.text, resolve), From b281c123a7d36abb260367e791170337c5aacc0c Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 24 Jun 2024 13:32:34 -0400 Subject: [PATCH 107/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index 6359ba9e2a..e44a6fa676 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -99,11 +99,6 @@ function mainMenu() { }); } -function backToClock() { - NRF.wake(); - load(); -} - function browseScore() { const scoreFiles = require("Storage").list(/^Scorecard-/); if (scoreFiles.length === 0) { @@ -113,7 +108,7 @@ function browseScore() { "END": 1 } }).then(choice => { - if (choice === 1) backToClock(); + if (choice === 1) load(); }); } let fileIndx = scoreFiles.length - 1; @@ -131,7 +126,7 @@ function browseScore() { }).then(choice => { if (choice === 1) fileIndx = (fileIndx - 1 + scoreFiles.length) % scoreFiles.length; else if (choice === 2) fileIndx = (fileIndx + 1) % scoreFiles.length; - else if (choice === 3) backToClock(); + else if (choice === 3) load(); browseFiles(); }); } @@ -139,7 +134,6 @@ function browseScore() { } function fixGPS() { - NRF.sleep(); Bangle.on('GPS', onGPS); Bangle.setGPSPower(1, "golf-gps"); E.showMessage("Golf GPS v0.1\n\nWaiting for GPS fix...\n\nwritten by\nJinseok Jeon\n\n "); From d918f573eb4b37c17fc6ca4e45f33c7620c76485 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Tue, 25 Jun 2024 09:32:47 -0400 Subject: [PATCH 108/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index e44a6fa676..682a62a31d 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -42,7 +42,7 @@ var lastFix = { lon: 0, alt: 0, // altitude in m speed: 0, // km/h - course: 0, // heading in degree + course: 0, // heading in degrees time: 0, satellites: 0, fix: 0, @@ -81,9 +81,9 @@ function readOneCourseData() { } function distanceCalc(lat1, long1, lat2, long2) { - const delLat = Math.abs(lat1 - lat2) * 111151.3; // 111151.3 = (2*6368500*pi)/360, 6368500 ~ Earth radius at latitude 42.3° - const delLong = Math.abs(long1 - long2) * 111151.3 * Math.cos(radians((lat1 + lat2)/2)); - return Math.sqrt(delLat * delLat + delLong * delLong) / 0.9144; // in yards + const delLat = Math.abs(lat1 - lat2) * 111194.9; // 111194.9 = (2*6371000*pi)/360, 6371000 ~ Earth's average radius + const delLong = Math.abs(long1 - long2) * 111194.9 * Math.cos(radians((lat1+lat2)/2)); + return Math.sqrt(delLat * delLat + delLong * delLong)/0.9144; // in yards } function mainMenu() { From b6c013a9aec5a5968d1e395e15cc6bdac7995cec Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 25 Jun 2024 21:51:55 +0100 Subject: [PATCH 109/258] Fix lint --- apps/measuretime/measuretime.app.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/measuretime/measuretime.app.js b/apps/measuretime/measuretime.app.js index 2a45dc779d..c7865bffee 100644 --- a/apps/measuretime/measuretime.app.js +++ b/apps/measuretime/measuretime.app.js @@ -3,8 +3,7 @@ g.setFont("7x11Numeric7Seg"); g.setFontAlign(0, 0); - const centerX = g.getWidth() / 2, //88 - centerY = g.getHeight() / 2; //88 + const centerY = g.getHeight() / 2; //88 const lineStart = 25; const lineEndFull = 110; const lineEndHalf = 90; @@ -37,7 +36,7 @@ let yT = centerY - 13; let yB = centerY + 13; - for (i = 0; i < steps.length; i++) { + for (let i = 0; i < steps.length; i++) { xL += steps[i]; xR -= steps[i]; yT += stepsReversed[i]; From 2c39e38b3f3e6aca5d8f6e4f1a8105397ce3e4f4 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 25 Jun 2024 22:04:07 +0100 Subject: [PATCH 110/258] Fail the build if lint warnings appear --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 619a4c482f..57ecf5201f 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "npm-watch": "^0.11.0" }, "scripts": { - "lint-apps": "node bin/sync-lint-exemptions.mjs && eslint ./apps", - "lint-modules": "eslint ./modules", + "lint-apps": "node bin/sync-lint-exemptions.mjs && eslint --max-warnings 0 ./apps", + "lint-modules": "eslint --max-warnings 0 ./modules", "test": "node bin/sanitycheck.js && npm run lint-apps && npm run lint-modules", "fix": "eslint --fix ./apps ./modules", "update-local-apps": "./bin/create_apps_json.sh apps.local.json", From ccf6c2a7e566cf3b49348af74a1580db6abc88d2 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 25 Jun 2024 22:04:31 +0100 Subject: [PATCH 111/258] Detect unused lint-disable directives --- .eslintrc.js | 1 + apps/btadv/app.ts | 2 +- apps/clkinfogps/geotools.js | 6 ++-- apps/gipy/pkg/gps.d.ts | 2 +- apps/gipy/pkg/gps_bg.wasm.d.ts | 2 +- apps/gpstouch/geotools.js | 6 ++-- apps/kitchen/kitchen.app.js | 2 +- apps/lint_exemptions.js | 6 ++-- apps/schoolCalendar/fullcalendar/main.js | 18 +++++----- apps/walkersclock/app.js | 46 ++++++++++++------------ apps/wohrm/app.js | 6 ++-- 11 files changed, 49 insertions(+), 48 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5d15ec3857..e79f87a5db 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -245,4 +245,5 @@ module.exports = { })), ], ignorePatterns: findGeneratedJS(["apps/", "modules/"]), + reportUnusedDisableDirectives: true, } diff --git a/apps/btadv/app.ts b/apps/btadv/app.ts index b56546ba11..3511cca5d6 100644 --- a/apps/btadv/app.ts +++ b/apps/btadv/app.ts @@ -1,6 +1,6 @@ { // @ts-expect-error helper -// eslint-disable-next-line @typescript-eslint/no-unused-vars + const __assign = Object.assign; const Layout = require("Layout"); diff --git a/apps/clkinfogps/geotools.js b/apps/clkinfogps/geotools.js index d251c1f959..2f25c8453e 100644 --- a/apps/clkinfogps/geotools.js +++ b/apps/clkinfogps/geotools.js @@ -1,5 +1,5 @@ /** - * + * * A module of Geo functions for use with gps fixes * * let geo = require("geotools"); @@ -71,7 +71,7 @@ OsGridRef.latLongToOsGrid = function(point) { * */ function to_map_ref(digits, easting, northing) { - if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing + if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); let e = easting; let n = northing; @@ -108,7 +108,7 @@ function to_map_ref(digits, easting, northing) { } /** - * + * * Module exports section, example code below * * let geo = require("geotools"); diff --git a/apps/gipy/pkg/gps.d.ts b/apps/gipy/pkg/gps.d.ts index 9ad6460682..6e2c14f5a8 100644 --- a/apps/gipy/pkg/gps.d.ts +++ b/apps/gipy/pkg/gps.d.ts @@ -1,5 +1,5 @@ /* tslint:disable */ -/* eslint-disable */ + /** * @param {Gps} gps */ diff --git a/apps/gipy/pkg/gps_bg.wasm.d.ts b/apps/gipy/pkg/gps_bg.wasm.d.ts index 251d9c3b1c..5b84a92291 100644 --- a/apps/gipy/pkg/gps_bg.wasm.d.ts +++ b/apps/gipy/pkg/gps_bg.wasm.d.ts @@ -1,5 +1,5 @@ /* tslint:disable */ -/* eslint-disable */ + export const memory: WebAssembly.Memory; export function __wbg_gps_free(a: number): void; export function disable_elevation(a: number): void; diff --git a/apps/gpstouch/geotools.js b/apps/gpstouch/geotools.js index 5adc57872b..93fcc1fdc3 100644 --- a/apps/gpstouch/geotools.js +++ b/apps/gpstouch/geotools.js @@ -1,5 +1,5 @@ /** - * + * * A module of Geo functions for use with gps fixes * * let geo = require("geotools"); @@ -71,7 +71,7 @@ OsGridRef.latLongToOsGrid = function(point) { * */ function to_map_ref(digits, easting, northing) { - if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing + if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); let e = easting; let n = northing; @@ -108,7 +108,7 @@ function to_map_ref(digits, easting, northing) { } /** - * + * * Module exports section, example code below * * let geo = require("geotools"); diff --git a/apps/kitchen/kitchen.app.js b/apps/kitchen/kitchen.app.js index 2c2cebaef8..1d3d70a1ff 100644 --- a/apps/kitchen/kitchen.app.js +++ b/apps/kitchen/kitchen.app.js @@ -463,7 +463,7 @@ OsGridRef.latLongToOsGrid = function(point) { * */ function to_map_ref(digits, easting, northing) { - if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing + if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); let e = easting; let n = northing; diff --git a/apps/lint_exemptions.js b/apps/lint_exemptions.js index 055e3e1dc6..e5a2170bdf 100644 --- a/apps/lint_exemptions.js +++ b/apps/lint_exemptions.js @@ -926,7 +926,7 @@ module.exports = { ] }, "apps/kitchen/kitchen.app.js": { - "hash": "1ef7b31e52110e34fb952d7ba0426c8bd9574e5f18be9fbc3b8ad1cc762dda21", + "hash": "cea726937a7179851091b0728d3ad1e773eac703a5bfdc28be6e2f247fdd44c9", "rules": [ "no-undef" ] @@ -1004,7 +1004,7 @@ module.exports = { ] }, "apps/gpstouch/geotools.js": { - "hash": "5816fbb2dd630f574e5ee505e1b9ec6f80c3c53778b7a5520e5db28b91cdffc5", + "hash": "7e67733286f9d7708a54814f6f27d73ddffed2f433febc9604138f2f7a832cbf", "rules": [ "no-undef" ] @@ -1314,7 +1314,7 @@ module.exports = { ] }, "apps/schoolCalendar/fullcalendar/main.js": { - "hash": "04dcd3cb3025c7aa67631d287b025a897b1cd984b8ea306abae2d722976fb7c5", + "hash": "8c417deb073328655117a93f045e77e9b808e84d584e648c6d7e360271ae8d07", "rules": [ "no-undef", "no-unused-vars", diff --git a/apps/schoolCalendar/fullcalendar/main.js b/apps/schoolCalendar/fullcalendar/main.js index 95650f472b..a41e45c677 100644 --- a/apps/schoolCalendar/fullcalendar/main.js +++ b/apps/schoolCalendar/fullcalendar/main.js @@ -111,7 +111,7 @@ var FullCalendar = (function (exports) { ContextType.Provider = function () { var _this = this; var isNew = !this.getChildContext; - var children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params + var children = origProvider.apply(this, arguments); if (isNew) { var subs_1 = []; this.shouldComponentUpdate = function (_props) { @@ -4688,14 +4688,14 @@ var FullCalendar = (function (exports) { var wrappedSuccess = function () { if (!isResolved) { isResolved = true; - success.apply(this, arguments); // eslint-disable-line prefer-rest-params + success.apply(this, arguments); } }; var wrappedFailure = function () { if (!isResolved) { isResolved = true; if (failure) { - failure.apply(this, arguments); // eslint-disable-line prefer-rest-params + failure.apply(this, arguments); } } }; @@ -5008,7 +5008,7 @@ var FullCalendar = (function (exports) { var createPortal = FullCalendarVDom.createPortal; var flushToDom = FullCalendarVDom.flushToDom; var unmountComponentAtNode = FullCalendarVDom.unmountComponentAtNode; - /* eslint-enable */ + var ScrollResponder = /** @class */ (function () { function ScrollResponder(execFunc, emitter, scrollTime, scrollTimeReset) { @@ -5085,7 +5085,7 @@ var FullCalendar = (function (exports) { } PureComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) { if (this.debug) { - // eslint-disable-next-line no-console + console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state)); } return !compareObjs(this.props, nextProps, this.propEquality) || @@ -6613,7 +6613,7 @@ var FullCalendar = (function (exports) { var endMarker = framingRange.end; var instanceStarts = []; while (dayMarker < endMarker) { - var instanceStart + var instanceStart // if everyday, or this particular day-of-week = void 0; // if everyday, or this particular day-of-week @@ -11731,7 +11731,7 @@ var FullCalendar = (function (exports) { } dragging.emitter.on('pointerdown', this.handlePointerDown); dragging.emitter.on('dragstart', this.handleDragStart); - new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new + new ExternalElementDragging(dragging, settings.eventData); } ExternalDraggable.prototype.destroy = function () { this.dragging.destroy(); @@ -11833,7 +11833,7 @@ var FullCalendar = (function (exports) { if (typeof settings.mirrorSelector === 'string') { dragging.mirrorSelector = settings.mirrorSelector; } - new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new + new ExternalElementDragging(dragging, settings.eventData); } ThirdPartyDraggable.prototype.destroy = function () { this.dragging.destroy(); @@ -13605,7 +13605,7 @@ var FullCalendar = (function (exports) { if (!slatCoords) { return null; } - return segs.map(function (seg, i) { return (createElement(NowIndicatorRoot, { isAxis: false, date: date, + return segs.map(function (seg, i) { return (createElement(NowIndicatorRoot, { isAxis: false, date: date, // key doesn't matter. will only ever be one key: i }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-line'].concat(classNames).join(' '), style: { top: slatCoords.computeDateTop(seg.start, date) } }, innerContent)); })); }); }; diff --git a/apps/walkersclock/app.js b/apps/walkersclock/app.js index 5b83bf583d..f78be61ca2 100644 --- a/apps/walkersclock/app.js +++ b/apps/walkersclock/app.js @@ -8,7 +8,7 @@ * - two function menus at present * GPS Power = On/Off * GPS Display = Grid | Speed Alt - * when the modeline in CYAN use button BTN1 to switch between options + * when the modeline in CYAN use button BTN1 to switch between options * - display the current steps if one of the steps widgets is installed * - ensures that BTN2 requires a 1.5 second press in order to switch to the launcher * this is so you dont accidently switch out of the GPS/watch display with you coat sleeve @@ -65,14 +65,14 @@ let last_fix = { satellites: 0 }; -function drawTime() { +function drawTime() { var d = new Date(); var da = d.toString().split(" "); var time = da[4].substr(0,5); g.reset(); g.clearRect(0,Y_TIME, 239, Y_ACTIVITY - 1); - + g.setColor(1,1,1); // white g.setFontAlign(0, -1); @@ -83,7 +83,7 @@ function drawTime() { } else { g.setFont("Vector", 80); } - + g.drawString(time, g.getWidth()/2, Y_TIME); } @@ -93,19 +93,19 @@ function drawActivity() { clearActivityArea = true; prevSteps = steps; - + if (clearActivityArea) { g.clearRect(0, Y_ACTIVITY, 239, Y_MODELINE - 1); clearActivityArea = false; } - + if (!gpsPowerState) { g.setColor(0,255,0); // green g.setFont("Vector", 60); g.drawString(getSteps(), g.getWidth()/2, Y_ACTIVITY); return; } - + g.setFont("6x8", 3); g.setColor(1,1,1); g.setFontAlign(0, -1); @@ -130,10 +130,10 @@ function drawActivity() { let ref = to_map_ref(6, os.easting, os.northing); let speed; let activityStr = ""; - + if (age < 0) age = 0; g.setFontVector(40); - g.setColor(0xFFC0); + g.setColor(0xFFC0); switch(gpsDisplay) { case GDISP_OS: @@ -146,7 +146,7 @@ function drawActivity() { case GDISP_SPEED: speed = last_fix.speed; speed = speed.toFixed(1); - activityStr = speed + "kph"; + activityStr = speed + "kph"; break; case GDISP_ALT: activityStr = last_fix.alt + "m"; @@ -159,7 +159,7 @@ function drawActivity() { g.clearRect(0, Y_ACTIVITY, 239, Y_MODELINE - 1); g.drawString(activityStr, 120, Y_ACTIVITY); g.setFont("6x8",2); - g.setColor(1,1,1); + g.setColor(1,1,1); g.drawString(age, 120, Y_ACTIVITY + 46); } } @@ -167,7 +167,7 @@ function drawActivity() { function onTick() { if (!Bangle.isLCDOn()) return; - + if (gpsPowerState) { drawAll(); return; @@ -226,7 +226,7 @@ function drawInfo() { drawModeLine(str,col); return; } - + switch(infoMode) { case INFO_NONE: col = 0x0000; @@ -239,7 +239,7 @@ function drawInfo() { default: str = "Battery: " + E.getBattery() + "%"; } - + drawModeLine(str,col); } @@ -283,7 +283,7 @@ function changeInfoMode() { infoMode = INFO_NONE; clearActivityArea = true; return; - + case FN_MODE_GDISP: switch (gpsDisplay) { case GDISP_OS: @@ -304,7 +304,7 @@ function changeInfoMode() { break; } } - + switch(infoMode) { case INFO_NONE: if (stepsWidget() !== undefined) @@ -319,7 +319,7 @@ function changeInfoMode() { default: infoMode = INFO_NONE; } - + clearActivityArea = true; } @@ -351,7 +351,7 @@ function changeFunctionMode() { break; } } - + infoMode = INFO_NONE; // function mode overrides info mode } @@ -374,7 +374,7 @@ function processFix(fix) { gpsState = GPS_SATS; clearActivityArea = true; } - + if (fix.fix) { if (!last_fix.fix) { if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) { @@ -401,7 +401,7 @@ function stepsWidget() { } return undefined; } - + /************* GPS / OSREF Code **************************/ @@ -413,10 +413,10 @@ function formatTime(now) { function timeSince(t) { var hms = t.split(":"); var now = new Date(); - + var sn = 3600*(now.getHours()) + 60*(now.getMinutes()) + 1*(now.getSeconds()); var st = 3600*(hms[0]) + 60*(hms[1]) + 1*(hms[2]); - + return (sn - st); } @@ -483,7 +483,7 @@ OsGridRef.latLongToOsGrid = function(point) { * */ function to_map_ref(digits, easting, northing) { - if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing + if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); let e = easting; let n = northing; diff --git a/apps/wohrm/app.js b/apps/wohrm/app.js index e32ab36bad..728c934eaf 100644 --- a/apps/wohrm/app.js +++ b/apps/wohrm/app.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ + const Setter = { NONE: "none", UPPER: 'upper', @@ -31,7 +31,7 @@ const upperLshape = isB1 ? { left: 210, bottom: 40, top: 210, - rectWidth: 30, + rectWidth: 30, cornerRoundness: 5, orientation: -1, color: '#f00' @@ -62,7 +62,7 @@ const centerBar = { minY: (upperLshape.bottom + upperLshape.top - (upperLshape.rectWidth*1.5))/2, maxY: (upperLshape.bottom + upperLshape.top + (upperLshape.rectWidth*1.5))/2, confidenceWidth: isB1 ? 10 : 8, - minX: isB1 ? 55 : upperLshape.rectWidth + 14, + minX: isB1 ? 55 : upperLshape.rectWidth + 14, maxX: isB1 ? 165 : Bangle.appRect.x2 - upperLshape.rectWidth - 14 }; From e9c8a7f67f21d7c21cae994489cf502503f9f8ad Mon Sep 17 00:00:00 2001 From: deirdreobyrne Date: Wed, 26 Jun 2024 18:01:18 +0100 Subject: [PATCH 112/258] Initial version --- apps/bootbthomebatt/ChangeLog | 1 + apps/bootbthomebatt/README.md | 13 +++++++++++++ apps/bootbthomebatt/bluetooth.png | Bin 0 -> 1119 bytes apps/bootbthomebatt/boot.js | 10 ++++++++++ apps/bootbthomebatt/metadata.json | 15 +++++++++++++++ 5 files changed, 39 insertions(+) create mode 100644 apps/bootbthomebatt/ChangeLog create mode 100644 apps/bootbthomebatt/README.md create mode 100644 apps/bootbthomebatt/bluetooth.png create mode 100644 apps/bootbthomebatt/boot.js create mode 100644 apps/bootbthomebatt/metadata.json diff --git a/apps/bootbthomebatt/ChangeLog b/apps/bootbthomebatt/ChangeLog new file mode 100644 index 0000000000..2a37193a3e --- /dev/null +++ b/apps/bootbthomebatt/ChangeLog @@ -0,0 +1 @@ +0.01: Initial release. diff --git a/apps/bootbthomebatt/README.md b/apps/bootbthomebatt/README.md new file mode 100644 index 0000000000..990dcb16b0 --- /dev/null +++ b/apps/bootbthomebatt/README.md @@ -0,0 +1,13 @@ +# BLE BTHome Battery Service + +Broadcasts battery remaining percentage over BLE using the BTHome protocol + +## Usage + +This boot code runs in the background and has no user interface. + +Based on [BootGATTBat](https://github.com/espruino/BangleApps/tree/master/apps/bootgattbat) by [Jonathan Jefferies](https://github.com/jjok). + +## Creator + +[Deirdre O'Byrne](https://github.com/deirdreobyrne) diff --git a/apps/bootbthomebatt/bluetooth.png b/apps/bootbthomebatt/bluetooth.png new file mode 100644 index 0000000000000000000000000000000000000000..1a884a62c1029ae43243f8f58ef9dc3a3327e437 GIT binary patch literal 1119 zcmV-l1fctgP)cE5(396`@Dz$wLqIPzYppJ*B(brZX|I zHChYW;Hm1lL~9feUi^283bm!!Ka0DG`v(zZvk8h?W%2@c?XT zES*ZPJU3a{7h{cB0RRrPTh=dGr*a^!0&xQX?DMczGEQwQ4)Y`c0EQJR_Oa?r)W%5x z0HhI_x1HK2pc0j7k^sKW*iQXQ=2YX6D9n-q_%qO+(!1;R%(3!ggBm9SkZ!j|fYm^E zR%O?(qZ5_=gLo$b@WZ#`wXJ`4=)@t~Y>Yv)Y!7y;OB zeO8rs?l*_RK!7NCKLhRU0}||esEhzCyx)O;I=Y5XtC(@B$NTljy45^tT?SHJ10ruX zOS$(<@@!=?P@`0+S)vnkL!=bB)DOg{Q*}L+GO)XAK;&$g@DSo22n#XlR9!)?07D(! zDxz;SOSunBbNCAVm!5U2c~9jVx@WU3=u3)pJ!ur3cu;sm-)NQ!pM}i;0{{TnZpA^Z ztASvff%b#?JdoF$<=hv8)Q159pyx{LBoBD4SyIzhb(HlW;>}!J+1=AqCDO_A6&XN}=e)0!pcibn z_HtD9d_@BAknp}zDCb9=>MK#y^fmCZ_8GoYkv>KTT7e$nHy?0mXP*UpX*>1PgVgRc z3#Fdn#d~2}5mATky^{qxZ#%U!Ve5AonQN!;&C-!_@cJGbKmk6^xYakqWbkDSU>e?6 zF9=onXw<2mHO=A62q0{DUp*iYB(+@Ja7a-n2aQU$C-1do)^M)j_l7Z`m-DHf;N2WNgeIsEr6@kFmJ z8_YbwGn3bL?QPY+LOBr_TDRcEMmfJ|;s=HR0IQ!ry9xAti1(G5Y&@#1^&${_&Hfi+ z9c`2jUpMuHhg{we{KebpwXs3NLqaRiAr)b6sg!$n>kZxDN)lj0k<-mm?qZatNdeqJ z)Lky+8&Ml40dUjvB)_tlzY&Ld+&A&{bh~wEWib~^c!+ZagoS&X-t=l^d_A@r#J2*U lKABp3ezkHW*6{xc{R@3@kib`1LqGrk002ovPDHLkV1l684mkh- literal 0 HcmV?d00001 diff --git a/apps/bootbthomebatt/boot.js b/apps/bootbthomebatt/boot.js new file mode 100644 index 0000000000..b585fb3c56 --- /dev/null +++ b/apps/bootbthomebatt/boot.js @@ -0,0 +1,10 @@ +(() => { + var btHomeSequenceNo = 0; + function advertiseBTHomeBattery() { + require("ble_advert").set(0xFCD2, [0x40,0x00,btHomeSequenceNo,0x01,E.getBattery()]); + btHomeSequenceNo = (btHomeSequenceNo + 1) & 255; + } + + setInterval(advertiseBTHomeBattery, 5 * 60 * 1000); + advertiseBattery(); +})(); diff --git a/apps/bootbthomebatt/metadata.json b/apps/bootbthomebatt/metadata.json new file mode 100644 index 0000000000..9deebfdc07 --- /dev/null +++ b/apps/bootbthomebatt/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "bootbthomebatt", + "name": "BLE BTHome Battery Service", + "shortName": "BTHome Battery Service", + "version": "0.01", + "description": "Broadcasts battery remaining over bluetooth using the BTHome protocol.\n", + "icon": "bluetooth.png", + "type": "bootloader", + "tags": "battery,ble,bluetooth,bthome", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"bthomebat.boot.js","url":"boot.js"} + ] +} From 9450cce1a56ff7b412440ba8e0c5d3b6942f7fb0 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:08:05 -0400 Subject: [PATCH 113/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index 682a62a31d..3f23c6d98d 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -1,3 +1,9 @@ +/* + Golf-GPS app v0.01 + written by JeonLab (https://jeonlab.wordpress.com) + 6/26/2024 +*/ + var currentHole = 1, totalShots = 0, courseName = "", @@ -72,8 +78,8 @@ function readOneCourseData() { l = courseData.readLine(); if (l !== undefined) { const parts = l.split(','); - lat[i] = parseFloat(parts[0]).toFixed(6); - lon[i] = parseFloat(parts[1]).toFixed(6); + lat[i] = parseFloat(parts[0]); + lon[i] = parseFloat(parts[1]); par[i] = parseInt(parts[2]); score[i] = 0; } @@ -81,8 +87,8 @@ function readOneCourseData() { } function distanceCalc(lat1, long1, lat2, long2) { - const delLat = Math.abs(lat1 - lat2) * 111194.9; // 111194.9 = (2*6371000*pi)/360, 6371000 ~ Earth's average radius - const delLong = Math.abs(long1 - long2) * 111194.9 * Math.cos(radians((lat1+lat2)/2)); + let delLat = Math.abs(lat1 - lat2) * 111194.9; // 111194.9 = (2*6371000*pi)/360, 6371000 ~ Earth's average radius + let delLong = Math.abs(long1 - long2) * 111194.9 * Math.cos(radians((lat1+lat2)/2)); return Math.sqrt(delLat * delLat + delLong * delLong)/0.9144; // in yards } @@ -141,7 +147,7 @@ function fixGPS() { let fixInterval = setInterval(() => { let date = new Date(); g.clearRect(0, 150, W, H); - g.setFontAlign(1, 1).setFont('6x8:3').drawString(E.getBattery(), W, H); + g.setFontAlign(1, 1).setFont('6x8:3').drawString(lastFix.hdop, W, H); g.setFontAlign(-1, 1).setFont('6x8:3').drawString((date.getHours() > 12 ? date.getHours() % 12 : date.getHours()) + ':' + zeroPad(date.getMinutes(), 2), 2, H); if (lastFix.fix && lastFix.hdop <= 5) { clearInterval(fixInterval); From 61b6e98017dc3d4ae8c828a3bd0e6121b0941243 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 26 Jun 2024 18:08:52 +0100 Subject: [PATCH 114/258] boot: only set `display:1` if we have a passkey --- apps/boot/ChangeLog | 1 + apps/boot/bootupdate.js | 4 ++-- apps/boot/metadata.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index b7535f3de2..1d8e44b72f 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -71,3 +71,4 @@ 0.60: Minor code improvements 0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined') 0.62: Handle setting for configuring BLE privacy +0.63: Only set BLE `display:1` if we have a passkey diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 714f5f3a61..aa4a7e7b58 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -79,9 +79,9 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`; if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`; if (s.bleprivacy || (s.passkey!==undefined && s.passkey.length==6)) { - let passkey = s.passkey ? `passkey:${E.toJS(s.passkey.toString())},` : ""; + let passkey = s.passkey ? `passkey:${E.toJS(s.passkey.toString())},display:1,mitm:1,` : ""; let privacy = s.bleprivacy ? `privacy:${E.toJS(s.bleprivacy)},` : ""; - boot+=`NRF.setSecurity({${passkey}${privacy}mitm:1,display:1});\n`; + boot+=`NRF.setSecurity({${passkey}${privacy}});\n`; } if (s.blename === false) boot+=`NRF.setAdvertising({},{showName:false});\n`; if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`; diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index 2a93c19128..dcc55da58e 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.62", + "version": "0.63", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", From e3b29d4f5f2a58f4aeb9ecbcd2d492737ae59a1c Mon Sep 17 00:00:00 2001 From: deirdreobyrne Date: Wed, 26 Jun 2024 18:53:59 +0100 Subject: [PATCH 115/258] Attempt 2 --- apps/bootbthomebatt/boot.js | 45 ++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/apps/bootbthomebatt/boot.js b/apps/bootbthomebatt/boot.js index b585fb3c56..e13135b45e 100644 --- a/apps/bootbthomebatt/boot.js +++ b/apps/bootbthomebatt/boot.js @@ -1,10 +1,39 @@ -(() => { - var btHomeSequenceNo = 0; - function advertiseBTHomeBattery() { - require("ble_advert").set(0xFCD2, [0x40,0x00,btHomeSequenceNo,0x01,E.getBattery()]); - btHomeSequenceNo = (btHomeSequenceNo + 1) & 255; +var btHomeBatterySequence = 0; + +function advertiseBTHomeBattery() { + var advert = [ 0x40, /* BTHome Device Information + bit 0: "Encryption flag" + bit 1-4: "Reserved for future use" + bit 5-7: "BTHome Version" */ + + 0x00, // Sequence number + btHomeBatterySequence, + + 0x01, // Battery, 8 bit + E.getBattery() + ]; + + if(Array.isArray(Bangle.bleAdvert)){ + var found = false; + for(var ad in Bangle.bleAdvert){ + if(ad[0xFCD2]){ + ad[0xFCD2] = advert; + found = true; + break; + } + } + if(!found) + Bangle.bleAdvert.push({ 0xFCD2: advert }); + } else { + Bangle.bleAdvert[0xFCD2] = advert; } + NRF.setAdvertising(Bangle.bleAdvert); + btHomeBatterySequence = (btHomeBatterySequence + 1) & 255; +} + +if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; +setInterval(function() { + advertiseBTHomeBattery(); +}, 300000); // update every 5 min - setInterval(advertiseBTHomeBattery, 5 * 60 * 1000); - advertiseBattery(); -})(); +advertiseBTHomeBattery(); From e0b3bb0cbeb0ae7e1a28b40185c5758c93a156da Mon Sep 17 00:00:00 2001 From: deirdreobyrne Date: Wed, 26 Jun 2024 19:18:34 +0100 Subject: [PATCH 116/258] Update README.md Adding link to the protocol description --- apps/bootbthomebatt/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bootbthomebatt/README.md b/apps/bootbthomebatt/README.md index 990dcb16b0..b3e10e1cfe 100644 --- a/apps/bootbthomebatt/README.md +++ b/apps/bootbthomebatt/README.md @@ -1,6 +1,6 @@ # BLE BTHome Battery Service -Broadcasts battery remaining percentage over BLE using the BTHome protocol +Broadcasts battery remaining percentage over BLE using the [BTHome protocol](https://bthome.io/) ## Usage From 1a22dbbc5cc09ab105e3e853a873b4ce8fdda5da Mon Sep 17 00:00:00 2001 From: deirdreobyrne Date: Wed, 26 Jun 2024 19:30:14 +0100 Subject: [PATCH 117/258] Cleanup --- apps/bootbthomebatt/README.md | 4 +--- apps/bootbthomebatt/boot.js | 12 +----------- apps/bootbthomebatt/metadata.json | 2 +- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/apps/bootbthomebatt/README.md b/apps/bootbthomebatt/README.md index b3e10e1cfe..79c379f33b 100644 --- a/apps/bootbthomebatt/README.md +++ b/apps/bootbthomebatt/README.md @@ -1,13 +1,11 @@ # BLE BTHome Battery Service -Broadcasts battery remaining percentage over BLE using the [BTHome protocol](https://bthome.io/) +Broadcasts battery remaining percentage over BLE using the [BTHome protocol](https://bthome.io/) - which makes for easy integration into [Home Assistant](https://www.home-assistant.io/) ## Usage This boot code runs in the background and has no user interface. -Based on [BootGATTBat](https://github.com/espruino/BangleApps/tree/master/apps/bootgattbat) by [Jonathan Jefferies](https://github.com/jjok). - ## Creator [Deirdre O'Byrne](https://github.com/deirdreobyrne) diff --git a/apps/bootbthomebatt/boot.js b/apps/bootbthomebatt/boot.js index e13135b45e..64275ed312 100644 --- a/apps/bootbthomebatt/boot.js +++ b/apps/bootbthomebatt/boot.js @@ -1,17 +1,7 @@ var btHomeBatterySequence = 0; function advertiseBTHomeBattery() { - var advert = [ 0x40, /* BTHome Device Information - bit 0: "Encryption flag" - bit 1-4: "Reserved for future use" - bit 5-7: "BTHome Version" */ - - 0x00, // Sequence number - btHomeBatterySequence, - - 0x01, // Battery, 8 bit - E.getBattery() - ]; + var advert = [0x40, 0x00, btHomeBatterySequence, 0x01, E.getBattery()]; if(Array.isArray(Bangle.bleAdvert)){ var found = false; diff --git a/apps/bootbthomebatt/metadata.json b/apps/bootbthomebatt/metadata.json index 9deebfdc07..0992edc245 100644 --- a/apps/bootbthomebatt/metadata.json +++ b/apps/bootbthomebatt/metadata.json @@ -3,7 +3,7 @@ "name": "BLE BTHome Battery Service", "shortName": "BTHome Battery Service", "version": "0.01", - "description": "Broadcasts battery remaining over bluetooth using the BTHome protocol.\n", + "description": "Broadcasts battery remaining over bluetooth using the BTHome protocol - makes for easy integration with Home Assistant.\n", "icon": "bluetooth.png", "type": "bootloader", "tags": "battery,ble,bluetooth,bthome", From ce28c0d1b6085d44d1bd2fa5cd40178d7b948860 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 26 Jun 2024 21:41:39 +0100 Subject: [PATCH 118/258] btadv: preserve existing bleAdvert --- apps/btadv/app.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/btadv/app.ts b/apps/btadv/app.ts index 3511cca5d6..3f66d3f183 100644 --- a/apps/btadv/app.ts +++ b/apps/btadv/app.ts @@ -771,7 +771,11 @@ enableSensors(); const bangle2 = Bangle as { bleAdvert?: BleAdvert | BleAdvert[]; }; - const cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : []; + const cycle = Array.isArray(bangle2.bleAdvert) + ? bangle2.bleAdvert + : bangle2.bleAdvert + ? [bangle2.bleAdvert] + : []; for(const id in ad){ const serv = ad[id as BleServ]; From 91dd1f6ec4d03374504633b96be9b39800edb46d Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 26 Jun 2024 21:42:16 +0100 Subject: [PATCH 119/258] btadv: use `ble_advert` module --- apps/btadv/ChangeLog | 1 + apps/btadv/app.js | 9 +-------- apps/btadv/app.ts | 21 +-------------------- apps/btadv/metadata.json | 2 +- 4 files changed, 4 insertions(+), 29 deletions(-) diff --git a/apps/btadv/ChangeLog b/apps/btadv/ChangeLog index 245f4fca6b..c019a97b93 100644 --- a/apps/btadv/ChangeLog +++ b/apps/btadv/ChangeLog @@ -1,3 +1,4 @@ 0.01: New app! 0.02: Advertise accelerometer data and sensor location 0.03: Use the bleAdvert module +0.04: Actually use the ble_advert module diff --git a/apps/btadv/app.js b/apps/btadv/app.js index b72a8127a5..457973e471 100644 --- a/apps/btadv/app.js +++ b/apps/btadv/app.js @@ -1,4 +1,3 @@ -var _a; { var __assign = Object.assign; var Layout_1 = require("Layout"); @@ -441,8 +440,6 @@ var _a; NRF.setServices(ad, { uart: false, }); - var bangle2 = Bangle; - var cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : []; for (var id in ad) { var serv = ad[id]; var value = void 0; @@ -450,11 +447,7 @@ var _a; value = serv[ch].value; break; } - cycle.push((_a = {}, _a[id] = value || [], _a)); + require("ble_advert").set(id, value || []); } - bangle2.bleAdvert = cycle; - NRF.setAdvertising(cycle, { - interval: 100, - }); } } diff --git a/apps/btadv/app.ts b/apps/btadv/app.ts index 3f66d3f183..4ae75fae31 100644 --- a/apps/btadv/app.ts +++ b/apps/btadv/app.ts @@ -767,16 +767,6 @@ enableSensors(); }, ); - type BleAdvert = { [key: string]: number[] }; - const bangle2 = Bangle as { - bleAdvert?: BleAdvert | BleAdvert[]; - }; - const cycle = Array.isArray(bangle2.bleAdvert) - ? bangle2.bleAdvert - : bangle2.bleAdvert - ? [bangle2.bleAdvert] - : []; - for(const id in ad){ const serv = ad[id as BleServ]; let value; @@ -787,16 +777,7 @@ enableSensors(); break; } - cycle.push({ [id]: value || [] }); + require("ble_advert").set(id, value || []); } - - bangle2.bleAdvert = cycle; - - NRF.setAdvertising( - cycle, - { - interval: 100, - } - ); } } diff --git a/apps/btadv/metadata.json b/apps/btadv/metadata.json index 060c2b498e..71a0fedaf4 100644 --- a/apps/btadv/metadata.json +++ b/apps/btadv/metadata.json @@ -2,7 +2,7 @@ "id": "btadv", "name": "btadv", "shortName": "btadv", - "version": "0.03", + "version": "0.04", "description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth", "icon": "icon.png", "tags": "health,tool,sensors,bluetooth", From beb02e6260a095de1e1d9b097734f5c6584697e3 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 26 Jun 2024 21:43:35 +0100 Subject: [PATCH 120/258] bthometemp: use `ble_advert` module --- apps/bthometemp/ChangeLog | 1 + apps/bthometemp/app.js | 17 +---------------- apps/bthometemp/metadata.json | 2 +- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/bthometemp/ChangeLog b/apps/bthometemp/ChangeLog index 480780ec5d..94a60aa3e2 100644 --- a/apps/bthometemp/ChangeLog +++ b/apps/bthometemp/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Handle the case where other apps have set bleAdvert to an array +0.03: Use the ble_advert module diff --git a/apps/bthometemp/app.js b/apps/bthometemp/app.js index cf74c79376..f18e33c208 100644 --- a/apps/bthometemp/app.js +++ b/apps/bthometemp/app.js @@ -38,21 +38,7 @@ function onTemperature(p) { pressure100&255,(pressure100>>8)&255,pressure100>>16 ]; - if(Array.isArray(Bangle.bleAdvert)){ - var found = false; - for(var ad in Bangle.bleAdvert){ - if(ad[0xFCD2]){ - ad[0xFCD2] = advert; - found = true; - break; - } - } - if(!found) - Bangle.bleAdvert.push({ 0xFCD2: advert }); - }else{ - Bangle.bleAdvert[0xFCD2] = advert; - } - NRF.setAdvertising(Bangle.bleAdvert); + require("ble_advert").set(0xFCD2, advert); } // Gets the temperature in the most accurate way with pressure sensor @@ -60,7 +46,6 @@ function drawTemperature() { Bangle.getPressure().then(p =>{if (p) onTemperature(p);}); } -if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; setInterval(function() { drawTemperature(); }, 10000); // update every 10s diff --git a/apps/bthometemp/metadata.json b/apps/bthometemp/metadata.json index fc6804f17b..3e96d95f84 100644 --- a/apps/bthometemp/metadata.json +++ b/apps/bthometemp/metadata.json @@ -1,7 +1,7 @@ { "id": "bthometemp", "name": "BTHome Temperature and Pressure", "shortName":"BTHome T", - "version":"0.02", + "version":"0.03", "description": "Displays temperature and pressure, and advertises them over bluetooth for Home Assistant using BTHome.io standard", "icon": "app.png", "tags": "bthome,bluetooth,temperature", From f8e854403a104dc97ad810371c2609f4a85acc9f Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Wed, 26 Jun 2024 22:36:30 +0100 Subject: [PATCH 121/258] hwid batt: colour based on power use, highlight based on time left --- apps/hwid_a_battery_widget/ChangeLog | 1 + apps/hwid_a_battery_widget/metadata.json | 2 +- apps/hwid_a_battery_widget/widget.js | 65 +++++++++++++++++++----- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/apps/hwid_a_battery_widget/ChangeLog b/apps/hwid_a_battery_widget/ChangeLog index 99601c31b3..e12d070f67 100644 --- a/apps/hwid_a_battery_widget/ChangeLog +++ b/apps/hwid_a_battery_widget/ChangeLog @@ -9,3 +9,4 @@ 0.09: Add option for showing battery high mark 0.10: Fix background color 0.11: Minor code improvements +0.12: Show power consumption and hours remaining via percentage shading and colour diff --git a/apps/hwid_a_battery_widget/metadata.json b/apps/hwid_a_battery_widget/metadata.json index 2abc57a9bd..7b7e18f200 100644 --- a/apps/hwid_a_battery_widget/metadata.json +++ b/apps/hwid_a_battery_widget/metadata.json @@ -3,7 +3,7 @@ "name": "A Battery Widget (with percentage) - Hanks Mod", "shortName":"H Battery Widget", "icon": "widget.png", - "version":"0.11", + "version":"0.12", "type": "widget", "supports": ["BANGLEJS", "BANGLEJS2"], "readme": "README.md", diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index 84fa32e0ce..95d283d05d 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -5,7 +5,6 @@ var old_x = this.x; var old_y = this.y; - let COLORS = { 'white': g.theme.dark ? "#000" : "#fff", 'black': g.theme.dark ? "#fff" : "#000", @@ -20,25 +19,26 @@ return COLORS.low; }; - function draw() { - var s = width - 1; - var x = this.x; - var y = this.y; - if (x !== undefined && y !== undefined) { + function draw(_self, uu) { + let x = this.x; + let y = this.y; + if (x != null && y != null) { + g.reset(); g.setBgColor(COLORS.white); g.clearRect(old_x, old_y, old_x + width, old_y + height); const l = E.getBattery(); // debug: Math.floor(Math.random() * 101); + let s = width - 1; let xl = x+4+l*(s-12)/100; + // Charging bar g.setColor(levelColor(l)); - g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar + g.fillRect(x+4,y+14+3,xl,y+16+3); + // Show percentage - g.setColor(COLORS.black); g.setFontAlign(0,0); g.setFont('Vector',16); - g.drawString(l, x + 14, y + 10); - + this.drawText(l, uu); } old_x = this.x; old_y = this.y; @@ -47,10 +47,49 @@ else changeInterval(id, intervalLow); } - Bangle.on('charging',function(charging) { draw(); }); - var id = setInterval(()=>WIDGETS["hwid_a_battery_widget"].draw(), intervalLow); + const drawString = function(l) { + g.drawString(l, this.x + 14, this.y + 10); + }; + let drawText; + + if(E.getPowerUsage){ + drawText = function(l, uu) { + const u = uu == null ? E.getPowerUsage().total : uu; + + // text colour is based off power usage + // colour height is based off time left, higher = more + + g.setColor(COLORS.black); + drawString.call(this, l); + + if(u >= 23000) + g.setColor("#f00"); // red, e.g. GPS ~20k + else if(u > 2000) + g.setColor("#fc0"); // yellow, e.g. CPU ~1k, HRM ~700 + else + g.setColor("#0f0"); // green: ok + + const hrs = 200000 / u; + const days = hrs / 24; + const dayPercent = Math.min(days / 16, 1); + const th = g.getFontHeight(); + + g.setClipRect(this.x, this.y + dayPercent * th, this.x + width, this.y + th); + + drawString.call(this, l); + }; + }else{ + drawText = function(l) { + g.setColor(COLORS.black); + drawString.call(this, l); + }; + } + + const d = () => WIDGETS["hwid_a_battery_widget"].draw(); + Bangle.on('charging', d); + var id = setInterval(d, intervalLow); var width = 30; var height = 19; - WIDGETS["hwid_a_battery_widget"]={area:"tr",width,draw:draw}; + WIDGETS["hwid_a_battery_widget"]={area:"tr",width,draw,drawText}; })(); From 324e17ff957aacb335403a23a67752b2bbd75232 Mon Sep 17 00:00:00 2001 From: deirdreobyrne Date: Wed, 26 Jun 2024 23:26:08 +0100 Subject: [PATCH 122/258] Using the ble_advert module --- apps/bootbthomebatt/boot.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/apps/bootbthomebatt/boot.js b/apps/bootbthomebatt/boot.js index 64275ed312..a9bdcd4d3d 100644 --- a/apps/bootbthomebatt/boot.js +++ b/apps/bootbthomebatt/boot.js @@ -3,25 +3,10 @@ var btHomeBatterySequence = 0; function advertiseBTHomeBattery() { var advert = [0x40, 0x00, btHomeBatterySequence, 0x01, E.getBattery()]; - if(Array.isArray(Bangle.bleAdvert)){ - var found = false; - for(var ad in Bangle.bleAdvert){ - if(ad[0xFCD2]){ - ad[0xFCD2] = advert; - found = true; - break; - } - } - if(!found) - Bangle.bleAdvert.push({ 0xFCD2: advert }); - } else { - Bangle.bleAdvert[0xFCD2] = advert; - } - NRF.setAdvertising(Bangle.bleAdvert); + require("ble_advert").set(0xFCD2, advert); btHomeBatterySequence = (btHomeBatterySequence + 1) & 255; } -if (!Bangle.bleAdvert) Bangle.bleAdvert = {}; setInterval(function() { advertiseBTHomeBattery(); }, 300000); // update every 5 min From 5f6d49a62222650579ec3f522d4121b3b34fea4a Mon Sep 17 00:00:00 2001 From: Brian Whelan Date: Thu, 27 Jun 2024 08:23:36 +0100 Subject: [PATCH 123/258] PR fixes --- apps/reply/interface.html | 15 ++++++--------- apps/reply/lib.js | 6 ------ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/apps/reply/interface.html b/apps/reply/interface.html index 2034a11952..ddad4ef35f 100644 --- a/apps/reply/interface.html +++ b/apps/reply/interface.html @@ -61,14 +61,14 @@ function toggleVisibility() { let empty = document.getElementById("empty"); let loading = document.getElementById("loading"); - if (replies.length == 0) { - empty.setAttribute("class", "d-block"); - loading.setAttribute("class", "d-hide"); - } - else if (fetching) { + if (fetching) { loading.setAttribute("class", "d-block"); empty.setAttribute("class", "d-hide"); } + else if (replies.length == 0) { + empty.setAttribute("class", "d-block"); + loading.setAttribute("class", "d-hide"); + } else { loading.setAttribute("class", "d-hide"); empty.setAttribute("class", "d-hide"); @@ -105,7 +105,7 @@ var reply = {}; reply.text = document.getElementById('msg').value; replies.push(reply); - return updateDevice(); + updateDevice(); } function updateDevice() { @@ -114,10 +114,7 @@ Util.writeStorage("replies.json", JSON.stringify(replies), () => { fetching = false; renderReplyList(); - return true; }); - fetching = false; - return false; } diff --git a/apps/reply/lib.js b/apps/reply/lib.js index e390669e83..4a040c557a 100644 --- a/apps/reply/lib.js +++ b/apps/reply/lib.js @@ -12,8 +12,6 @@ exports.reply = function (options) { responseMessage = { t: "notify", id: msg.id, n: "REPLY", msg: replyText }; } E.showMenu(); - layout.setUI(); - layout.render(); if (options.sendReply == null || options.sendReply) { Bluetooth.println(JSON.stringify(responseMessage)); } @@ -26,8 +24,6 @@ exports.reply = function (options) { title: options.title || /*LANG*/ "Reply with:", back: function () { E.showMenu(); - layout.setUI(); - layout.render(); reject("User pressed back"); }, }, // options @@ -56,8 +52,6 @@ exports.reply = function (options) { { buttons: { Ok: true }, remove: function () { - layout.setUI(); - layout.render(); reject( "Please install a keyboard app, or set a custom reply via the app loader!" ); From d56e64235a702b45236cd9c60f046ff33bbd1aba Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 27 Jun 2024 09:18:16 +0100 Subject: [PATCH 124/258] bump latest version --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index a8d6d10eb9..d63610adec 100644 --- a/loader.js +++ b/loader.js @@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v22"; +var RECOMMENDED_VERSION = "2v23"; // could check http://www.espruino.com/json/BANGLEJS.json for this // We're only interested in Bangles From 5a2f18885c9b4f90599f12739bb133b0a5475fe3 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:34:01 -0400 Subject: [PATCH 125/258] Update storage-write.js --- apps/golf-gps/storage-write.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/golf-gps/storage-write.js b/apps/golf-gps/storage-write.js index fbd800c215..69b4e319c2 100644 --- a/apps/golf-gps/storage-write.js +++ b/apps/golf-gps/storage-write.js @@ -12,7 +12,8 @@ and hit HOME and type " and hit END and type \n"+. */ -f = require("Storage").open("course-data","w"); // "w" to create or overwrite, "a" to append +const Storage = require("Storage"); +const f = Storage.open("course-data", "a"); // "w" to create or overwrite, "a" to append f.write( "course name\n"+ "00.000000,00.000000,0\n"+ From c76f9353c51d8c4efb172c907a52ccb812e21225 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 17:39:46 +0200 Subject: [PATCH 126/258] Create app.js --- apps/ashadyclock/app.js | 81 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 apps/ashadyclock/app.js diff --git a/apps/ashadyclock/app.js b/apps/ashadyclock/app.js new file mode 100644 index 0000000000..d7ff795943 --- /dev/null +++ b/apps/ashadyclock/app.js @@ -0,0 +1,81 @@ +let drawTimeout; +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +let images = new Array(10); +for (i=0;i<10;i++) { + let image = + { + width : 98, height : 100, bpp : 3, + transparent: 4, + buffer : require("Storage").read(i + ".bin") + }; + images[i] = image; +} + +let palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ + g.toColor("#000"), + g.toColor("#000"), + g.toColor("#F00"), + g.toColor("#FF0"), + g.toColor("#00F"), + g.toColor("#000"), + g.toColor("#FF0"), + g.toColor("#000") + ]).buffer))); + +let palTop = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ + g.toColor("#FFF"), + g.toColor("#000"), + g.toColor("#FFF"), + g.toColor("#FFF"), + g.toColor("#00F"), + g.toColor("#000"), + g.toColor("#FFF"), + g.toColor("#000"), + ]).buffer))); + +function draw() { + + // work out how to display the current time + let d = new Date(); + + g.setColor(0,0,0); + g.fillRect(0,0,g.getWidth(),g.getHeight()); + + let xOffset = (g.getWidth() - 176) / 2; + let yOffset = (g.getWidth() - 176) / 2; + + let imgUR = images[d.getMinutes() % 10]; + let imgUL = images[Math.floor(d.getMinutes()/10)]; + imgUR.palette = palBottom; + imgUL.palette = palBottom; + g.drawImage(imgUR, 75 + xOffset, 77 + yOffset); + g.drawImage(imgUL, 0 + xOffset, 77 + yOffset); + + let imgOR = images[d.getHours() % 10]; + let imgOL = images[Math.floor(d.getHours()/10)]; + imgOR.palette = palTop; + imgOL.palette = palTop; + g.drawImage(imgOR, 75 + xOffset, 0 + yOffset); + g.drawImage(imgOL, 0 + xOffset, 0 + yOffset); + + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); +// draw immediately at first +draw(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); From b2a84ea6d3cbb45f5faf6a29ac9a7a9faf15aaaa Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 17:42:16 +0200 Subject: [PATCH 127/258] Add files via upload --- apps/ashadyclock/app.png | Bin 0 -> 2179 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/ashadyclock/app.png diff --git a/apps/ashadyclock/app.png b/apps/ashadyclock/app.png new file mode 100644 index 0000000000000000000000000000000000000000..e01fd0746948e6f2413136e69fed8056820f6444 GIT binary patch literal 2179 zcmV-}2z>X6P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2pCC3K~z{rotS%2 zRo4~9KL{Ygm2i1Kj7U%ui&P~b1srRWF)C)-SZ!mC7DQtmL&rMW_)n(OI2yrL>_3i9 zCpa;2QfHEBQY(^JK?K37wGx{;MNKIx@)qR%xEK1Jb2i6&UiV)7%^v*LK5Os0&f3qT z56lP*44j!bGbJU(ZnyXL_J)Ln49oD*6q%l$p2*0^k`GGy`}>t9Bxh9)8&c?;y{U0H z9H;+snx;uWYk#_S``g=R&zbFXI>W-k0s;cW0|SZAB{nxVE1IF)xOpS-`9#H>EbNE9 zQTQshE9=*<&&tkX0%Brf+(dJ`{&r_)r!WBK@~NXCckkR)gh@aPQWps2md;0=BO@ci z0G2IZCe%~G*HeT(pz!eUxVSiBxTXAWtNOV286$`t;nt~YaD zU;X-!W5~30ba%wZ#|u9hor6-|{mr||Q?6XyBiFKR`!>amnD0Xho8Q`eM*oy?!+|E>=9y!u6G&nm#xcH!y~%4aV>AG zdqX&RqT*tOuB}?`H{VZ8)bFL188W)MqMjJ;+4bvP-1LQEpy)tRLqmfwJd*QTj_!qA zi*RyFSa{fjmIu0qYN@KJ3JMAmt}a++C^IWlC<9(jp#2B-M?`4WoeFojJ-hb|3~07i z1&Q3+M8_z5ARFr?Aa156I0P z-PF{?TedI^q@|~A*}6p-1`Z!OTyv>L7#5fZ73J(b($49ty3giTZ`QeqKJhyEkNEdlE8VLUuHgr(kX( zLbSJblj{fPEZ4j+3}h|J@*DbR+8Asc)lswuhQ zq&SABAtVsPLl~TbsUsM5ARq#D-{8!*!qX%}ZayCP71q|)mN%(VYIj6GkCy@w>W63>(xWgh7z?A247{3xww9`4lP4M6oN+J9DyScZGG5u8tZ;Jx{%9!3N&)BM8W)~FT3!SEkNGCK`Lmk;c0J%;~v zV8{<2T}N>(HzE9S6(Tq-{FxbGLOf@O%?>-?c$~9a%7fjGu5nS*-Uh#D`1j(4BQ60& zhoCNB@{=Oq*2Ur?`o_>dj%dZyK2pTJX3RN;y-V=UD*!hd7Dl5Ltxg2{z{Vg9j})=> zGnYB^S`zF$Va6wh(M9x*BJ*P;6r#FA)EIsIr4M#&Tqsu()@#KF^->ROzA$;D3U=}& zpp~=ae>gNuvR1V5gXV!Go!z3w=+h{lPVM~5O2T@rsA-h`I{EQ!c%+KPE|-AD#xdfQ z%a~rA`bKhy@x{{b$gXBov|>6iBV6BB5gkr^c}q&fx_QFi$m4NbZ1U@`V3Uwe+`hv_)= zV_C6@k_Q*jGmc~bk|bA%2kc=e&BfwyVLr*}g!p$O-u$yPG;YXUijr5Pl_X*4fui6~ zsF0_rhYZb*#`#>NDba9rm|?BBau>@_qEDHdX7J$qRSu3Wfj^tDA<>GF5&YsLKE5N@ zxDRN}bX?0tvdO1`pz0R#&ZEORH{3?PSc}4imOnE*jNN58;c{}SSDb)f={nu+c{YNj|TAl6nwfGA%@*g6)lhP^G{IKF8mctLjP`vlo+PRieU$~T*M~p zXafACtS~oLSNZXATJ?#m|a6&RdK5Y z4ZL{pCD|uupH>_=hkwX#D+N@WkJJS740|K4cOv^3>I;w%W)g>&V)f@JyCntWYx9?G zLegQ&BbQ8Gl>J9qO$niL4eMDsd7(r@e5VB<-r^eMrFhtm7kLYyCaj;lc&J>nWPIl+ z#{wrEC+|~meEPVg_6^(4jJcBwN7;QwVUiEc#kH|GEV002ovPDHLk FV1iF^ARzz% literal 0 HcmV?d00001 From 2575014a00bacf4d7caea0d6bd8041f6907e4f57 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 17:45:57 +0200 Subject: [PATCH 128/258] Create app-icon.js --- apps/ashadyclock/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/ashadyclock/app-icon.js diff --git a/apps/ashadyclock/app-icon.js b/apps/ashadyclock/app-icon.js new file mode 100644 index 0000000000..3e45d1754f --- /dev/null +++ b/apps/ashadyclock/app-icon.js @@ -0,0 +1 @@ +E.toArrayBuffer(atob("MDADAAAAHA4HHAAAHA4HA4HA4HAAAAA4//////AAH//////6///AAAA///6//9AAV/////////XAAAA//////6AAv//X//////4AAAA//////4AA////X/////4AAAA//////4AA/////6////4AAAA//////4AA///6vX///v4AAAAAA////4AA///4A//6//AAAAAAF///64AAHA4CFX////AAAAAAH//X/oAAAAAAH6///4AAAAAAH////AAAAAAAH////QAAAAAAH////AAAAAAA/////AAAAAAAH////AAAAAAC////4AAAAAAAH////AAAAAAH////4AAAAAAA6v///AAAAAA//X/9QAAAAAAA////VAAAAAA////4AAAAAAAA////4AAAAAH////4AAAAAAAA////4AAAAA/6///AAAAAAAAA////4AAAAA////6AAAAAAAAF////4AAAAH/X/94AAAAAAAAH/X//oAAAAX////AAAAAAAAAH////AAAAA/////AAAAAAAAAH////AEQAB+///4AwEAAAAAAH////AigAH////4EUGEAAAAAX////A0GA///3/AmEUigAAAAjGMYxEw0AxxGOIGg0w0igAAGikUwmGmmgwEUwmmGmk0wwAAmmmmmmmGmmgimmmmmmmGmgAGmmmGgGmmmmAmmmmiE0w00wAE0000wE000wwmmmmiA0000wAE00U0AGmmmmA0000wE0U00wAAAAAAAmmmmmAAAAAAGmmmmAAAAAAAE0000wAAAAAE00U00AAAAAACk0000QAAAAAmmmmmgAAAAAAGmmmmigAAAAA00000AAAAAAA00000UAAAAAmmmmmEAAAAAA000w00AAAAAGmmmmmAAAAAAE00000QAAAAE0U000wAAAAAA00000wAAAAA000mmiAAAAAAmmmmmiAAAAAmimmmkQAAAAAGmmmmmAAAAAE00000QAAAAAA00000wmEwmAmmmmmmEwmEwAE0000w00000wmmmmmmimmmgAGmmmmmmmmmGEw00mmmmmmmgAGmmmmmmmmmGE0w00000000AA0000U000mmmA000000000wwAGmmmmmmmmmEU0GmmmmmmmmAAAAiAEACgAECAAgCEAAiAEAAA")) From 6b66123b0ba70b090ae3bcbfe7dd964e915ff04c Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 17:56:20 +0200 Subject: [PATCH 129/258] Create metadata.json --- apps/ashadyclock/metadata.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 apps/ashadyclock/metadata.json diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json new file mode 100644 index 0000000000..749ba984df --- /dev/null +++ b/apps/ashadyclock/metadata.json @@ -0,0 +1,24 @@ +{ "id": "ashadyclock", + "name": "A Shady Clock", + "shortName":"Shady Clk", + "icon": "app.png", + "version":"0.01", + "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app hex values.", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"ashadyclock.app.js","url":"app.js"}, + {"name":"ashadyclock.img","url":"app-icon.js","evaluate":true}, + {"name":"ashadyclock.0.bin","url":"0.bin"}, + {"name":"ashadyclock.1.bin","url":"1.bin"}, + {"name":"ashadyclock.2.bin","url":"2.bin"}, + {"name":"ashadyclock.3.bin","url":"3.bin"}, + {"name":"ashadyclock.4.bin","url":"4.bin"}, + {"name":"ashadyclock.5.bin","url":"5.bin"}, + {"name":"ashadyclock.6.bin","url":"6.bin"}, + {"name":"ashadyclock.7.bin","url":"7.bin"}, + {"name":"ashadyclock.8.bin","url":"8.bin"}, + {"name":"ashadyclock.9.bin","url":"9.bin"}, + ] +} From e8d93fce5db37604b39b19c5ab53d526d54f9707 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:01:01 +0200 Subject: [PATCH 130/258] Add files via upload --- apps/ashadyclock/screenshot.png | Bin 0 -> 956 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/ashadyclock/screenshot.png diff --git a/apps/ashadyclock/screenshot.png b/apps/ashadyclock/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..731055915276045df41a9bf0604c21aa887e548a GIT binary patch literal 956 zcmV;t14I0YP)Px#1ZP1_K>z@;j|==^1poj53{Xr|MF0Q*`26_z008*>03D4q0ssI232;bRa{vGi z!vFvd!vV){sAK>D135`VK~!ko?U?~`vmg+K7q|f2fEg~}aR0;ofOh^gS>^2xsLiW+o5QQsU~_!6 z`7>a;0hsOu4r*}t60pAk*sH)deBu2)$Im@zKA4WLem&-SDLO0zgWtLS7#!4K5`q0L z*qjCSO0elAp#{H}G>PlM(j<%)OhPbv-88Tt1rC$I;w0vQ!3J>>=rW!KmM5VE`%&O9 z39LxMoCW5uQY3LLSdoMd>_>qWNnp1MMH0|5HnYH*BrXN3lF)!5!$?yY0VV)UgTUr2 zupb2ulfb$pfG1`Q*!QLVEHDiMo3p@v6j&Lt5ratw?p`nqtV}`%xZGc9i1+QtLIFM2wWTO|6!Ski)`5w#pI$0 z+?t%@!R63m*S$V05^%A+_7WR$Ox0?l)Qm@o<4Co$&B-KDvMm{xthTHr8RxcJHVIeV z#wOg1M?n;>%~^IX2}@13d|GN``zbTCW?Qzhvx{xDTDF>XeTXnP{mjWmY}cIfmRH$v z$7@?&*u_@7ZgyRjuU`{bu1;7#*t5Ba=Bix7YN>ISxr2Fas_i?XRkm7fu2XUhm>VOm zO?-!Ks_V1czN(M3=kB#$zOR#RF88a2#tqh~RlQRSW4Kq4`lsWB^jJv#?U_KP?5ihv z#MN<@J7(;C9&(@dT+cCc-hDW>pXwxZyfCqUI^J<~%C;iy*cv@D2vk{eEH z^{Wg=L_Nz5XSMoc_*BCQX|Zy{Ov{3(ryCj8s{bknwPd+rsKwpd@B*p#xX>cP)}x{& z-N*13tcWp&pB1sy7p#cq9+WtBlWBIM_yuw#@^idRe<4)zPZzhm~)(GIZm@2 z&&gQ#o*OnKl$UrJs~jWCID)Stg=06%@uZAZFT82DifNAf9nY=eR|}*rcC34Q9{V2z er2f=dMf49EKbfC)7-Z4_0000 Date: Sat, 29 Jun 2024 18:02:06 +0200 Subject: [PATCH 131/258] Update metadata.json --- apps/ashadyclock/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index 749ba984df..e528924ccc 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -19,6 +19,6 @@ {"name":"ashadyclock.6.bin","url":"6.bin"}, {"name":"ashadyclock.7.bin","url":"7.bin"}, {"name":"ashadyclock.8.bin","url":"8.bin"}, - {"name":"ashadyclock.9.bin","url":"9.bin"}, + {"name":"ashadyclock.9.bin","url":"9.bin"} ] } From c90b7c4c0f61843f293a3c5d024e7801538659cb Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:02:48 +0200 Subject: [PATCH 132/258] Add files via upload --- apps/ashadyclock/0.bin | Bin 0 -> 3675 bytes apps/ashadyclock/1.bin | Bin 0 -> 3675 bytes apps/ashadyclock/2.bin | Bin 0 -> 3675 bytes apps/ashadyclock/3.bin | Bin 0 -> 3675 bytes apps/ashadyclock/4.bin | Bin 0 -> 3675 bytes apps/ashadyclock/5.bin | Bin 0 -> 3675 bytes apps/ashadyclock/6.bin | Bin 0 -> 3675 bytes apps/ashadyclock/7.bin | Bin 0 -> 3675 bytes apps/ashadyclock/8.bin | Bin 0 -> 3675 bytes apps/ashadyclock/9.bin | Bin 0 -> 3675 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/ashadyclock/0.bin create mode 100644 apps/ashadyclock/1.bin create mode 100644 apps/ashadyclock/2.bin create mode 100644 apps/ashadyclock/3.bin create mode 100644 apps/ashadyclock/4.bin create mode 100644 apps/ashadyclock/5.bin create mode 100644 apps/ashadyclock/6.bin create mode 100644 apps/ashadyclock/7.bin create mode 100644 apps/ashadyclock/8.bin create mode 100644 apps/ashadyclock/9.bin diff --git a/apps/ashadyclock/0.bin b/apps/ashadyclock/0.bin new file mode 100644 index 0000000000000000000000000000000000000000..0be5abd1596413a8ddaadb0b8cfec52121c4f807 GIT binary patch literal 3675 zcmbVPK}#D!6yB94>ZSH1F|=SyjP&YxQxXD&oKz^9#X}4vusJDsX~8XvhrPNa7=s7~ z3iVgy2PneQLl5nt>%sm3mv-x|Z!$Zx?`0`S{r6a_ts3D? zw>s^=r-2=fqjxo7S#`k(eGxZm8fFZ_p&~VuzpTyb&S2in&CO`qfeLEfzSUprx|7qM zutBv?pNC0Ql_8FxOKRBTteCT&)lQt@NC|7 ztl~@uh!r97^TPqkt`vRu%+}&k#x((f-FRlktGD|*K}%$}YW&Q1+_TO{h$xwpJN>i;OvVU(Gt*~)%|46vNM?5)UXMXI6uBOTq5|u-*@l0qqG89Bs(y))QN^YI zDlYg+^j!tQkWVBf;~s%r`Y#dYp=hUGu!Z=HqR$9+Pqb6jM|>`f@GE*eHm-Pf_79ZT zc0e+X0=@>SjVOgAQ%g`f4G0H9Nml6qSNoVyl2ux_9}p#Q0FODRg*%AWk-Q%dnH zpfZ7(lE9kngc9LE3}K>7Xp5?4LR&PZbV|!YDDA~&;t>v@T%sCbCR1e6Pn0`NCU<<} zQXEn(%GXdz|E1j6a8m9;9uwtaLu=T$A538;9+fJw&A*e0I1HpX5Y_yha$CGggaeof zRSjR$ZD98pQJIV+rI%>q{!1n*N@D5PgTI@8=$dRd#7B$e(Zx}7!KS_T*JT*u2V(kZ~P6pFQd8u literal 0 HcmV?d00001 diff --git a/apps/ashadyclock/1.bin b/apps/ashadyclock/1.bin new file mode 100644 index 0000000000000000000000000000000000000000..54fe59905e461db25a98d9a3492783faba668d5f GIT binary patch literal 3675 zcmcJSziZn-6oBtsTXi7Ro>EyYR8S`&Yi6m$Wb%|PvFKnMY>=l6fkGh)B7@clh1kXz zYAC3GMIdWRK}!p5hj>Z$Vzm+3dXi#%KZyt}96}10ci+8xpPuE_tKNSZ+QVVrm$Wcg zj7tv5Y{!bG_+aNOkHY`}pyt3!c!36Z5^T~t(xWQ?o=IDI(15lK?Mhz1rRP+&UqF4A z1FBqz|l7jAw2OJ~=x)p@fwi}&t1>Hl7K?+oL*mI6eX;f4dQ$l#JH2UCHqZ_AI zaB5xKX!qr=)99@1P3cUIXUA<{K}aS-T2q!LLo;NNj!=YJyAM&Qf@>7|m6A214sJWx za7If$8cNScY{Q;kVvzLTUDs&4MmLUzt|(f>AZfEv&nJc%UQ?MIOn2jd$H%@!&GEJa zDo)QoVUhmeLE`(f=m(Xhb?uhmL17QGAT>Ei8|2n{pLm)iu3@`7i|E6xro!UUSR}8m z$J*X367`nRBu&~F&-q6dDM?842tzt&k>XIO46qGqcT^_RN1a6K&YR-_;b!PLYjz>u$pAeO_2f}Lpo%- zNwPYL)TFyfN;oEwHmUq1kEc^y6Co0zi3ll!xs)c{Op-`KO$a2uxfZU8=w(8@E!l2z zpQT;w;MfM299$Mn>I9Nd6Z{1(&?K5-HQ5y*5t=aFB&|2XT(-d_XOu;gDuKi{xFnMq z&Be0a7a@_FTytxQFqds`_edGcZCV%-r@zkJwrFC4sde?4bD5rWbQ+sIq?=2 j-%9a!6Sb-NV-x>e;&&6TzKm@dzn=$5Y{KR|=I?(2207P9 literal 0 HcmV?d00001 diff --git a/apps/ashadyclock/2.bin b/apps/ashadyclock/2.bin new file mode 100644 index 0000000000000000000000000000000000000000..a95c74b204245656d57e14a9dbb61b8f7b9f6463 GIT binary patch literal 3675 zcmb_e&r2Io5O&)d%qd=~F?h%!#`fyT!$um!Lr%6(Y8DT1Aq$(62QL;(i7DjTptzAG z)PNBG3c;f-!nTJV?7^kf-m6Ppymj`+dpq-XwWU-Z%UgN-<(qG2zIi*9!p=Xfu466S z|DjUo*-h83?|!)ZCpIkE3D((MGTO|p2g25QJ&|qSIT_hLm5aIQwS{WS7@ zyF23lJ$pVsX(zCfe`cgkOB(F!#hf(APD@sdmF#YBPD!om+IaR+N?IGQ*5@bBMJV~| zL{GJ)R%~e zz~04tS?ziVY8L=uU#(9_FPb+9u zVM;ZCm@?XZSwosHo<1qI)nQv5m}y@n)qkenVxgLkwGMDlJSd~8bzuXquyG@yGWlUn zFPiGm)Kl=>Z*)v})r;*d=aNNNa=#d5_#E`nd@`CbrFE;CQB|AQEIRqxOvC2`6PAZo zY&V9v#t`=JcyzlL3yNmMS6MSwSQ&ss@Ky41cttgDlW=@wF1RJ%g)=l|Clnk)i}MNo zvucNe_noiFd;tHDQL4ScIIfLhk68^^PZ6ap8Yxq$WhR{JQj-|J;&kDgEB z(DnpB2@)jK6|YIwcV3D1Y*JUe0PvX|qL|6mCTEsu%dGRz!bb29TP2(|=v-=p{uxrA zinfQ$ghORWF`#xsluA@0aDcGg(3x48ww}%k37ruoLRsT{3SCk~Ufae)gDHutNYRcH zLfarM+(70QQCcR&=}D9jnjkee(q)7YnkubE&m`j#iBjuyD*2c*g4EzDvERxNOMx&e?5N#WdxUq literal 0 HcmV?d00001 diff --git a/apps/ashadyclock/3.bin b/apps/ashadyclock/3.bin new file mode 100644 index 0000000000000000000000000000000000000000..db3821284b5eaeb812807b513dee48f747394279 GIT binary patch literal 3675 zcmbtXF>ljA6efs5J%tHMry`}TSUbB2R0%0lH%i3dT56X5arqDXjclnN(`Dk!St z{t6aY03mr|paWjYj$&FX8}FRFcki7YghX|U&WZi`d*6NUyKBE*>;LoUrLkQ)J~I%< z&yv!i@0p8}#*dNAaXH8od2hyfD}H!7Y^^O@LD!X&#deH!ZT`M7=g;-8@pbmEavUqT zV^3zxkCSJWO6SS$-KAT%jmiB4DYfI>m8HeoH!GcvF|pFGNXfri2%fsV7kl>}7?Y+y zo>5x2gC$j3r@Ol%xpj_+QugDXV&e9e_9P{@mnA58`%`NT5V~G4a--#Z()Dt=ZZJxd zDU*esrc?P}_%f}wjlvVtltT$Y55N(iOFcU!q!e$Kfhgp2 zqO>}rq?r)f`wYA3B>hs#2fnl!CEae*G7)o!JXv4YZF?#;(p6l#3`NXRkXpB?8!@+9oAX?4Y2Mx%#;Q#s>4Yp*EQ6Err{>>OlOL z1))m%_q^cAZU0IN-e?xrZr${DG-?&98<-wY_BN~Aif!_vg;4@!@FXyX0+~wGbiI%4STd>c<PbK zn$G4i3sCHmyfhH824%98+s6$~G{{?#PI@;H9Ciey*GLKWZkE_UiCt3j4n$%DC6+3* z!L@nP0ws1y9k=cCWCKbpFy~M@;|9B}x3b)x64S?teqAWcpkNVkyMY#y zyFam_(jZCZolN8&S6rMNmw&nEKY#iIB=d^L literal 0 HcmV?d00001 diff --git a/apps/ashadyclock/4.bin b/apps/ashadyclock/4.bin new file mode 100644 index 0000000000000000000000000000000000000000..b59e41a0dbadabd7620d0a4f508d4529ab07135e GIT binary patch literal 3675 zcmcImK}#D^5Y9@Inv*>g(NJg)8tXOZRVo2tPr0eVLn0)QoP-8?%1V%u9t|5@mk=-@ z#9z_7ErmT5S}#k%-iwPa-a2oyZ+GT7As$+I$gIguzVFR^-;8CfSpVVJC?}5bZ}ogv zPYw^G@0kHQ_b6?gs^?$5ntT4{eRT8(E>+2V`Rqlint2zVU9TO#j)Da}zm)AbsgyIH za#Af9eH7>c}X25_`-uI=A2-!aTPt-Dq42vm@;VshUBYB_C`tLAgnjFY`^3*1cx zC*7?BTdOT6IvBN&qGPb7aR z-)X=>QmOd_AzXL?`k|s~1S1qh1oagO7hJ(f9D3abei2`njKL5VsQj^BCb5_-lp1c% zDBkr}6!|}f)CBvb@|Q4hLIH_r1eHwY1V~CF;S5m>LRM~DzMgOtw^YHM!{hi@XUqZpiA@&Dv<;_CwODo(;d zY6GPB6+Q?d&FV=I?`%jQUOJMP!U^#f@%k6Wc-UQt{}iBb#2CROcMw70nJ_o$CMjM- z9M9n~94=}qOcs>eXVwNvmS0g6vg{mJ1jsLhmO-4HA9ltXw!0}?6F@?D&aV~0a3?r9p@rTRJ-LynD`-9lhlI>rvJMZ|U)#AcVe| zai|An;t23Zd07TX3@)#^o~|0VSKYnsyfsP{8~tx`UCj^Gm5e{boJr>1+u?GYy!Bd} z$B(Oo&vxzx#2<)NJ{5^wc38OwPw`}yUvU1DN^ct zd05)cc77Nw4GL?NlZ!n=l`6jsySQfMa<-r(ieX)HvqME`Htg;dd+xU3jtn;{tcl?Q z;MEFJy7Z5LNewB9qP-b&YZOeWWdDnCJIRY1W3@-1d?fp*wReU+U_Qf&jPX{-s&&S7 zI74lSqS3LCQbDsOz$fpn_FUiw^(22gX+dF42Ej}GxJzG%(|}_Q?zN-yxF!m_-c*#n z!o7HG!|grmO8g7OXHQZ(H0PbMz=3o6_!P>1D`*Lh0+hudkeeI&iwO8ha}d+M4xW%t z8mD*Xy^H(p&|F}GIGqPYsggyN^s^}E|&@uW)s|f33!Y>pi$?hDajt-&K+~{Q6****mP`f zfvHqtl%S>OM5sii)N2VfvTDn8HWHzdjbm>n`;3yBxutV-J-Bt=VU%#dl}Io~c}s-q zVtZc_9Dri3IYsmKQ*r%7_D)esao|ZV+nYJJ8-I0q61Pk;T$oC{y&v#Bkg25fi3(+tzvSCk^`z5h z%b`#pK1L;*0*sPs0KLQp6KSL#$+wi_2o#;0Ss{=t&4KYv-zbRSWC~=a_dtabB;n!m z)t!*R_PC`W+YmqjS@ z!vyzEOF_Z9(l|g2{F?-(cp1R_r%wbZu)pw#0P)RPgMO=O76o>Sb1Z^h3L=CEa4^LW Vk+Xt=0?^?1$jh>?=w<(3{{hn|y?p=x literal 0 HcmV?d00001 diff --git a/apps/ashadyclock/6.bin b/apps/ashadyclock/6.bin new file mode 100644 index 0000000000000000000000000000000000000000..d45f451b6543db8b172c4c2903c7e587941dc55a GIT binary patch literal 3675 zcmbVP&ui0Q7*40H?ZtVTT@@TsJG^>MWn&=Zw2Qcf9oj$(?a4#0i-d~R(^fi~l_IP% z$o`65JUJ1<4j%LpLB*>qYS_&;KfX8bn>Ixpp=2Z9ljnWNjrfRsSRe?AX%JVt!?m z&n;vOqrOtg7$eF3##!lK&wFc)+;XdQ`)vQv!(k)meZ2~yOwlQkmU3?fbzvnrR)-`jj zu@q5i0x@%08{YgBzfN}FgE#;JpREd9rNU%fn2hqo3@=62HrsO={3@n=*U zM5v8-_pODtH7;0P_-tFFc5m!o)*UjP{=2cHX8S5Y`K3b9cNcewU!ac>0Y~^1112HymnFW+OsJHfmsujHthRT2odIqEd;N6 z-^Ictt07X7|0J0pw_RbU1Lp~)^qZog2Rb$`gd6#Sz@DH=I$gWv&`F0PETP(BxyvR+ zl@1*`n$9Lgl{)VUr6We^IHq(-#_<@X)0vc7oRTbB#iNv-GD@-(YKNtSpCY`ARbl|3M4^NM%HBt!QVseI1P@y7tb8CklX>#Q4y-Fm08_kc0Ck<>Nl!=wuX|0+gE(+=jNS%H)I4O&X6)@jdQ8yUb111# zJCOo+=^MkNAOYV-tnjHI$ zB^Nq`ZZ2qVmfTa1QNqNr+J|x@w#3BUqvioh)DK%ubfRG26i}k!6(KYpX(P; zfN-UkNgMnSMS!WqV`6$UDN#PwxFz=7^nNo{jETZjA`*?;=*jfT^~>ZRP9|6;d>2Uc zTeQIq6qYzjsU_3~Fs*KxFgvdTOV3%)PB9elqmDLCDb zZcHiDK|w`k^L?U}4_WX-s05C0CuT*T72qWQ7vKjHfC3zG2n^okzp=xE25ux$hm-~i zoV`gv;Cw-Fi!2`+klW-34K|Yla&zOeO IPtN?yU&?u?p8x;= literal 0 HcmV?d00001 diff --git a/apps/ashadyclock/7.bin b/apps/ashadyclock/7.bin new file mode 100644 index 0000000000000000000000000000000000000000..f847816ab1b56f2d4f001f154829accd5dac2e48 GIT binary patch literal 3675 zcmc&%F>BjE6t+z5+LJT6vFYH!aoV-BQerZB%9hAvumv_)Q%bW&L71YBF%FT1FvJj0 ze?@*kOF>-P zueq{elukUlT~Ajeqb*Hpp%tjFJlIeq{VvSmp4VvQ`>niW82!d(sR%xKN@l)ae9a(j ztzH#(RdaVxTw7HFNP}X#UjXG`q1jcfnmXwY zs%tr$YYS)80x)%W1xItwpVMm8e-14`q715C63Xfh*FHFf%Lo$5kE6Mc>N*fgB{3b% zbjq&%IJgbBn|-aqw?KzHL!-W_^eCiB@Q!?#Xm30Y>7GZ3JC`e($!$3?$oj|AfFAD3 zXGr34tZo)a*WR-*OZ$2{q#Fjw+u@Li?hsQ}lg-sf4vFY4%GJ<1WHOlqUs#H+K*CX2 z*BP+_nD(M?4?)7;cv!1@tS{-|3f}^xrI7h3+s!CTJ%Dr|K)PIgrkR|ePJ*ei0+`}v zB4+G}QG-(9p`LdHc#yh)bpQ%J+^xuG=`RG-(0)Wl+d<71r!#c9e>F#x11)=7Rt&0?}eYo>eNQ(m9x)JQkr z#x#Ob)WOo@8o{umq9W05Xy3+bggReSW1~de?jo)cOezeBsYMz=+wCBYq*1#|)`$pc zhBU(EkATG*oymLFJw@OGdv%ipkCpIE8Xe4?1 nXBv@sj-2zm7Ag{aIZ2j3j5c)7LCN9KU?d6uG!6Lg|I7IWvR)w? literal 0 HcmV?d00001 diff --git a/apps/ashadyclock/8.bin b/apps/ashadyclock/8.bin new file mode 100644 index 0000000000000000000000000000000000000000..deb47c3c03a5951ba918b96d688e42921ff454af GIT binary patch literal 3675 zcmb7HL2KJc6t-MjZT4bc8f;(cgJaWc&q_%!J@}Lqmd2t7%V2?Y${|o#uojVnt_coN zf(azuK=LaLd(E<-?O_kwix1nf*Ai_ja_bvKGw(geT38r_Y&4&~_q}hv8BKbv$^RcO zdL1MFH2LX2=8jJ0ULwXgAHQP6ksSZzJa!KvuQ$uy*o~US$5ylUL@#RZe{Jslz65H^ zD{NOctZJ>KYX$w0UrruEb)&+UhUWW@-q3=g)qGZX2({%s`lDJK9`;LBt)y>^bZxu* zZ9zbRQ*(4*^G{cTVhZ@I0CKU%yuUyA{ZgS1l&sZy+3&-6?cK6xD$OtZjy|>8`@@T= z7WhuP@kIBHJ1WBZzpsOhQMGPW`*1Dko7So}Jp6IMWHZC(`1gXJG=eC1scui@ZuCGxxv2GE(%susKKlLXw|fX zdO6%2hBZ6fv_ljA*(KF=qvsa@GIg&`-K(hs1;S0xaN0Feo%eMZns!((2WB}CAU50p zt8fQR)iK_Q=>U?B24Fhv6!tQbg0CfWfg^?>ChyB|GgCG=i$6+`dnmjbu~cB-;dF(Q z-n>Oh(esQ9B4o(d(aTKm_2jdp!EsFcSW@a3w*=ujqV&d+($V}4p)^bWB(TZ@dvxx( zK`XtEqzAmOgbA!%0!t{7(wsP2CqKv1;U`LM0NnK`B~)qeH&V_4qjZUs?ii(?2_@CI zjzKs8B`Uu15~DQmsQ5ecQRWj$={jQK$I43{p>&+g2_>|5g;E;FRQv$MJJ>H)MmE;q4Qm@O4lqPmtYsHN47W3~|-F-vDc?aWCi0aQ1lB4^db%8+SawwQk; zr5ScD4+nw{jU-CTQHiSEF}|TPMD26sZLmSYLI6EL4Ung{iwV%0`VQRx+iFiPT$r=r`1xiY#GrF4qrDcud2>^#K^b&}lHI5kob`!da z6(wc>SY>yZC+H=%Y%f``kPVJxENqUSkrLXQQR3?bcN3-(no1CxseEH95sBO@Gyqo# z`2gk)9+p@+q-ziYDwpzdGBgLulaP}i>2$ou(pnQ{Z%RpRln^*D?!FysyL}kw9W)J+ir%in#yc z##JO9K&2-20lAdq3)Nyhy@gdXr?ys@D_h{4Hs4PaK#>VbqboXKMLHyb$AmA9r z?n*4$jzwXTNk@%AW=f%odslJ5NhIU#oN8{z4Tia?Ut*|+wFDsJL3PEbCV&!5V!N4r zg=nQazZO%fnYx$A0SHh6G|hNf))Dgs#Td>TgK8$Nj$r^!vYIfePp#+jb6LlRjb={k zr{aAVq@B~+CDnXNl*GehCoNK0pC1a093vyTYrp*4f4u zkg2r+iVc8blaJP#S6$Y*0FXWdVQ6xswD-aW$|c(vIGWs12liXMoy^Nkk5a=51Yv(X z5tQ^lhz7yYdYsaN62b?dnp~wrTb5JOPQ6NJ;GB{<{e~!wl<80TQ&0l65v46#N34P= zRi`uYJ<&L&tn&j=s_W#Zq=Yz3IzCFCEsK~#-y!0ka+L@NptOoa<6ioQ(uHKWV|xd8 zL}ej<5iU9HoT#K#iS1N~kV&e2(o=A}XQN2TYku z9Y0 z&ILV*2SO+g zDIJ74tnto;m9r-V{~s%M$~zZI;|~&df>jcffDZ>@6P)C-b8+Mz2X`(=gk*s*!IXIC z7D&E9m>^1`bEzlGLN4Znt-v;cFhOe9Lc;`ksm?pMK#Zi|N@eHrJ9pL~Wn5`U=bn&C zt9Q9`@7B3@2$P<}A34N4SR!=icEiI2Vv~E4sDw_9d2-ErnS{5SCST6vVj`4A@+nBX zgia0RWVb*M9YM)ZVr@dU3yX@25(+el56P{>OA8N^y6`sZ?qI>epWdy+)*Q$Lz}%!n zu!dEK??8v{P}FxHv8p630C|c%m>Sc>DteI0|0C<_*dU>f**mD-|3g zV4(mU(EKxtrd37^o^UX~j}Rm=98J!7C2&nQ|4ank+8epoxA@@9&CIi95s$tFxBvVL DI0mlD literal 0 HcmV?d00001 From ce2e7c1c48aa92e07014c5894150531cfea5c27e Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:04:56 +0200 Subject: [PATCH 133/258] Update metadata.json --- apps/ashadyclock/metadata.json | 35 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index e528924ccc..cd1bf9d4da 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -1,24 +1,17 @@ -{ "id": "ashadyclock", - "name": "A Shady Clock", - "shortName":"Shady Clk", - "icon": "app.png", - "version":"0.01", - "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app hex values.", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS","BANGLEJS2"], +{ + "id": "distortclk", + "name": "Distort Clock", + "shortName":"Distort Clock", + "version": "0.03", + "description": "A clockface", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme":"README.md", "storage": [ - {"name":"ashadyclock.app.js","url":"app.js"}, - {"name":"ashadyclock.img","url":"app-icon.js","evaluate":true}, - {"name":"ashadyclock.0.bin","url":"0.bin"}, - {"name":"ashadyclock.1.bin","url":"1.bin"}, - {"name":"ashadyclock.2.bin","url":"2.bin"}, - {"name":"ashadyclock.3.bin","url":"3.bin"}, - {"name":"ashadyclock.4.bin","url":"4.bin"}, - {"name":"ashadyclock.5.bin","url":"5.bin"}, - {"name":"ashadyclock.6.bin","url":"6.bin"}, - {"name":"ashadyclock.7.bin","url":"7.bin"}, - {"name":"ashadyclock.8.bin","url":"8.bin"}, - {"name":"ashadyclock.9.bin","url":"9.bin"} + {"name":"distortclk.app.js","url":"app.js"}, + {"name":"distortclk.img","url":"app-icon.js","evaluate":true} ] } From 250b6a6b26d485a0c1f141b0be4945ff0fb96838 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:05:14 +0200 Subject: [PATCH 134/258] Update metadata.json --- apps/ashadyclock/metadata.json | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index cd1bf9d4da..e528924ccc 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -1,17 +1,24 @@ -{ - "id": "distortclk", - "name": "Distort Clock", - "shortName":"Distort Clock", - "version": "0.03", - "description": "A clockface", - "icon": "app.png", - "type": "clock", - "tags": "clock", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "readme":"README.md", +{ "id": "ashadyclock", + "name": "A Shady Clock", + "shortName":"Shady Clk", + "icon": "app.png", + "version":"0.01", + "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app hex values.", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"distortclk.app.js","url":"app.js"}, - {"name":"distortclk.img","url":"app-icon.js","evaluate":true} + {"name":"ashadyclock.app.js","url":"app.js"}, + {"name":"ashadyclock.img","url":"app-icon.js","evaluate":true}, + {"name":"ashadyclock.0.bin","url":"0.bin"}, + {"name":"ashadyclock.1.bin","url":"1.bin"}, + {"name":"ashadyclock.2.bin","url":"2.bin"}, + {"name":"ashadyclock.3.bin","url":"3.bin"}, + {"name":"ashadyclock.4.bin","url":"4.bin"}, + {"name":"ashadyclock.5.bin","url":"5.bin"}, + {"name":"ashadyclock.6.bin","url":"6.bin"}, + {"name":"ashadyclock.7.bin","url":"7.bin"}, + {"name":"ashadyclock.8.bin","url":"8.bin"}, + {"name":"ashadyclock.9.bin","url":"9.bin"} ] } From 4c0d309dd8d8905689d1b78de81be57315febba0 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:07:16 +0200 Subject: [PATCH 135/258] Update metadata.json --- apps/ashadyclock/metadata.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index e528924ccc..b0333deac8 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -6,6 +6,8 @@ "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app hex values.", "type": "clock", "tags": "clock", + "allow_emulator": true, + "screenshots": [{"url":"screenshot.png"}], "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"ashadyclock.app.js","url":"app.js"}, From 808cf03252d74f21232a1ea9fad5c5c77f3e4704 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:11:59 +0200 Subject: [PATCH 136/258] Update metadata.json --- apps/ashadyclock/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index b0333deac8..da06a8768e 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -3,11 +3,11 @@ "shortName":"Shady Clk", "icon": "app.png", "version":"0.01", - "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app hex values.", + "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app color values.", "type": "clock", "tags": "clock", "allow_emulator": true, - "screenshots": [{"url":"screenshot.png"}], + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}], "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"ashadyclock.app.js","url":"app.js"}, From 7165c504e7fe69f03cca1c16174d742dc347b276 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:22:32 +0200 Subject: [PATCH 137/258] Add files via upload --- apps/ashadyclock/screenshot-1.png | Bin 0 -> 4405 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/ashadyclock/screenshot-1.png diff --git a/apps/ashadyclock/screenshot-1.png b/apps/ashadyclock/screenshot-1.png new file mode 100644 index 0000000000000000000000000000000000000000..31a46610438be189c1ba775966b8b7935d878f44 GIT binary patch literal 4405 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 Date: Sat, 29 Jun 2024 18:40:23 +0200 Subject: [PATCH 138/258] Update app.js --- apps/ashadyclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ashadyclock/app.js b/apps/ashadyclock/app.js index d7ff795943..b0101dbec0 100644 --- a/apps/ashadyclock/app.js +++ b/apps/ashadyclock/app.js @@ -14,7 +14,7 @@ for (i=0;i<10;i++) { { width : 98, height : 100, bpp : 3, transparent: 4, - buffer : require("Storage").read(i + ".bin") + buffer : require("Storage").read("ashadyclock".i + ".bin") }; images[i] = image; } From ca2537d894d5e76dfbc19a15514999910bdc5c1c Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:43:49 +0200 Subject: [PATCH 139/258] Update app.js --- apps/ashadyclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ashadyclock/app.js b/apps/ashadyclock/app.js index b0101dbec0..b86cee52f3 100644 --- a/apps/ashadyclock/app.js +++ b/apps/ashadyclock/app.js @@ -14,7 +14,7 @@ for (i=0;i<10;i++) { { width : 98, height : 100, bpp : 3, transparent: 4, - buffer : require("Storage").read("ashadyclock".i + ".bin") + buffer : require("Storage").read("ashadyclock.".i + ".bin") }; images[i] = image; } From 55735966c0677575721a017ac2763d67c7390fd4 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:48:56 +0200 Subject: [PATCH 140/258] Update metadata.json --- apps/ashadyclock/metadata.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index da06a8768e..58006f5ace 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -6,7 +6,6 @@ "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app color values.", "type": "clock", "tags": "clock", - "allow_emulator": true, "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}], "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ From 283541fa1d3e19da2f7436abdba5a59cd08db963 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sat, 29 Jun 2024 18:54:42 +0200 Subject: [PATCH 141/258] Update app.js --- apps/ashadyclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ashadyclock/app.js b/apps/ashadyclock/app.js index b86cee52f3..474886795f 100644 --- a/apps/ashadyclock/app.js +++ b/apps/ashadyclock/app.js @@ -14,7 +14,7 @@ for (i=0;i<10;i++) { { width : 98, height : 100, bpp : 3, transparent: 4, - buffer : require("Storage").read("ashadyclock.".i + ".bin") + buffer : require("Storage").read("ashadyclock." + i + ".bin") }; images[i] = image; } From 19d94c11d19a052e929918031bd92415ac27270a Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.com⁩> Date: Sun, 30 Jun 2024 18:03:54 +0200 Subject: [PATCH 142/258] delaylock: delay lock 5 sec after backlight off New bootloader app for delaying the locking of the touchscreen to 5 seconds after the backlight turns off. Giving you the chance to interact with the watch without having to press the hardware button again. Inspired by the conversation between Gordon Williams and user156427 linked here: https://forum.espruino.com/conversations/392219/ --- apps/delaylock/ChangeLog | 1 + apps/delaylock/README.md | 23 +++++++++++++++++++++++ apps/delaylock/app.png | Bin 0 -> 1350 bytes apps/delaylock/boot.js | 21 +++++++++++++++++++++ apps/delaylock/metadata.json | 13 +++++++++++++ 5 files changed, 58 insertions(+) create mode 100644 apps/delaylock/ChangeLog create mode 100644 apps/delaylock/README.md create mode 100644 apps/delaylock/app.png create mode 100644 apps/delaylock/boot.js create mode 100644 apps/delaylock/metadata.json diff --git a/apps/delaylock/ChangeLog b/apps/delaylock/ChangeLog new file mode 100644 index 0000000000..5560f00bce --- /dev/null +++ b/apps/delaylock/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/delaylock/README.md b/apps/delaylock/README.md new file mode 100644 index 0000000000..da2ef3cda2 --- /dev/null +++ b/apps/delaylock/README.md @@ -0,0 +1,23 @@ +# Delayed Locking + +Delay the locking of the touchscreen to 5 seconds after the backlight turns off. Giving you the chance to interact with the watch without having to press the hardware button again. + +## Usage + +Just install and the behavior is tweaked at boot time. + +## Features + +- respects the LCD Timeout and Brightness as configured in the settings app. + +## Requests + +Tag @thyttan in an issue to https://gitbub.com/espruino/BangleApps/issues to report problems or suggestions. + +## Creator + +thyttan + +## Acknowledgements + +Inspired by the conversation between Gordon Williams and user156427 linked here: https://forum.espruino.com/conversations/392219/ diff --git a/apps/delaylock/app.png b/apps/delaylock/app.png new file mode 100644 index 0000000000000000000000000000000000000000..7bdce945db62321b6d5053ba6adbfa223d2f14b1 GIT binary patch literal 1350 zcmV-M1-bf(P)M_w3HcA8ZI+fhqAj!RbWX;p(~clE=aar;HfWK&ahAlgQce^C|B$nttw?xljiC!h) zeP()Hh|YsS<~eG+0frCn!V*(~5wq-;7t_}5%Ut3=!GqiS5D&r{BUq@YRC^C-sML0#8$}%#2BD`8LA7wh(4D9^4^m`bh+x) z2FH=Go3PT7s@2Iga3DVt5zqMRu)bI$2YHS)9vCjfoKQ^wmg{@~0mmU&`}q{WY74+W z-+E)UMIMVbPWA-bi0M&HfLmhj2mbT*Ka6pa4jdjU{+Elz;o+WfaOM+*qSpxYO=1~%lDqh-{xo*rNqJ)5pRSeDbY;`L`npoK;N#mYCj%Z`ir)sPO z8Rafu?5YWHAv!OzO*F8+zRx0Ly%bS|Bhy!Xlri7m zj?8Em*+W1FpQY3UmKY&gvPKmU^(JOYDYXG;Uj}K!9$NsSM^nQw*S>7C z{c5uIK&1Gpwfnc6lFI8^R#??>!n7hfA!zNs2k``;tfztmaC|pbSO*!@v@E; zMhwjspMd&Kds*l@S;WSCRkMX8_KoZ_;_ieAAS92o1Mu{uaKXR*H+x~8$!_JT5B+i?wNiDHly9~({WcmIv%`=<80vJj(b z+8t>xAwx(h1TS{=g;ki^HrrmmfUB5@PN;y!w%PI8<{2>5Pk~T924~KVz$Ya`@~J2` z$}e`{(0#-P#1646WU};>2U>{OE}^BA*N>GRFAVm7UXcR|%5TEi^P}+jg$bytn`jB$ zM-ov^VqVkI#t08}DTQ9V|yubaAWGS6VMWzJ`zE8WiJE8jWmMTEk9OG=?q zMvZZn(d?oMSpX@)R5FlLAU*J(1e$-;TKOX5glTd)kxPnPR;FkdS)1*boRaf)9Vh&y z<@g^HdXWX<{X*#tuLv}sQ-F@fT2H)8+5_?|+HB7lE_dpQ&uiPS$)Rs1twdK9nNviQ z_Ji8S*r8+jhyG?xSW0R$mkj7Qp+BG-y^(4@X{3=x8q}unAMPu)uw890ZU6uP07*qo IM6N<$f`CV!Z literal 0 HcmV?d00001 diff --git a/apps/delaylock/boot.js b/apps/delaylock/boot.js new file mode 100644 index 0000000000..87dcbf1867 --- /dev/null +++ b/apps/delaylock/boot.js @@ -0,0 +1,21 @@ +{ + let backlightTimeout = Bangle.getOptions().backlightTimeout; + let brightness = require("Storage").readJSON("setting.json", true); + brightness = brightness?brightness.brightness:1; + + Bangle.setOptions({ + backlightTimeout: backlightTimeout, + lockTimeout: backlightTimeout+5000 + }); + + let turnLightsOn = (_,numOrObj)=>{ + if (!Bangle.isBacklightOn()) { + Bangle.setLCDPower(brightness); + if (typeof numOrObj !== "number") E.stopEventPropagation(); // Touches will not be passed on to other listeners, but swipes will. + } + }; + + setWatch(turnLightsOn, BTN1, { repeat: true, edge: 'rising' }); + Bangle.prependListener("swipe", turnLightsOn); + Bangle.prependListener("touch", turnLightsOn); +} diff --git a/apps/delaylock/metadata.json b/apps/delaylock/metadata.json new file mode 100644 index 0000000000..dff4d92198 --- /dev/null +++ b/apps/delaylock/metadata.json @@ -0,0 +1,13 @@ +{ "id": "delaylock", + "name": "Delayed Locking", + "version":"0.01", + "description": "Delay the locking of the screen to 5 seconds after the backlight turns off.", + "icon": "app.png", + "tags": "settings, configuration, backlight, touchscreen, screen", + "type": "bootloader", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"delaylock.boot.js","url":"boot.js"} + ] +} From 577bc8b192fa2f1c76c6e383773748f2650be3e9 Mon Sep 17 00:00:00 2001 From: thyttan <97237430+thyttan@users.noreply.github.com> Date: Sun, 30 Jun 2024 18:22:48 +0200 Subject: [PATCH 143/258] _example_app: remove superflous spaces in metadata --- apps/_example_app/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/_example_app/metadata.json b/apps/_example_app/metadata.json index e0d664338a..42dfca2b8b 100644 --- a/apps/_example_app/metadata.json +++ b/apps/_example_app/metadata.json @@ -5,7 +5,7 @@ "description": "A detailed description of my great app", "icon": "app.png", "tags": "", - "supports" : ["BANGLEJS2"], + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"7chname.app.js","url":"app.js"}, From ff574897dd1e9a8fde40592787354218731a6557 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:52:29 +0200 Subject: [PATCH 144/258] Update app.js --- apps/ashadyclock/app.js | 117 ++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 46 deletions(-) diff --git a/apps/ashadyclock/app.js b/apps/ashadyclock/app.js index 474886795f..90f147bc9d 100644 --- a/apps/ashadyclock/app.js +++ b/apps/ashadyclock/app.js @@ -1,3 +1,9 @@ +var settings = Object.assign({ + // default values + showWidgets: false, + alternativeColor: false, +}, require('Storage').readJSON("ashadyclock.json", true) || {}); + let drawTimeout; // schedule a draw for the next minute function queueDraw() { @@ -8,28 +14,31 @@ function queueDraw() { }, 60000 - (Date.now() % 60000)); } -let images = new Array(10); -for (i=0;i<10;i++) { - let image = - { - width : 98, height : 100, bpp : 3, - transparent: 4, - buffer : require("Storage").read("ashadyclock." + i + ".bin") - }; - images[i] = image; +let palBottom; +if (settings.alternativeColor) { + palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ + g.toColor("#000"), + g.toColor("#000"), + g.toColor("#0FF"), + g.toColor("#0FF"), + g.toColor("#00F"), + g.toColor("#000"), + g.toColor("#00F"), + g.toColor("#000") + ]).buffer))); +} else { + palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ + g.toColor("#000"), + g.toColor("#000"), + g.toColor("#F00"), + g.toColor("#FF0"), + g.toColor("#00F"), + g.toColor("#000"), + g.toColor("#FF0"), + g.toColor("#000") + ]).buffer))); } -let palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ - g.toColor("#000"), - g.toColor("#000"), - g.toColor("#F00"), - g.toColor("#FF0"), - g.toColor("#00F"), - g.toColor("#000"), - g.toColor("#FF0"), - g.toColor("#000") - ]).buffer))); - let palTop = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ g.toColor("#FFF"), g.toColor("#000"), @@ -41,41 +50,57 @@ let palTop = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([ g.toColor("#000"), ]).buffer))); -function draw() { +let xOffset = (g.getWidth() - 176) / 2; +let yOffset = (g.getHeight() - 176) / 2; - // work out how to display the current time - let d = new Date(); - - g.setColor(0,0,0); - g.fillRect(0,0,g.getWidth(),g.getHeight()); +function drawTop(d0, d1) { + if (settings.showWidgets && g.getHeight()<=176) { + drawNumber(d0, 82 + xOffset, 24 + yOffset, palTop, {scale: 0.825}); + drawNumber(d1, 13 + xOffset, 24 + yOffset, palTop, {scale: 0.825}); + } else { + drawNumber(d0, 80, 0, palTop); + drawNumber(d1, -1, 0, palTop); + } +} - let xOffset = (g.getWidth() - 176) / 2; - let yOffset = (g.getWidth() - 176) / 2; - - let imgUR = images[d.getMinutes() % 10]; - let imgUL = images[Math.floor(d.getMinutes()/10)]; - imgUR.palette = palBottom; - imgUL.palette = palBottom; - g.drawImage(imgUR, 75 + xOffset, 77 + yOffset); - g.drawImage(imgUL, 0 + xOffset, 77 + yOffset); - - let imgOR = images[d.getHours() % 10]; - let imgOL = images[Math.floor(d.getHours()/10)]; - imgOR.palette = palTop; - imgOL.palette = palTop; - g.drawImage(imgOR, 75 + xOffset, 0 + yOffset); - g.drawImage(imgOL, 0 + xOffset, 0 + yOffset); +function drawBottom(d0, d1) { + if (settings.showWidgets && g.getHeight()<=176) { + drawNumber(d1, 82 + xOffset, 92 + yOffset, palBottom, {scale: 0.825}); + drawNumber(d0, 13 + xOffset, 92 + yOffset, palBottom, {scale: 0.825}); + } else { + drawNumber(d1, 80, 75, palBottom); + drawNumber(d0, -1, 75, palBottom); + } +} + +function drawNumber(number, x, y, palette, options) { + let image = + { + width : 98, height : 100, bpp : 3, + transparent: 4, + buffer : require("Storage").read("ashadyclock." + number +".bin") + }; + image.palette = palette; + g.drawImage(image, x, y, options); +} + +function draw() { + let d = new Date(); + g.clearRect(0, settings.showWidgets ? 24 : 0, g.getWidth(),g.getHeight()); + drawBottom(0, 5); + drawTop(0, 5); queueDraw(); } -// Clear the screen once, at startup g.clear(); // draw immediately at first draw(); // Show launcher when middle button pressed Bangle.setUI("clock"); -// Load widgets -Bangle.loadWidgets(); -Bangle.drawWidgets(); + +if(settings.showWidgets) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); +} From b1cae599e41e403fcbaf35aa0e675381dce005b0 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:53:05 +0200 Subject: [PATCH 145/258] Create settings.js --- apps/ashadyclock/settings.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 apps/ashadyclock/settings.js diff --git a/apps/ashadyclock/settings.js b/apps/ashadyclock/settings.js new file mode 100644 index 0000000000..eef57e5193 --- /dev/null +++ b/apps/ashadyclock/settings.js @@ -0,0 +1,32 @@ +(function(back) { + var FILE = "ashadyclock.json"; + // Load settings + var settings = Object.assign({ + showWidgets: false, + alternativeColor: false, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Shady Clck" }, + "< Back" : () => back(), + 'Show Widgets': { + value: !!settings.showWidgets, // !! converts undefined to false + onchange: v => { + settings.showWidgets = v; + writeSettings(); + } + }, + 'Blue Color': { + value: !!settings.alternativeColor, // !! converts undefined to false + onchange: v => { + settings.alternativeColor = v; + writeSettings(); + } + }, + }); +}) From 1331dab998873d8963dd27da55ca76d392683908 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:53:37 +0200 Subject: [PATCH 146/258] Add files via upload From c3cff9957424b90385365299a8d8cffee11b04cd Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:54:43 +0200 Subject: [PATCH 147/258] Add files via upload From da0c4a62343fd5f1d25a3ede6218555eba308cbd Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:57:58 +0200 Subject: [PATCH 148/258] Update metadata.json --- apps/ashadyclock/metadata.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index 58006f5ace..f360b4f907 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -20,6 +20,8 @@ {"name":"ashadyclock.6.bin","url":"6.bin"}, {"name":"ashadyclock.7.bin","url":"7.bin"}, {"name":"ashadyclock.8.bin","url":"8.bin"}, - {"name":"ashadyclock.9.bin","url":"9.bin"} - ] + {"name":"ashadyclock.9.bin","url":"9.bin"}, + {"name":"ashadyclock.settings.js","url":"settings.js"}, + ], + "data": [{"name":"ashadyclock.json"} } From c79a70e1725857f841cdd026a512281fa2dfb4a9 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:59:59 +0200 Subject: [PATCH 149/258] Update metadata.json --- apps/ashadyclock/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index f360b4f907..c74f0ff2fb 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -23,5 +23,5 @@ {"name":"ashadyclock.9.bin","url":"9.bin"}, {"name":"ashadyclock.settings.js","url":"settings.js"}, ], - "data": [{"name":"ashadyclock.json"} + "data": [{"name":"ashadyclock.json"}] } From 8a77119c870f91d321fe8177a0b12fa7bcea86c7 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:01:47 +0200 Subject: [PATCH 150/258] Update metadata.json --- apps/ashadyclock/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index c74f0ff2fb..1072af13f2 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -21,7 +21,7 @@ {"name":"ashadyclock.7.bin","url":"7.bin"}, {"name":"ashadyclock.8.bin","url":"8.bin"}, {"name":"ashadyclock.9.bin","url":"9.bin"}, - {"name":"ashadyclock.settings.js","url":"settings.js"}, + {"name":"ashadyclock.settings.js","url":"settings.js"} ], "data": [{"name":"ashadyclock.json"}] } From 7f616e01d3b2411d8e65efecefc2bb6422ec63b6 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:07:53 +0200 Subject: [PATCH 151/258] Update app.js --- apps/ashadyclock/app.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/ashadyclock/app.js b/apps/ashadyclock/app.js index 90f147bc9d..5228ef9f9c 100644 --- a/apps/ashadyclock/app.js +++ b/apps/ashadyclock/app.js @@ -87,8 +87,9 @@ function drawNumber(number, x, y, palette, options) { function draw() { let d = new Date(); g.clearRect(0, settings.showWidgets ? 24 : 0, g.getWidth(),g.getHeight()); - drawBottom(0, 5); - drawTop(0, 5); + + drawBottom(Math.floor(d.getMinutes()/10), d.getMinutes() % 10); + drawTop(Math.floor(d.getHours()/10), d.getHours() % 10); queueDraw(); } From 7d7e8325516e320b5191e6967bf16a78c4c5f6ab Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:29:50 +0200 Subject: [PATCH 152/258] Add files via upload --- apps/ashadyclock/screenshot-1.png | Bin 4405 -> 1454 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/ashadyclock/screenshot-1.png b/apps/ashadyclock/screenshot-1.png index 31a46610438be189c1ba775966b8b7935d878f44..a0c7ea3be5d87ab963eafa8ead8769aff5995e2a 100644 GIT binary patch delta 1448 zcmV;Z1y}mDBCZRN7=Hu<0002Yd-S0I0004VQb$4nuFf3k00004XF*Lt006O%3;baP z0000LP)t-s00030|Nj90|Nj60`26?){P+L>_zl7#4FCWD32;bRa{vGi!vFvd!vV){ zsAK>D1tLjAK~#9!?V8PY@*og~$L~Pa_6|I~f?fg(*t76t?SCfse+wUwR9l$p*2|Ll?z?)AdHiL- z$1NY1Wq@tvLI(WG1?+1zGI04~%vNLUg@7}DpHGX7U47Imke;5No}Qkbo}Ql*?IPSw zEjO*vXDRoTdxX-BR1v68!JhW?B&FNPVc&HYEL5~}Zhuc2xB@hm5^ft=>k=@|^#_X? zyux*4Vz4UYM$e=Li*;_jatTKSZoCY5qL>4SkZ#TI#}-thAm+A z`@#B8fqz$qejHdgfE6KFCBfoFz`z8K{q!N=D2fXVoM6!a21;;pBcX5$82G@zC!7Hb zHg4`E1=oR2F4fq6k#Vyp*rUL!M_TcMwPqtH{~X69pAzt%>&FH#UCb+?gwUk1M~YYfV|?Bmw!C?4_pBGTUTD-mwskuCu*O6EqY#iZMophg8_^g#mlLh8kb(wTdyuLxWcxy|w${F)Z#)hh)*G_Mx47z?+gc(m zoHd5H>bBL`k7bR4!5H>39*c}+WG7p@+Ef@@#26Ti?Q@MejD1rhjm~fE|K<>l$$u9y z1_onfC~E3pH?V3l~h|R%29nhiQxp^DkTy7v?t}M{E-F z8?OnYZ<8pEN4&!9OSM@OW?s04RexdTg=<}y*LYazXdg~vq@y1f^f-;jgfQn)of76; zxDp)=tP5A7qk+>nEzD<}EHE%G)v1p5DVWAp#yMfGg)38FU|YD-9qq$oJY)(COvc#) z18RJ2HXifB*d?Flrk2?PLsU>pa2>Pc>O6KmU}`ot`NVIJ<80;*MaKCLB!4!8Ws!0A z6Bx^6oc+w{#$*iSB`z|~f67*IYAP|#f67*IG9HVJfx4%QrZIBSr(L+zSW*j@8UwX( z*-{PZA_J3gI6Dhs;bLQ;oWoGOaQUnWS8+>EPGKJj3%tn~FLNcgaH(EG=Uci|e@2k> z^PJ)m&uMSx>QLDByncFpdm(yydU|?#dU|^H4Zwe|whWs94oqkO0000 literal 4405 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 Date: Sun, 30 Jun 2024 22:30:40 +0200 Subject: [PATCH 153/258] Update metadata.json --- apps/ashadyclock/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index 1072af13f2..a9e9555912 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -6,7 +6,7 @@ "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app color values.", "type": "clock", "tags": "clock", - "screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}], + "screenshots": [{"url":"screenshot-1.png"},{"url":"screenshot.png"}], "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"ashadyclock.app.js","url":"app.js"}, From 703010b29bdf74df5b979546d9a47337a9fbc356 Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Sun, 30 Jun 2024 22:31:38 +0200 Subject: [PATCH 154/258] Update metadata.json --- apps/ashadyclock/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ashadyclock/metadata.json b/apps/ashadyclock/metadata.json index a9e9555912..1b92ecabc4 100644 --- a/apps/ashadyclock/metadata.json +++ b/apps/ashadyclock/metadata.json @@ -3,7 +3,7 @@ "shortName":"Shady Clk", "icon": "app.png", "version":"0.01", - "description": "A nice clock with drop shadow. Hours and minutes. Create any color combination with the existing images by changing only the app color values.", + "description": "A nice clock with drop shadow. Hours and minutes. Configure color and widgets in settings. Create any color combination with the existing images by changing only the app color values.", "type": "clock", "tags": "clock", "screenshots": [{"url":"screenshot-1.png"},{"url":"screenshot.png"}], From 6abb02dff19a65a6db1ce11ac23de501048144fb Mon Sep 17 00:00:00 2001 From: Chriz76 <48691511+Chriz76@users.noreply.github.com> Date: Mon, 1 Jul 2024 06:42:09 +0200 Subject: [PATCH 155/258] Update app.js --- apps/ashadyclock/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/ashadyclock/app.js b/apps/ashadyclock/app.js index 5228ef9f9c..8c86ca8d2d 100644 --- a/apps/ashadyclock/app.js +++ b/apps/ashadyclock/app.js @@ -55,11 +55,11 @@ let yOffset = (g.getHeight() - 176) / 2; function drawTop(d0, d1) { if (settings.showWidgets && g.getHeight()<=176) { - drawNumber(d0, 82 + xOffset, 24 + yOffset, palTop, {scale: 0.825}); - drawNumber(d1, 13 + xOffset, 24 + yOffset, palTop, {scale: 0.825}); + drawNumber(d1, 82 + xOffset, 24 + yOffset, palTop, {scale: 0.825}); + drawNumber(d0, 13 + xOffset, 24 + yOffset, palTop, {scale: 0.825}); } else { - drawNumber(d0, 80, 0, palTop); - drawNumber(d1, -1, 0, palTop); + drawNumber(d1, 80, 0, palTop); + drawNumber(d0, -1, 0, palTop); } } From 3211cf6955624ea01913581da005106189f1e4e5 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 1 Jul 2024 09:09:45 +0100 Subject: [PATCH 156/258] clkinfosec 0.02: Remove 's' after seconds (on some clocks this looks like '5') --- apps/clkinfosec/ChangeLog | 1 + apps/clkinfosec/clkinfo.js | 2 +- apps/clkinfosec/metadata.json | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/clkinfosec/ChangeLog b/apps/clkinfosec/ChangeLog index 5560f00bce..11fefd24b1 100644 --- a/apps/clkinfosec/ChangeLog +++ b/apps/clkinfosec/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Remove 's' after seconds (on some clocks this looks like '5') \ No newline at end of file diff --git a/apps/clkinfosec/clkinfo.js b/apps/clkinfosec/clkinfo.js index a23d617544..d407228db6 100644 --- a/apps/clkinfosec/clkinfo.js +++ b/apps/clkinfosec/clkinfo.js @@ -11,7 +11,7 @@ g.drawImage(atob("GBgBAP4AA/+ABwHAHABwGAAwMAAYYAAMYAAMwAAGwAAGwAAGwAAGwAAGwAAGwAAGYAAMYAAMMAAYGAAwHABwBwHAA/+AAP4AAAAA")); g.drawLine(11,11,x,y).drawLine(12,11,x+1,y).drawLine(11,12,x,y+1).drawLine(12,12,x+1,y+1); return { - text : s.toString().padStart(2,0)+"s", + text : s.toString().padStart(2,0), img : g.asImage("string") }; }, diff --git a/apps/clkinfosec/metadata.json b/apps/clkinfosec/metadata.json index 3749b64906..aaf96e9bba 100644 --- a/apps/clkinfosec/metadata.json +++ b/apps/clkinfosec/metadata.json @@ -1,6 +1,6 @@ { "id": "clkinfosec", - "name": "Secondx Clockinfo", - "version":"0.01", + "name": "Seconds Clockinfo", + "version":"0.02", "description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the time in seconds (many clocks only display minutes)", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], From 228265ba69d4c853a2f1f110e2c98760e0fc61e2 Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 1 Jul 2024 09:06:27 -0400 Subject: [PATCH 157/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index 3f23c6d98d..55054a9a23 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -238,7 +238,7 @@ function showPlayData() { function finishGame() { let date = new Date(); - let saveFileContents = ' '; // ALT 255 + let saveFileContents = ' '; let parTotal = 0; Bangle.setGPSPower(0); @@ -246,7 +246,7 @@ function finishGame() { const saveFile = require("Storage").open(saveFilename, "w"); for (let i = 1; i < 19; i++) { saveFileContents += String(score[i] - par[i]).padStart(2, ' '); - saveFileContents += (i == 18 ? ' ' : (i % 6 == 0 ? ' \n ' : '')); + saveFileContents += (i == 18 ? ' ' : (i % 6 == 0 ? ' \n ' : '')); parTotal += par[i]; } saveFile.write(`${date.getFullYear()}_${zeroPad(date.getMonth() + 1, 2)}_${zeroPad(date.getDate(), 2)}\n${courseName}${totalShots}/${parTotal}\n${saveFileContents}`); From 56793614e9871bab64c566bdd8a751893110513a Mon Sep 17 00:00:00 2001 From: jeonlab <37269397+jeonlab@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:29:10 -0400 Subject: [PATCH 158/258] Update golf-gps.js --- apps/golf-gps/golf-gps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/golf-gps/golf-gps.js b/apps/golf-gps/golf-gps.js index 55054a9a23..787e971270 100644 --- a/apps/golf-gps/golf-gps.js +++ b/apps/golf-gps/golf-gps.js @@ -228,7 +228,7 @@ function showPlayData() { // battery level bar g.setColor('#000').drawRect(59, H * 2 / 3 - 2, W - 5, H * 2 / 3 + 4); g.drawRect(58, H * 2 / 3 - 1, W - 4, H * 2 / 3 + 5); - g.setColor(E.getBattery() > 30 ? '#03f' : 'f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 60), H * 2 / 3 + 3); + g.setColor(E.getBattery() > 15 ? '#03f' : '#f00').fillRect(60, H * 2 / 3, 60 + E.getBattery() / 100 * (W - 60), H * 2 / 3 + 3); // hdop level indicator if (lastFix.hdop < 5) g.setColor('#0f0').fillRect(60, H / 3, 96, H / 3 + 5); From acd9b3be949aea1fb7adb5e44a0daf6206d79251 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 2 Jul 2024 08:42:50 +0100 Subject: [PATCH 159/258] Rename `uu` --- apps/hwid_a_battery_widget/widget.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index 95d283d05d..0f02f41383 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -19,7 +19,7 @@ return COLORS.low; }; - function draw(_self, uu) { + function draw(_self, pwrOverride) { let x = this.x; let y = this.y; if (x != null && y != null) { @@ -38,7 +38,7 @@ // Show percentage g.setFontAlign(0,0); g.setFont('Vector',16); - this.drawText(l, uu); + this.drawText(l, pwrOverride); } old_x = this.x; old_y = this.y; @@ -53,8 +53,8 @@ let drawText; if(E.getPowerUsage){ - drawText = function(l, uu) { - const u = uu == null ? E.getPowerUsage().total : uu; + drawText = function(l, pwrOverride) { + const u = pwrOverride == null ? E.getPowerUsage().total : pwrOverride; // text colour is based off power usage // colour height is based off time left, higher = more From 07c4a52aa9463d95876b027781ccb34997508c8f Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 2 Jul 2024 08:43:00 +0100 Subject: [PATCH 160/258] Sum power ignoring LCD --- apps/hwid_a_battery_widget/widget.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/hwid_a_battery_widget/widget.js b/apps/hwid_a_battery_widget/widget.js index 0f02f41383..7886faafdd 100644 --- a/apps/hwid_a_battery_widget/widget.js +++ b/apps/hwid_a_battery_widget/widget.js @@ -54,7 +54,13 @@ if(E.getPowerUsage){ drawText = function(l, pwrOverride) { - const u = pwrOverride == null ? E.getPowerUsage().total : pwrOverride; + const pwr = E.getPowerUsage(); + let total = 0; + for(const key in pwr){ + if(!key.startsWith("LCD")) + total += pwr[key]; + } + const u = pwrOverride == null ? total : pwrOverride; // text colour is based off power usage // colour height is based off time left, higher = more From 59f3726deb16d7a795aafe245d09f5c5256ae5f0 Mon Sep 17 00:00:00 2001 From: jla-42 <66872002+jla-42@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:25:14 +0200 Subject: [PATCH 161/258] updated sleepquiet --- README.md | 4 +- apps/.eslintrc.js | 203 ---- apps/_example_app/metadata.json | 2 +- apps/_example_clock/clock.js | 44 - apps/_example_clock/metadata.json | 1 + apps/activityreminder/ChangeLog | 1 + apps/activityreminder/lib.js | 15 +- apps/activityreminder/metadata.json | 4 +- apps/advcasio/ChangeLog | 3 +- apps/advcasio/README.md | 13 +- apps/advcasio/app.js | 6 +- apps/advcasio/metadata.json | 7 +- apps/alarm/ChangeLog | 2 + apps/alarm/README.md | 2 +- apps/alarm/app.js | 115 +- apps/alarm/metadata.json | 2 +- apps/andark/ChangeLog | 5 +- apps/andark/README.md | 14 +- apps/andark/andark_screen.png | Bin 3660 -> 3710 bytes apps/andark/app.js | 154 +-- apps/andark/metadata.json | 7 +- apps/android/ChangeLog | 3 + apps/android/README.md | 4 + apps/android/boot.js | 30 +- apps/android/metadata.json | 2 +- apps/android/test.js | 153 --- apps/astrocalc/ChangeLog | 1 + apps/astrocalc/astrocalc-app.js | 29 +- apps/astrocalc/metadata.json | 2 +- apps/autoreset/ChangeLog | 1 + apps/autoreset/README.md | 6 +- apps/autoreset/boot.js | 23 +- apps/autoreset/metadata.json | 8 +- apps/backswipe/ChangeLog | 3 + apps/backswipe/boot.js | 4 +- apps/backswipe/metadata.json | 2 +- apps/backswipe/settings.js | 3 +- apps/bigdclock/ChangeLog | 1 + apps/bigdclock/bigdclock.app.js | 16 +- apps/bigdclock/metadata.json | 2 +- apps/binaryclk/ChangeLog | 7 + apps/binaryclk/app.js | 89 +- apps/binaryclk/metadata.json | 4 +- apps/binaryclk/screenshot.png | Bin 16648 -> 2178 bytes apps/binaryclk/settings.js | 8 + apps/boot/ChangeLog | 3 + apps/boot/bootupdate.js | 18 +- apps/boot/metadata.json | 2 +- apps/bootgattbat/ChangeLog | 1 + apps/bootgattbat/boot.js | 20 +- apps/bootgattbat/metadata.json | 2 +- apps/bootgatthrm/ChangeLog | 4 +- apps/bootgatthrm/boot.js | 32 +- apps/bootgatthrm/metadata.json | 2 +- apps/btadv/ChangeLog | 2 + apps/btadv/app.js | 9 +- apps/btadv/app.ts | 22 +- apps/btadv/metadata.json | 2 +- apps/bthome/ChangeLog | 6 +- apps/bthome/app.js | 26 +- apps/bthome/boot.js | 20 +- apps/bthome/metadata.json | 2 +- apps/bthome/settings.js | 54 +- apps/bthometemp/ChangeLog | 1 + apps/bthometemp/app.js | 17 +- apps/bthometemp/metadata.json | 2 +- apps/bthrm/ChangeLog | 5 + apps/bthrm/README.md | 8 + apps/bthrm/bthrm.js | 13 +- apps/bthrm/default.json | 3 +- apps/bthrm/lib.js | 319 ++---- apps/bthrm/metadata.json | 2 +- apps/bthrm/recorder.js | 4 +- apps/bthrm/settings.js | 198 +++- apps/buffgym/.eslintrc.json | 35 - apps/cards/ChangeLog | 1 + apps/cards/app.js | 43 +- apps/cards/metadata.json | 9 +- apps/chargent/ChangeLog | 2 + apps/chargent/README.md | 4 +- apps/chargent/boot.js | 3 +- apps/chargent/metadata.json | 2 +- apps/clkinfocal/ChangeLog | 3 + apps/clkinfocal/clkinfo.js | 7 +- apps/clkinfocal/metadata.json | 2 +- apps/clkinfogps/clkinfo.js | 2 +- apps/clkinfogps/geotools.js | 6 +- apps/clock_info/ChangeLog | 4 + apps/clock_info/lib.js | 61 +- apps/clock_info/metadata.json | 4 +- apps/clockcal/ChangeLog | 4 +- apps/clockcal/README.md | 15 +- apps/clockcal/app.js | 51 +- apps/clockcal/metadata.json | 2 +- apps/clockcal/settings.js | 10 +- apps/color_catalog/ChangeLog | 1 + apps/color_catalog/app.js | 8 +- apps/color_catalog/metadata.json | 2 +- apps/contacts/ChangeLog | 1 + apps/contacts/contacts.app.js | 216 ++-- apps/contacts/metadata.json | 2 +- apps/cscsensor/ChangeLog | 4 + apps/cscsensor/cscsensor.app.js | 131 +-- apps/cscsensor/metadata.json | 3 +- apps/cycling/ChangeLog | 1 + apps/cycling/README.md | 4 +- apps/cycling/blecsc-emu.js | 111 -- apps/cycling/blecsc.js | 150 --- apps/cycling/cycling.app.js | 28 +- apps/cycling/metadata.json | 4 +- apps/daisy/ChangeLog | 1 + apps/daisy/README.md | 3 +- apps/daisy/app.js | 29 +- apps/daisy/metadata.json | 2 +- apps/daisy/settings.js | 11 +- apps/doztime/ChangeLog | 3 +- apps/doztime/app-bangle2.js | 15 +- apps/doztime/metadata.json | 2 +- apps/drained/ChangeLog | 1 + apps/drained/app.js | 25 +- apps/drained/app.ts | 30 +- apps/drained/metadata.json | 2 +- apps/fastload/README.md | 8 +- apps/fastreset/README.md | 2 +- apps/fastreset/metadata.json | 2 +- apps/fileman/ChangeLog | 1 + apps/fileman/metadata.json | 1 + apps/folderlaunch/ChangeLog | 3 +- apps/folderlaunch/app.js | 15 +- apps/folderlaunch/app.ts | 15 +- apps/folderlaunch/configLoad.ts | 2 +- apps/folderlaunch/metadata.json | 2 +- apps/folderlaunch/types.d.ts | 2 +- apps/fontall/ChangeLog | 1 + apps/fontall/font.pbf | Bin 2039413 -> 1953834 bytes apps/fontall/metadata.json | 4 +- apps/fontext/ChangeLog | 1 + apps/fontext/font.pbf | Bin 21305 -> 20066 bytes apps/fontext/metadata.json | 5 +- apps/fuzzyw/ChangeLog | 3 +- apps/fuzzyw/README.md | 1 + apps/fuzzyw/fuzzyw.app.js | 206 ++-- apps/fuzzyw/fuzzyw.settings.js | 25 +- apps/fuzzyw/metadata.json | 2 +- apps/fwupdate/custom.html | 19 +- apps/gipy/pkg/gps.d.ts | 2 +- apps/gipy/pkg/gps_bg.wasm.d.ts | 2 +- apps/gpsinfo/ChangeLog | 1 + apps/gpsinfo/gps-info.js | 16 +- apps/gpsinfo/metadata.json | 2 +- apps/gpstouch/geotools.js | 6 +- apps/ha/ChangeLog | 1 + apps/ha/README.md | 4 +- apps/ha/ha.app.js | 115 +- apps/ha/ha.lib.js | 59 +- apps/ha/metadata.json | 2 +- apps/hassio/ChangeLog | 3 +- apps/hassio/hassio.boot.js | 4 +- apps/hassio/metadata.json | 4 +- apps/health/ChangeLog | 1 + apps/health/app.js | 1 - apps/health/metadata.json | 2 +- apps/helloworld/ChangeLog | 1 + apps/helloworld/helloworld.app.js | 6 +- apps/helloworld/metadata.json | 2 +- apps/hrm/ChangeLog | 1 + apps/hrm/heartrate.js | 5 +- apps/hrm/metadata.json | 2 +- apps/imgclock/ChangeLog | 3 +- apps/imgclock/app.js | 1 + apps/imgclock/metadata.json | 4 +- apps/intclock/README.md | 4 +- apps/ios/ChangeLog | 3 +- apps/ios/boot.js | 2 +- apps/ios/metadata.json | 2 +- apps/kbmulti/ChangeLog | 2 + apps/kbmulti/lib.js | 38 +- apps/kbmulti/metadata.json | 2 +- apps/kbmulti/settings.js | 25 +- apps/kineticscroll/ChangeLog | 2 + apps/kineticscroll/boot.js | 132 ++- apps/kineticscroll/boot.min.js | 9 +- apps/kineticscroll/metadata.json | 2 +- apps/kitchen/kitchen.app.js | 2 +- apps/launch/ChangeLog | 1 + apps/launch/app.js | 79 +- apps/launch/metadata.json | 2 +- apps/lcars/ChangeLog | 1 + apps/lcars/lcars.app.js | 2 +- apps/lcars/metadata.json | 2 +- apps/lint_exemptions.js | 627 ++++------- apps/locale/ChangeLog | 2 + apps/locale/README.md | 1 - apps/locale/locale.html | 57 +- apps/locale/locales.js | 106 +- apps/locale/metadata.json | 2 +- apps/messagegui/ChangeLog | 2 + apps/messagegui/app.js | 32 +- apps/messagegui/metadata.json | 2 +- apps/messagesoverlay/ChangeLog | 10 + apps/messagesoverlay/README.md | 13 +- apps/messagesoverlay/lib.js | 737 ++++++++---- apps/messagesoverlay/metadata.json | 7 +- apps/multitimer/ChangeLog | 3 + apps/multitimer/app.js | 2 + apps/multitimer/boot.js | 2 +- apps/multitimer/metadata.json | 2 +- apps/notify/ChangeLog | 1 + apps/notify/metadata.json | 2 +- apps/notify/notify_bjs2.js | 1 + apps/openhaystack/ChangeLog | 1 + apps/openhaystack/custom.html | 2 +- apps/openhaystack/metadata.json | 2 +- apps/owmweather/ChangeLog | 2 + apps/owmweather/lib.js | 22 +- apps/owmweather/metadata.json | 2 +- apps/patriotclk/app.js | 6 +- apps/pebblepp/ChangeLog | 3 + apps/pebblepp/app.js | 41 +- apps/pebblepp/metadata.json | 7 +- apps/pebblepp/settings.js | 16 +- apps/promenu/ChangeLog | 7 + apps/promenu/README.md | 2 + apps/promenu/bootb2.js | 314 +++--- apps/promenu/metadata.json | 2 +- apps/ratchet_launch/ChangeLog | 1 + apps/ratchet_launch/app.js | 6 +- apps/ratchet_launch/metadata.json | 2 +- apps/recorder/ChangeLog | 6 + apps/recorder/app.js | 14 +- apps/recorder/clkinfo.js | 4 +- apps/recorder/interface.html | 121 +- apps/recorder/metadata.json | 2 +- apps/recorder/widget.js | 19 +- apps/rellotge/ChangeLog | 3 + apps/rellotge/README.md | 8 + apps/rellotge/metadata.json | 2 +- apps/rellotge/rellotge.js | 256 +++-- apps/rep/app.ts | 6 +- apps/run/ChangeLog | 1 + apps/run/README.md | 2 + apps/run/metadata.json | 4 +- apps/runplus/ChangeLog | 4 + apps/runplus/README.md | 3 + apps/runplus/app.js | 160 ++- apps/runplus/karvonen.js | 514 +++++---- apps/runplus/metadata.json | 9 +- apps/runplus/settings.js | 8 + apps/sched/ChangeLog | 2 + apps/sched/lib.js | 3 +- apps/sched/metadata.json | 2 +- apps/sched/sched.js | 5 +- apps/schoolCalendar/fullcalendar/main.js | 18 +- apps/setting/ChangeLog | 1 + apps/setting/metadata.json | 2 +- apps/setting/settings.js | 28 + apps/sixths/ChangeLog | 3 +- apps/sixths/metadata.json | 2 +- apps/sixths/sixths.app.js | 193 ++-- apps/sleeplog/ChangeLog | 2 + apps/sleeplog/README.md | 2 + apps/sleeplog/app.js | 8 +- apps/sleeplog/boot.js | 61 +- apps/sleeplog/lib.js | 46 +- apps/sleeplog/metadata.json | 2 +- apps/sleeplog/settings.js | 60 +- apps/sleepphasealarm/ChangeLog | 2 + apps/sleepphasealarm/app.js | 8 +- apps/sleepphasealarm/interface.html | 6 +- apps/sleepphasealarm/metadata.json | 2 +- apps/sleepphasealarm/settings.js | 16 +- apps/slpquiet/.eslintrc.json | 5 - apps/slpquiet/ChangeLog | 1 + apps/slpquiet/README.md | 6 + apps/slpquiet/app.js | 11 + apps/slpquiet/boot.js | 2 +- apps/slpquiet/metadata.json | 2 +- apps/taglaunch/ChangeLog | 1 + apps/taglaunch/app.js | 3 +- apps/taglaunch/metadata.json | 2 +- apps/teatimer/ChangeLog | 1 + apps/teatimer/README.md | 2 +- apps/teatimer/app.js | 7 +- apps/teatimer/metadata.json | 4 +- apps/thmswtch/README.md | 6 + apps/timerclk/ChangeLog | 1 + apps/timerclk/alarm.js | 6 +- apps/timerclk/metadata.json | 2 +- apps/timerclk/timer.js | 6 +- apps/twenties/metadata.json | 2 +- apps/walkersclock/app.js | 46 +- apps/widChargingStatus/ChangeLog | 2 + apps/widChargingStatus/metadata.json | 2 +- apps/widChargingStatus/widget.js | 5 +- apps/widChargingStatus/widget.ts | 7 +- apps/widbata/widbata.wid.js | 2 +- apps/widbtstates/widget.js | 6 +- apps/widbtstates/widget.ts | 6 +- apps/widdevst/ChangeLog | 2 + apps/widdevst/README.md | 4 + apps/widdevst/metadata.json | 5 +- apps/widdevst/wid.js | 38 +- apps/widdst/ChangeLog | 3 +- apps/widdst/metadata.json | 2 +- apps/widdst/settings.js | 13 +- apps/widhid/wid.ts | 4 +- apps/widmsggrid/ChangeLog | 1 + apps/widmsggrid/metadata.json | 2 +- apps/widmsggrid/widget.js | 2 +- apps/wohrm/app.js | 6 +- apps/worldclock/ChangeLog | 1 + apps/worldclock/app.js | 28 +- apps/worldclock/metadata.json | 2 +- bin/bulk-update-apps.mjs | 2 +- bin/exempt-lint.mjs | 2 +- bin/font_creator.js | 35 +- bin/runapptests.js | 522 +++++++-- bin/sanitycheck.js | 9 +- bin/sync-lint-exemptions.mjs | 2 +- core | 2 +- css/main.css | 2 +- lang/da_DK.json | 31 +- loader.js | 2 +- modules/.eslintrc.json | 163 --- modules/ClockFace_menu.js | 3 +- modules/Layout.js | 2 +- modules/Layout.md | 17 + modules/Slider.js | 11 +- modules/Slider.md | 8 +- modules/date_utils.js | 1 - modules/exstats.js | 75 +- modules/suncalc.js | 4 +- modules/widget_utils.js | 61 +- package-lock.json | 740 +++++++++++- package.json | 8 +- tsconfig.json | 4 +- typescript/package-lock.json | 16 +- typescript/package.json | 2 +- typescript/types/clock_info.d.ts | 4 +- typescript/types/layout.d.ts | 10 +- typescript/types/locale.d.ts | 10 + typescript/types/main.d.ts | 1302 ++++++++++++++++++---- typescript/types/settings.d.ts | 2 + webtools | 2 +- 344 files changed, 6476 insertions(+), 4252 deletions(-) delete mode 100644 apps/.eslintrc.js delete mode 100644 apps/_example_clock/clock.js delete mode 100644 apps/android/test.js delete mode 100644 apps/buffgym/.eslintrc.json delete mode 100644 apps/cycling/blecsc-emu.js delete mode 100644 apps/cycling/blecsc.js delete mode 100644 apps/slpquiet/.eslintrc.json delete mode 100644 modules/.eslintrc.json diff --git a/README.md b/README.md index ee1c220612..d595c7df13 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Bangle.js App Loader (and Apps) * Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) * Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/) +The release version is manually refreshed with regular intervals while the development version is continuously updated as new code is committed to this repository. + **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, and that it is not licensed in another way that would make this impossible. @@ -403,7 +405,7 @@ in an iframe. - +
Loading...