From 4079123c93bed52b653a8d53f4520aafc33c16b9 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 19 Aug 2023 18:21:21 -0500 Subject: [PATCH 001/133] =?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 000000000..df11c5f15 --- /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/133] 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 df11c5f15..f14777448 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/133] 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 f14777448..6263fc85a 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/133] 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 6263fc85a..d01102dd6 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/133] 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 d01102dd6..02698cdaa 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/133] Implement scroll bar for log display --- apps/stamplog/app.js | 84 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 02698cdaa..2e05dddc7 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', + {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: [], + // 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/133] 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 2e05dddc7..c979bf349 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/133] 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 c979bf349..0b74fc2e1 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/133] 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 0b74fc2e1..4776e173f 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/133] 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 4776e173f..71269fb43 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/133] 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 71269fb43..7b5975f5f 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/133] 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 7b5975f5f..1d54d8e32 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/133] 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 1d54d8e32..344f183c8 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/133] 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 344f183c8..d6317c1ca 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/133] 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 d6317c1ca..0663826c7 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/133] 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 0663826c7..cd310f913 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/133] 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 cd310f913..38f8e1eb2 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/133] 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 38f8e1eb2..cc4cfd7af 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/133] 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 cc4cfd7af..0d812eabe 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/133] 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 0d812eabe..b41c37c43 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/133] 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 b41c37c43..b9184efaf 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 30734047668adce7af677b273566bbde83eafbf0 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 10 May 2024 20:21:36 -0500 Subject: [PATCH 022/133] 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 023/133] 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 b9184efaf..521480776 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 ab045fbd2437eebadc640504471999e15e95d5a2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 24 May 2024 18:04:13 -0500 Subject: [PATCH 024/133] 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 521480776..901ddc458 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 025/133] 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 901ddc458..11d48ecd8 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 026/133] 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 11d48ecd8..7505c6afb 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 027/133] 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 7505c6afb..9e4fa2273 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 028/133] 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 000000000..b35f05e08 --- /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 000000000..ec66c5568 --- /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 000000000..8114b159d --- /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 029/133] 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 ec66c5568..000000000 --- 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 8114b159d..98d57e4bc 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 030/133] 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 98d57e4bc..2963953c6 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 031/133] 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 9e4fa2273..87f227f46 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 e542bed0a094edc28d43078a5096e71f5340dc20 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Wed, 19 Jun 2024 15:29:57 -0500 Subject: [PATCH 032/133] 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 000000000..6febe7849 --- /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 2963953c6..858fab237 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 bf3b572b9e4cd20e8465fb9de361ef236a32e433 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 9 Jul 2024 08:44:43 +0100 Subject: [PATCH 033/133] power_usage: add module --- modules/power_usage.js | 15 +++++++++++++++ modules/power_usage.ts | 28 ++++++++++++++++++++++++++++ typescript/types/modules.d.ts | 1 + 3 files changed, 44 insertions(+) create mode 100644 modules/power_usage.js create mode 100644 modules/power_usage.ts diff --git a/modules/power_usage.js b/modules/power_usage.js new file mode 100644 index 000000000..df2dd5324 --- /dev/null +++ b/modules/power_usage.js @@ -0,0 +1,15 @@ +exports.get = function () { + var pwr = E.getPowerUsage(); + var batt = E.getBattery(); + var usage = 0; + for (var key in pwr.device) { + if (!/^(LCD|LED)/.test(key)) + usage += pwr.device[key]; + } + var hrsLeft = 175000 * batt / (100 * usage); + return { + usage: usage, + hrsLeft: hrsLeft, + batt: batt, + }; +}; diff --git a/modules/power_usage.ts b/modules/power_usage.ts new file mode 100644 index 000000000..c87420d25 --- /dev/null +++ b/modules/power_usage.ts @@ -0,0 +1,28 @@ +type PowerUsage = { + usage: number, + hrsLeft: number, + batt: number, // battery percentage +}; + +type PowerUsageModule = { + get: () => PowerUsage, +}; + +exports.get = (): PowerUsage => { + const pwr = E.getPowerUsage(); + const batt = E.getBattery(); + let usage = 0; + for(const key in pwr.device){ + if(!/^(LCD|LED)/.test(key)) + usage += pwr.device[key]; + } + + // 175mAh, scaled based on battery (batt/100), scaled down based on usage + const hrsLeft = 175000 * batt / (100 * usage); + + return { + usage, + hrsLeft, + batt, + }; +}; diff --git a/typescript/types/modules.d.ts b/typescript/types/modules.d.ts index ad3612117..e8aa15ac1 100644 --- a/typescript/types/modules.d.ts +++ b/typescript/types/modules.d.ts @@ -5,3 +5,4 @@ declare function require(moduleName: "sched"): typeof Sched; declare function require(moduleName: "ClockFace"): typeof ClockFace_.ClockFace; declare function require(moduleName: "clock_info"): typeof ClockInfo; declare function require(moduleName: "Layout"): typeof Layout.Layout; +declare function require(moduleName: "power_usage"): PowerUsageModule; From b4b43417284e74c623f374959e11d692cf465458 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 9 Jul 2024 08:45:54 +0100 Subject: [PATCH 034/133] widbattpwr: use `power_usage` module --- apps/widbattpwr/ChangeLog | 1 + apps/widbattpwr/metadata.json | 2 +- apps/widbattpwr/widget.js | 13 +++---------- apps/widbattpwr/widget.ts | 14 +++----------- 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/apps/widbattpwr/ChangeLog b/apps/widbattpwr/ChangeLog index b1bdaa6fc..be3698e53 100644 --- a/apps/widbattpwr/ChangeLog +++ b/apps/widbattpwr/ChangeLog @@ -1 +1,2 @@ 0.01: Initial fork from hwid_a_battery_widget +0.02: Use `power_usage` module diff --git a/apps/widbattpwr/metadata.json b/apps/widbattpwr/metadata.json index 667485666..f802a21f5 100644 --- a/apps/widbattpwr/metadata.json +++ b/apps/widbattpwr/metadata.json @@ -3,7 +3,7 @@ "name": "Battery power and percentage widget", "shortName": "Batt Pwr", "icon": "widget.png", - "version": "0.01", + "version": "0.02", "type": "widget", "supports": ["BANGLEJS2"], "readme": "README.md", diff --git a/apps/widbattpwr/widget.js b/apps/widbattpwr/widget.js index 58f89dc34..16b444208 100644 --- a/apps/widbattpwr/widget.js +++ b/apps/widbattpwr/widget.js @@ -20,13 +20,7 @@ function draw() { var x = this.x; var y = this.y; - var batt = E.getBattery(); - var pwr = E.getPowerUsage(); - var usage = 0; - for (var key in pwr.device) { - if (!/^(LCD|LED)/.test(key)) - usage += pwr.device[key]; - } + var _a = require("power_usage").get(), usage = _a.usage, hrsLeft = _a.hrsLeft, batt = _a.batt; var pwrColour = powerColour(usage); g.reset() .setBgColor(g.theme.bg) @@ -43,9 +37,8 @@ txt = "".concat(batt, "%"); } else { - var hrs = 175000 * batt / (100 * usage); - var days = hrs / 24; - txt = days >= 1 ? "".concat(Math.round(Math.min(days, 99)), "d") : "".concat(Math.round(hrs), "h"); + var days = hrsLeft / 24; + txt = days >= 1 ? "".concat(Math.round(Math.min(days, 99)), "d") : "".concat(Math.round(hrsLeft), "h"); } var txth = 14; g.setColor(g.theme.fg); diff --git a/apps/widbattpwr/widget.ts b/apps/widbattpwr/widget.ts index de5352f32..5a2de84a9 100644 --- a/apps/widbattpwr/widget.ts +++ b/apps/widbattpwr/widget.ts @@ -22,13 +22,7 @@ let x = this.x!; let y = this.y!; - const batt = E.getBattery(); - const pwr = E.getPowerUsage(); - let usage = 0; - for(const key in pwr.device){ - if(!/^(LCD|LED)/.test(key)) - usage += pwr.device[key]; - } + const { usage, hrsLeft, batt } = require("power_usage").get(); const pwrColour = powerColour(usage); g.reset() @@ -47,10 +41,8 @@ if(showPct){ txt = `${batt}%`; }else{ - // 175mAh, scaled based on battery (batt/100), scaled down based on usage - const hrs = 175000 * batt / (100 * usage); - const days = hrs / 24; - txt = days >= 1 ? `${Math.round(Math.min(days, 99))}d` : `${Math.round(hrs)}h`; + const days = hrsLeft / 24; + txt = days >= 1 ? `${Math.round(Math.min(days, 99))}d` : `${Math.round(hrsLeft)}h`; } // draw time remaining, then shade it based on batt % From 1621ad6e079b978bff8f58a0ce7d72a870acaba4 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 9 Jul 2024 08:46:07 +0100 Subject: [PATCH 035/133] daisy: use `power_usage` module --- apps/daisy/ChangeLog | 1 + apps/daisy/app.js | 2 +- apps/daisy/metadata.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/daisy/ChangeLog b/apps/daisy/ChangeLog index 3bc9dda43..3d8ef0472 100644 --- a/apps/daisy/ChangeLog +++ b/apps/daisy/ChangeLog @@ -11,3 +11,4 @@ 0.11: Minor code improvements 0.12: Added setting to change Battery estimate to hours 0.13: Fixed Battery estimate Default to percentage and improved setting string +0.14: Use `power_usage` module diff --git a/apps/daisy/app.js b/apps/daisy/app.js index 7cafe6d48..4aa7eb132 100644 --- a/apps/daisy/app.js +++ b/apps/daisy/app.js @@ -116,7 +116,7 @@ function updateSunRiseSunSet(now, lat, lon, line){ function batteryString(){ let stringToInsert; if (settings.batt_hours) { - var batt_usage = 200000/E.getPowerUsage().total; + var batt_usage = require("power_usage").get().hrsLeft; let rounded; if (batt_usage > 24) { var days = Math.floor(batt_usage/24); diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json index 2367452cc..178cd8b20 100644 --- a/apps/daisy/metadata.json +++ b/apps/daisy/metadata.json @@ -1,6 +1,6 @@ { "id": "daisy", "name": "Daisy", - "version": "0.13", + "version": "0.14", "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", From ef0c6a77087e1f39955f557b41e8b2ad0bcd519e Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Tue, 9 Jul 2024 08:50:42 +0100 Subject: [PATCH 036/133] power_usage lint --- modules/power_usage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/power_usage.ts b/modules/power_usage.ts index c87420d25..ebe884a48 100644 --- a/modules/power_usage.ts +++ b/modules/power_usage.ts @@ -4,6 +4,7 @@ type PowerUsage = { batt: number, // battery percentage }; +// eslint-disable-next-line no-unused-vars type PowerUsageModule = { get: () => PowerUsage, }; From ce33c3a66222022b9800b4ee48881cc8fbf7344e Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 11 Jul 2024 21:14:28 +0100 Subject: [PATCH 037/133] Nerf power-type check for firmware issue (espruino/Espruino#2529) --- modules/power_usage.js | 2 +- modules/power_usage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/power_usage.js b/modules/power_usage.js index df2dd5324..2aadd0d71 100644 --- a/modules/power_usage.js +++ b/modules/power_usage.js @@ -3,7 +3,7 @@ exports.get = function () { var batt = E.getBattery(); var usage = 0; for (var key in pwr.device) { - if (!/^(LCD|LED)/.test(key)) + if (!key.startsWith("LCD")) usage += pwr.device[key]; } var hrsLeft = 175000 * batt / (100 * usage); diff --git a/modules/power_usage.ts b/modules/power_usage.ts index ebe884a48..d627badb9 100644 --- a/modules/power_usage.ts +++ b/modules/power_usage.ts @@ -14,7 +14,7 @@ exports.get = (): PowerUsage => { const batt = E.getBattery(); let usage = 0; for(const key in pwr.device){ - if(!/^(LCD|LED)/.test(key)) + if(!key.startsWith("LCD")) usage += pwr.device[key]; } From 650ceeac21dd5ee5293543566197cf5c0e574e6a Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 11 Jul 2024 21:20:43 +0100 Subject: [PATCH 038/133] main.d.ts: make startsWith() 2nd arg optional --- typescript/types/main.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/types/main.d.ts b/typescript/types/main.d.ts index 6921c7d12..06ebb3ecd 100644 --- a/typescript/types/main.d.ts +++ b/typescript/types/main.d.ts @@ -11290,7 +11290,7 @@ interface String { * @returns {boolean} `true` if the given characters are found at the beginning of the string, otherwise, `false`. * @url http://www.espruino.com/Reference#l_String_startsWith */ - startsWith(searchString: any, position: number): boolean; + startsWith(searchString: any, position?: number): boolean; /** * From e14d1f63d0b1ddb32359f413a0e539c5f49e2bc7 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 11 Jul 2024 22:41:31 +0100 Subject: [PATCH 039/133] typescript: update types --- typescript/types/main.d.ts | 108 ++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/typescript/types/main.d.ts b/typescript/types/main.d.ts index 06ebb3ecd..24633db5b 100644 --- a/typescript/types/main.d.ts +++ b/typescript/types/main.d.ts @@ -316,6 +316,48 @@ type VariableSizeInformation = { more?: VariableSizeInformation; }; +type PowerUsage = { + total: number, + device: { + CPU?: number, + UART?: number, + PWM?: number, + LED1?: number, + LED2?: number, + LED3?: number, + + // bangle + LCD?: number, + LCD_backlight?: number, + LCD_touch?: number, + HRM?: number, + GPS?: number, + compass?: number, + baro?: number, + + // nrf + BLE_periph?: number, + BLE_central?: number, + BLE_advertise?: number, + BLE_scan?: number, + + // pixljs + //LCD?: number, // (see above) + + // puck + mag?: number, + accel?: number, + + // jolt + driver0?: number, + driver1?: number, + pin0_internal_resistance?: number, + pin2_internal_resistance?: number, + pin4_internal_resistance?: number, + pin6_internal_resistance?: number, + }, +}; + type PipeOptions = { chunkSize?: number, end?: boolean, @@ -1014,13 +1056,15 @@ declare class NRF { static eraseBonds(callback?: any): void; /** - * Get this device's default Bluetooth MAC address. + * Get this device's default or current Bluetooth MAC address. * For Puck.js, the last 5 characters of this (e.g. `ee:ff`) are used in the * device's advertised Bluetooth name. + * + * @param {boolean} current - If true, return the current address rather than the default * @returns {any} MAC address - a string of the form 'aa:bb:cc:dd:ee:ff' * @url http://www.espruino.com/Reference#l_NRF_getAddress */ - static getAddress(): any; + static getAddress(current: boolean): any; /** * Set this device's default Bluetooth MAC address: @@ -4524,10 +4568,32 @@ declare class Bangle { static dbg(): any; /** - * Writes a register on the accelerometer + * Writes a register on the touch controller * * @param {number} reg * @param {number} data + * @url http://www.espruino.com/Reference#l_Bangle_touchWr + */ + static touchWr(reg: number, data: number): void; + + /** + * Reads a register from the touch controller + * **Note:** On Espruino 2v06 and before this function only returns a number (`cnt` + * is ignored). + * + * @param {number} reg - Register number to read + * @param {number} cnt - If specified, returns an array of the given length (max 128). If not (or 0) it returns a number + * @returns {any} + * @url http://www.espruino.com/Reference#l_Bangle_touchRd + */ + static touchRd(reg: number, cnt?: 0): number; + static touchRd(reg: number, cnt: number): number[]; + + /** + * Writes a register on the accelerometer + * + * @param {number} reg - Register number to write + * @param {number} data - An integer value to write to the register * @url http://www.espruino.com/Reference#l_Bangle_accelWr */ static accelWr(reg: number, data: number): void; @@ -8343,7 +8409,7 @@ declare class E { * E.showScroller({ * h : 40, c : 8, * draw : (idx, r) => { - * g.setBgColor((idx&1)?"#666":"#999").clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1); + * g.setBgColor((idx&1)?"#666":"#CCC").clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1); * g.setFont("6x8:2").drawString("Item Number\n"+idx,r.x+10,r.y+4); * }, * select : (idx) => console.log("You selected ", idx) @@ -9482,7 +9548,7 @@ declare class E { * @returns {any} An object detailing power usage in microamps * @url http://www.espruino.com/Reference#l_E_getPowerUsage */ - static getPowerUsage(): any; + static getPowerUsage(): PowerUsage; /** * Decode a UTF8 string. @@ -11137,21 +11203,21 @@ interface String { * Return the index of substring in this string, or -1 if not found * * @param {any} substring - The string to search for - * @param {any} fromIndex - Index to search from + * @param {any} [fromIndex] - [optional] Index to search from * @returns {number} The index of the string, or -1 if not found * @url http://www.espruino.com/Reference#l_String_indexOf */ - indexOf(substring: any, fromIndex: any): number; + indexOf(substring: any, fromIndex?: any): number; /** * Return the last index of substring in this string, or -1 if not found * * @param {any} substring - The string to search for - * @param {any} fromIndex - Index to search from + * @param {any} [fromIndex] - [optional] Index to search from * @returns {number} The index of the string, or -1 if not found * @url http://www.espruino.com/Reference#l_String_lastIndexOf */ - lastIndexOf(substring: any, fromIndex: any): number; + lastIndexOf(substring: any, fromIndex?: any): number; /** * Matches an occurrence `subStr` in the string. @@ -11206,20 +11272,20 @@ interface String { /** * * @param {number} start - The start character index (inclusive) - * @param {any} end - The end character index (exclusive) + * @param {any} [end] - [optional] The end character index (exclusive) * @returns {any} The part of this string between start and end * @url http://www.espruino.com/Reference#l_String_substring */ - substring(start: number, end: any): any; + substring(start: number, end?: any): any; /** * * @param {number} start - The start character index - * @param {any} len - The number of characters + * @param {any} [len] - [optional] The number of characters * @returns {any} Part of this string from start for len characters * @url http://www.espruino.com/Reference#l_String_substr */ - substr(start: number, len: any): any; + substr(start: number, len?: any): any; /** * @@ -11286,7 +11352,7 @@ interface String { /** * * @param {any} searchString - The string to search for - * @param {number} position - The start character index (or 0 if not defined) + * @param {number} [position] - [optional] The start character index (or 0 if not defined) * @returns {boolean} `true` if the given characters are found at the beginning of the string, otherwise, `false`. * @url http://www.espruino.com/Reference#l_String_startsWith */ @@ -11295,20 +11361,20 @@ interface String { /** * * @param {any} searchString - The string to search for - * @param {any} length - The 'end' of the string - if left off the actual length of the string is used + * @param {any} [length] - [optional] The 'end' of the string - if left off the actual length of the string is used * @returns {boolean} `true` if the given characters are found at the end of the string, otherwise, `false`. * @url http://www.espruino.com/Reference#l_String_endsWith */ - endsWith(searchString: any, length: any): boolean; + endsWith(searchString: any, length?: any): boolean; /** * * @param {any} substring - The string to search for - * @param {any} fromIndex - The start character index (or 0 if not defined) + * @param {any} [fromIndex] - [optional] The start character index (or 0 if not defined) * @returns {boolean} `true` if the given characters are in the string, otherwise, `false`. * @url http://www.espruino.com/Reference#l_String_includes */ - includes(substring: any, fromIndex: any): boolean; + includes(substring: any, fromIndex?: any): boolean; /** * Repeat this string the given number of times. @@ -11411,8 +11477,10 @@ interface RegExp { /** * The built-in class for handling Regular Expressions * **Note:** Espruino's regular expression parser does not contain all the features - * present in a full ES6 JS engine. However it does contain support for the all the - * basics. + * present in a full ES6 JS engine. however some parts of the spec are not implemented: + * * [Assertions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Assertions) other than `^` and `$` + * * [Numeric quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Quantifiers) (eg `x{3}`) + * There's a GitHub issue [concerning RegExp features here](https://github.com/espruino/Espruino/issues/1257) * @url http://www.espruino.com/Reference#RegExp */ declare const RegExp: RegExpConstructor From 5b5e3a0ef59b0418dadf26c07bd57f357a5ebad5 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Thu, 11 Jul 2024 22:41:42 +0100 Subject: [PATCH 040/133] power_usage: work with new types --- modules/power_usage.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/power_usage.ts b/modules/power_usage.ts index d627badb9..ab6982466 100644 --- a/modules/power_usage.ts +++ b/modules/power_usage.ts @@ -1,4 +1,4 @@ -type PowerUsage = { +type Pwr = { usage: number, hrsLeft: number, batt: number, // battery percentage @@ -6,16 +6,16 @@ type PowerUsage = { // eslint-disable-next-line no-unused-vars type PowerUsageModule = { - get: () => PowerUsage, + get: () => Pwr, }; -exports.get = (): PowerUsage => { +exports.get = (): Pwr => { const pwr = E.getPowerUsage(); const batt = E.getBattery(); let usage = 0; for(const key in pwr.device){ if(!key.startsWith("LCD")) - usage += pwr.device[key]; + usage += pwr.device[key as keyof typeof pwr.device]!; } // 175mAh, scaled based on battery (batt/100), scaled down based on usage From 9f69c722533d155373deeefa9625682f68f54748 Mon Sep 17 00:00:00 2001 From: blade Date: Sat, 13 Jul 2024 18:38:57 +1200 Subject: [PATCH 041/133] beep test app added --- apps/beeptest/ChangeLog | 1 + apps/beeptest/app-icon.js | 5 + apps/beeptest/beeptest.app.js | 274 ++++++++++++++++++++++++++++++++++ apps/beeptest/beeptest.img | Bin 0 -> 588 bytes apps/beeptest/metadata.json | 15 ++ apps/beeptest/readme.md | 30 ++++ 6 files changed, 325 insertions(+) create mode 100644 apps/beeptest/ChangeLog create mode 100644 apps/beeptest/app-icon.js create mode 100644 apps/beeptest/beeptest.app.js create mode 100644 apps/beeptest/beeptest.img create mode 100644 apps/beeptest/metadata.json create mode 100644 apps/beeptest/readme.md diff --git a/apps/beeptest/ChangeLog b/apps/beeptest/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/beeptest/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/beeptest/app-icon.js b/apps/beeptest/app-icon.js new file mode 100644 index 000000000..907c45577 --- /dev/null +++ b/apps/beeptest/app-icon.js @@ -0,0 +1,5 @@ +require("heatshrink").decompress( + atob( + "MDDEAP//8/wsMtP8DDIrMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAiIiIiAAAAAAAAAAAAAAAAABEQAAAAAAAiAAAiAAAAAAAAAAAAAAAREREREAAAAAIgAAACIAAAAAAAAAAAAREREREREQAAAAIgAAACIAAAAAAAAAAAEREREREREREAAAIgAAACIAAAAAAAAAAAEREREQAREREQAAIgAAACIAAAAAAAAAAAAREAAAABEREREAAiAAAiAAAAAAAAAAAAAAAAAAAAEREREQAiIiIiAAAAAAAAAAAAAAAAAAAAARERERAAIiIAAAAAAAAAAAAAAAAAAAAAAREREREAAAAAAAAAAAAAAAAAAAAAAAAAAREREREQAAAAAAAAAAAAAAAAAAAAAAAAMREREREQAAAAAAAAAAIiIiIiIiIiAAAAERERERERAAAAAAAAAAIiIiIiIiIiAAABERERERERAAAAEQAAAAAAAAAAAAAAAAABEREREREREAABERAAAAAAAAAAAAAAAAAREREREREREAARERAAAAAAAAAAAAAAAAEREREREREREQEREQAAAAAAAAAAAAAAAAEREREREQEREREREAAAAAIiIiIiIgAAABEREREREAARERERAAAAAAIiIiIiIgAAARERERERAAABEREQAAAAAAAAAAAAAAAAARERERERAAAAEREAAAAAAAAAAAAAAAAAEREREREQAAAAARAAAAAAAAAAAAAAAAAAEREREREAAAAAAAAAAAAAAAAAAAAAAAABEREREREAAAAAAAAAAAAAAAIiIiIAAAAREREREREQAAAAAAAAAAAAAAIiIiIAAAARERERERERAAAAAAAAAAAAAAAAAAAAAAERERERERERAAAAAAAAAAAAAAAAAAAAAAEREREREREREAAAAAAAAAAAAAAAAAAAABEREREREREREAAAAAAAAAAAAAAAAAAAABEREREREREREQAAAAAAAAAAAAAAAAAAAREREREREREREQAAAAAAAAAAAAAAAAAAEREREREREREREAAAAAAAAAAAAAAAAAAAERERAAAAAAAAAAAAAAAAAAAAAAAAAAABEREQAAAAAAAAAAAAAAAAAAAAAAAAAAAREREAAAAAAAAAAAAAAAAAAAAAAAAAAAARERAAAAAAAAAAAAAAAAAAAAAAAAAAAAERERAAAAAAAAAAAAAAAAAAAAAAAAAAAAEREwAAAAAAAAAAAAAAAAAAAAAAAAAAABEREAAAAAAAAAAAAAAAAAAAAAAAAAAAABERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + ), +); diff --git a/apps/beeptest/beeptest.app.js b/apps/beeptest/beeptest.app.js new file mode 100644 index 000000000..5f6438c24 --- /dev/null +++ b/apps/beeptest/beeptest.app.js @@ -0,0 +1,274 @@ +var Layout = require("Layout"); + +// Beep Test Data +const BEET_TEST_DATA = [ + { shuttles: 7, timePerShuttle: 9.0, totalTime: 63.0, distancePerLevel: 140 }, + { shuttles: 8, timePerShuttle: 8.0, totalTime: 64.0, distancePerLevel: 160 }, + { shuttles: 8, timePerShuttle: 7.58, totalTime: 60.6, distancePerLevel: 160 }, + { shuttles: 9, timePerShuttle: 7.2, totalTime: 64.8, distancePerLevel: 180 }, + { shuttles: 9, timePerShuttle: 6.86, totalTime: 61.7, distancePerLevel: 180 }, + { + shuttles: 10, + timePerShuttle: 6.55, + totalTime: 65.5, + distancePerLevel: 200, + }, + { + shuttles: 10, + timePerShuttle: 6.26, + totalTime: 62.6, + distancePerLevel: 200, + }, + { shuttles: 11, timePerShuttle: 6.0, totalTime: 66.0, distancePerLevel: 220 }, + { + shuttles: 11, + timePerShuttle: 5.76, + totalTime: 63.4, + distancePerLevel: 220, + }, + { + shuttles: 11, + timePerShuttle: 5.54, + totalTime: 60.9, + distancePerLevel: 220, + }, + { + shuttles: 12, + timePerShuttle: 5.33, + totalTime: 64.0, + distancePerLevel: 240, + }, + { + shuttles: 12, + timePerShuttle: 5.14, + totalTime: 61.7, + distancePerLevel: 240, + }, + { + shuttles: 13, + timePerShuttle: 4.97, + totalTime: 64.6, + distancePerLevel: 260, + }, + { shuttles: 13, timePerShuttle: 4.8, totalTime: 62.4, distancePerLevel: 260 }, + { + shuttles: 13, + timePerShuttle: 4.65, + totalTime: 60.4, + distancePerLevel: 260, + }, + { shuttles: 14, timePerShuttle: 4.5, totalTime: 63.0, distancePerLevel: 280 }, + { + shuttles: 14, + timePerShuttle: 4.36, + totalTime: 61.1, + distancePerLevel: 280, + }, + { + shuttles: 15, + timePerShuttle: 4.24, + totalTime: 63.5, + distancePerLevel: 300, + }, + { + shuttles: 15, + timePerShuttle: 4.11, + totalTime: 61.7, + distancePerLevel: 300, + }, + { shuttles: 16, timePerShuttle: 4.0, totalTime: 64.0, distancePerLevel: 320 }, + { + shuttles: 16, + timePerShuttle: 3.89, + totalTime: 62.3, + distancePerLevel: 320, + }, +]; + +// VO2max Data +const VO2MAX_DATA = [ + { level: 1, vo2max: 16.7 }, + { level: 2, vo2max: 23.0 }, + { level: 3, vo2max: 26.2 }, + { level: 4, vo2max: 29.3 }, + { level: 5, vo2max: 32.5 }, + { level: 6, vo2max: 35.7 }, + { level: 7, vo2max: 38.8 }, + { level: 8, vo2max: 42.0 }, + { level: 9, vo2max: 45.1 }, + { level: 10, vo2max: 48.3 }, + { level: 11, vo2max: 51.5 }, + { level: 12, vo2max: 54.6 }, + { level: 13, vo2max: 57.8 }, + { level: 14, vo2max: 60.9 }, + { level: 15, vo2max: 64.1 }, + { level: 16, vo2max: 67.3 }, + { level: 17, vo2max: 70.4 }, + { level: 18, vo2max: 73.6 }, + { level: 19, vo2max: 76.7 }, + { level: 20, vo2max: 79.9 }, + { level: 21, vo2max: 83.0 }, +]; + +let currentLevel = 0; +let currentShuttle = 0; +let timeRemaining = 0; +let intervalId; +let beepTestLayout; +let testState = "start"; // 'start' | 'running' | 'result' + +function initBeepTestLayout() { + beepTestLayout = new Layout( + { + type: "v", + c: [ + { type: "txt", font: "30%", pad: 0, label: "Start Test", id: "status" }, + { type: "txt", font: "15%", pad: 0, label: "", id: "level" }, + { type: "txt", font: "10%", pad: 0, label: "", id: "vo2max" }, // Smaller font for VO2max + { type: "txt", font: "10%", pad: 0, label: "", id: "distance" }, // Smaller font for Distance + ], + }, + { + btns: [ + { + label: "Start/Stop", + cb: (l) => { + if (testState === "start") { + startTest(); + } else if (testState === "running") { + stopTest(); + } else { + showStartScreen(); + } + }, + }, + ], + }, + ); +} + +function showStartScreen() { + testState = "start"; + g.clear(); + beepTestLayout.clear(beepTestLayout.status); + beepTestLayout.status.label = "Start\nTest"; + beepTestLayout.clear(beepTestLayout.level); + beepTestLayout.level.label = ""; + beepTestLayout.clear(beepTestLayout.vo2max); // Clear VO2max text + beepTestLayout.vo2max.label = ""; + beepTestLayout.clear(beepTestLayout.distance); // Clear Distance text + beepTestLayout.distance.label = ""; + beepTestLayout.render(); +} + +function startTest() { + testState = "running"; + currentLevel = 0; + currentShuttle = 0; + Bangle.buzz(2000); // Buzz for 2 seconds at the start of the test + runLevel(); +} + +function runLevel() { + if (currentLevel >= BEET_TEST_DATA.length) { + stopTest(); + return; + } + + const levelData = BEET_TEST_DATA[currentLevel]; + timeRemaining = levelData.timePerShuttle * 1000; // Convert to milliseconds + updateDisplay(); + + if (intervalId) clearInterval(intervalId); + intervalId = setInterval(() => { + if (timeRemaining <= 0) { + currentShuttle++; + Bangle.buzz(100); // Short buzz after each shuttle + + if (currentShuttle >= levelData.shuttles) { + // Buzz longer or twice at the end of each level + Bangle.buzz(1000); // Buzz for 1 second at level end + setTimeout(() => Bangle.buzz(1000), 500); // Buzz again after 0.5 seconds + currentLevel++; + currentShuttle = 0; + runLevel(); + return; + } + + timeRemaining = levelData.timePerShuttle * 1000; // Reset to original time for the next shuttle + } + + updateDisplay(); + timeRemaining -= 100; // Decrement time by 100 milliseconds + }, 100); // Update every 100 milliseconds +} + +function updateDisplay() { + g.clear(); // Clear the entire screen + beepTestLayout.status.label = formatTime(timeRemaining); + beepTestLayout.level.label = `Level: ${currentLevel + 1}.${currentShuttle + 1}`; + beepTestLayout.render(); +} + +function stopTest() { + g.clear(); // Clear the entire screen + testState = "result"; + clearInterval(intervalId); + + // Determine previous level and shuttle + let prevLevel = currentLevel; + let prevShuttle = currentShuttle; + + if (prevShuttle === 0) { + if (prevLevel > 0) { + prevLevel--; + prevShuttle = BEET_TEST_DATA[prevLevel].shuttles - 1; + } else { + prevShuttle = 0; + } + } else { + prevShuttle--; + } + + // Determine VO2max and total distance + const vo2max = getVO2max(prevLevel + 1); + const totalDistance = calculateTotalDistance(prevLevel + 1); + + beepTestLayout.clear(beepTestLayout.status); + beepTestLayout.status.label = "Result"; + beepTestLayout.clear(beepTestLayout.level); + beepTestLayout.level.label = `Level: ${prevLevel + 1}.${prevShuttle + 1}`; + beepTestLayout.clear(beepTestLayout.vo2max); + beepTestLayout.vo2max.label = `VO2max: ${vo2max}`; + beepTestLayout.clear(beepTestLayout.distance); + beepTestLayout.distance.label = `Distance: ${totalDistance} m`; + beepTestLayout.render(); +} + +function getVO2max(level) { + const result = VO2MAX_DATA.find((item) => item.level === level); + return result ? result.vo2max : "N/A"; +} + +function calculateTotalDistance(level) { + // Calculate the total number of shuttles completed + let totalShuttles = 0; + for (let i = 0; i < level - 1; i++) { + totalShuttles += BEET_TEST_DATA[i].shuttles; + } + const levelData = BEET_TEST_DATA[level - 1]; + totalShuttles += levelData.shuttles; // Add the shuttles completed in the current level + const distancePerShuttle = 20; // Distance per shuttle in meters + return totalShuttles * distancePerShuttle; // Total distance +} + +function formatTime(milliseconds) { + let seconds = Math.floor(milliseconds / 1000); + let tenths = Math.floor((milliseconds % 1000) / 100); // Get tenths of a second + return (seconds < 10 ? "" : "") + seconds + "." + tenths; // Display only the tenths digit +} + +// Initialize the app +Bangle.setLCDPower(1); // Keep the watch LCD lit up +initBeepTestLayout(); +showStartScreen(); diff --git a/apps/beeptest/beeptest.img b/apps/beeptest/beeptest.img new file mode 100644 index 0000000000000000000000000000000000000000..9d0fbe59d2ea33ec734e60698f1b524d4a6ea3b5 GIT binary patch literal 588 zcmb7=u?>ST5Je9pH`I8C6e&<9@CZcW_F$yc9wRG6nItU|`QPUQm&vE#S^WR+%rre^ z-#^>+_S)nh<.png) + +## Usage + +Describe how to use it + +## Features + +Name the function + +## Controls + +Name the buttons and what they are used for + +## Requests + +Name who should be contacted for support/update requests + +## Creator + +Your name From 405c91b89d064bf9c03d2390288cc489e18df7a1 Mon Sep 17 00:00:00 2001 From: blade Date: Sat, 13 Jul 2024 18:48:40 +1200 Subject: [PATCH 042/133] change icon --- apps/beeptest/app-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/beeptest/app-icon.js b/apps/beeptest/app-icon.js index 907c45577..de5ca57f2 100644 --- a/apps/beeptest/app-icon.js +++ b/apps/beeptest/app-icon.js @@ -1,5 +1,5 @@ require("heatshrink").decompress( atob( - "MDDEAP//8/wsMtP8DDIrMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAiIiIiAAAAAAAAAAAAAAAAABEQAAAAAAAiAAAiAAAAAAAAAAAAAAAREREREAAAAAIgAAACIAAAAAAAAAAAAREREREREQAAAAIgAAACIAAAAAAAAAAAEREREREREREAAAIgAAACIAAAAAAAAAAAEREREQAREREQAAIgAAACIAAAAAAAAAAAAREAAAABEREREAAiAAAiAAAAAAAAAAAAAAAAAAAAEREREQAiIiIiAAAAAAAAAAAAAAAAAAAAARERERAAIiIAAAAAAAAAAAAAAAAAAAAAAREREREAAAAAAAAAAAAAAAAAAAAAAAAAAREREREQAAAAAAAAAAAAAAAAAAAAAAAAMREREREQAAAAAAAAAAIiIiIiIiIiAAAAERERERERAAAAAAAAAAIiIiIiIiIiAAABERERERERAAAAEQAAAAAAAAAAAAAAAAABEREREREREAABERAAAAAAAAAAAAAAAAAREREREREREAARERAAAAAAAAAAAAAAAAEREREREREREQEREQAAAAAAAAAAAAAAAAEREREREQEREREREAAAAAIiIiIiIgAAABEREREREAARERERAAAAAAIiIiIiIgAAARERERERAAABEREQAAAAAAAAAAAAAAAAARERERERAAAAEREAAAAAAAAAAAAAAAAAEREREREQAAAAARAAAAAAAAAAAAAAAAAAEREREREAAAAAAAAAAAAAAAAAAAAAAAABEREREREAAAAAAAAAAAAAAAIiIiIAAAAREREREREQAAAAAAAAAAAAAAIiIiIAAAARERERERERAAAAAAAAAAAAAAAAAAAAAAERERERERERAAAAAAAAAAAAAAAAAAAAAAEREREREREREAAAAAAAAAAAAAAAAAAAABEREREREREREAAAAAAAAAAAAAAAAAAAABEREREREREREQAAAAAAAAAAAAAAAAAAAREREREREREREQAAAAAAAAAAAAAAAAAAEREREREREREREAAAAAAAAAAAAAAAAAAAERERAAAAAAAAAAAAAAAAAAAAAAAAAAABEREQAAAAAAAAAAAAAAAAAAAAAAAAAAAREREAAAAAAAAAAAAAAAAAAAAAAAAAAAARERAAAAAAAAAAAAAAAAAAAAAAAAAAAAERERAAAAAAAAAAAAAAAAAAAAAAAAAAAAEREwAAAAAAAAAAAAAAAAAAAAAAAAAAABEREAAAAAAAAAAAAAAAAAAAAAAAAAAAABERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "mEwwcBkmSpIC/yEACJ5bCBZGf/IFDgVJCJH/AAPkAoMgiQCDCIeTCAX//gRLz4RD//yCJvyv///w1MAoNPCoJcBLJRcFPpgCBn5uCUJoRCNwQRO/4RNQAYLFwBOCKAaSFCJR9CCIcnCgoCDCIikBn4CBCI6zBCIgCCCIzsEAAPPJQwRHQAxZDhL+E/MnUgoRFEQfJAoXkUJARFFIKwIF4RlDyf5CJibJcw4RILITUFTZARILIQ1JaggRSNBLmGCKSeJfA4RKcwY1NCILyDARbyLag4gMCIZ3LcwoRRX5YCDepIRI+QRPn6MOAWg=", ), ); From 7c733e1be87a0f7c1aaca0e36780613aa7e24a58 Mon Sep 17 00:00:00 2001 From: blade Date: Sat, 13 Jul 2024 19:00:03 +1200 Subject: [PATCH 043/133] change icon --- apps/beeptest/beeptest.png | Bin 0 -> 1032 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/beeptest/beeptest.png diff --git a/apps/beeptest/beeptest.png b/apps/beeptest/beeptest.png new file mode 100644 index 0000000000000000000000000000000000000000..ee892bbe24c1f4b2b495235658b084a787adbe4d GIT binary patch literal 1032 zcmV+j1o!)iP)dh%H(evdDpv9TfhUXmJU_fpkm27pJQ@60*ct8-g8;!yM2 z);B-g{P+a^YL|dX2f2H;{;dyKsrMLokop|aB*(E#Eg=~kPZ4*by(L8Sjy0$?>O5g< zU~A-s&zbS@SD9$WA!3e+zBW@s zR6r_~S-PWJixY`I!fL|(T*IZ9kp#X zi{QE#qJwSeLawf3!N8r2*`GAPhPr5AgI4OdmL~-qYFig_b)6c~SdQ#3Dg{$6zo>!R z%O<~4m@{WKCcvYN0?wuFkpQnV@K4vtTWH{7#>`hu6g}_Shk@PoZw4EkHtc^djboXk zMSd*$38qw9qk-wnwLdntzhh(yjxN-y>pTL!$}^;x=nr%1)cTGH@El#p)pZOo^w?;* zCM^SBihh75`Xw*<`Jq$;yq=Qh@$tROA-^Dgwo|w#;A2 Date: Sat, 13 Jul 2024 19:03:39 +1200 Subject: [PATCH 044/133] readme --- apps/beeptest/beeptest.img | Bin 588 -> 0 bytes apps/beeptest/readme.md | 23 ++++++++++------------- 2 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 apps/beeptest/beeptest.img diff --git a/apps/beeptest/beeptest.img b/apps/beeptest/beeptest.img deleted file mode 100644 index 9d0fbe59d2ea33ec734e60698f1b524d4a6ea3b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 588 zcmb7=u?>ST5Je9pH`I8C6e&<9@CZcW_F$yc9wRG6nItU|`QPUQm&vE#S^WR+%rre^ z-#^>+_S)nh<.png) +Beep Test ## Usage -Describe how to use it +Mark out a 20m space +Click the side button to start the test +Shuttle run between your markers when the watch buzzes +Push the button when you need to stop ## Features -Name the function +Buzzing on each shuttle run +Results page with vO2max and total distance covered ## Controls -Name the buttons and what they are used for +Side button starts, stops and resets the app. ## Requests -Name who should be contacted for support/update requests +bb0x88 on giuthub ## Creator -Your name +Blade From 1aec93ff39ebbff90d0b529e28d0fba14c15fd56 Mon Sep 17 00:00:00 2001 From: blade Date: Sat, 13 Jul 2024 19:06:28 +1200 Subject: [PATCH 045/133] readme --- apps/beeptest/{beeptest.app.js => beeptest.js} | 0 apps/beeptest/metadata.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename apps/beeptest/{beeptest.app.js => beeptest.js} (100%) diff --git a/apps/beeptest/beeptest.app.js b/apps/beeptest/beeptest.js similarity index 100% rename from apps/beeptest/beeptest.app.js rename to apps/beeptest/beeptest.js diff --git a/apps/beeptest/metadata.json b/apps/beeptest/metadata.json index 7dbbb0759..87a815c3e 100644 --- a/apps/beeptest/metadata.json +++ b/apps/beeptest/metadata.json @@ -4,12 +4,12 @@ "shortName": "Beep Test", "version": "0.01", "description": "Aerobic fitness test created by Léger & Lambert", - "icon": "app.png", + "icon": "beeptest.png", "tags": "", "supports": ["BANGLEJS2"], "readme": "README.md", "storage": [ - { "name": "beeptest.app.js", "url": "app.js" }, + { "name": "beeptest.app.js", "url": "beeptest.js" }, { "name": "beeptest.img", "url": "app-icon.js", "evaluate": true } ] } From 18e24537705c7cc1b8563ff5bef7d6b0c98c67b4 Mon Sep 17 00:00:00 2001 From: blade Date: Sat, 13 Jul 2024 19:08:37 +1200 Subject: [PATCH 046/133] readme --- apps/beeptest/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/beeptest/readme.md b/apps/beeptest/readme.md index 9fc715a81..dcb1f1574 100644 --- a/apps/beeptest/readme.md +++ b/apps/beeptest/readme.md @@ -12,7 +12,7 @@ Push the button when you need to stop ## Features Buzzing on each shuttle run -Results page with vO2max and total distance covered +Results page with vO2max and total distance covered. ## Controls From 609b050e5018da2be9eb163b00fec3bac227d9cd Mon Sep 17 00:00:00 2001 From: blade Date: Sat, 13 Jul 2024 19:10:18 +1200 Subject: [PATCH 047/133] readme --- apps/beeptest/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/beeptest/metadata.json b/apps/beeptest/metadata.json index 87a815c3e..c5d91ba55 100644 --- a/apps/beeptest/metadata.json +++ b/apps/beeptest/metadata.json @@ -7,7 +7,7 @@ "icon": "beeptest.png", "tags": "", "supports": ["BANGLEJS2"], - "readme": "README.md", + "readme": "readme.md", "storage": [ { "name": "beeptest.app.js", "url": "beeptest.js" }, { "name": "beeptest.img", "url": "app-icon.js", "evaluate": true } From 834d714e5820a5e44ce12e87c3eccd3664dbaa8b Mon Sep 17 00:00:00 2001 From: blade Date: Sat, 13 Jul 2024 19:11:53 +1200 Subject: [PATCH 048/133] readme --- apps/beeptest/metadata.json | 2 +- apps/beeptest/readme.md | 27 --------------------------- 2 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 apps/beeptest/readme.md diff --git a/apps/beeptest/metadata.json b/apps/beeptest/metadata.json index c5d91ba55..87a815c3e 100644 --- a/apps/beeptest/metadata.json +++ b/apps/beeptest/metadata.json @@ -7,7 +7,7 @@ "icon": "beeptest.png", "tags": "", "supports": ["BANGLEJS2"], - "readme": "readme.md", + "readme": "README.md", "storage": [ { "name": "beeptest.app.js", "url": "beeptest.js" }, { "name": "beeptest.img", "url": "app-icon.js", "evaluate": true } diff --git a/apps/beeptest/readme.md b/apps/beeptest/readme.md deleted file mode 100644 index dcb1f1574..000000000 --- a/apps/beeptest/readme.md +++ /dev/null @@ -1,27 +0,0 @@ -# App Name - -Beep Test - -## Usage - -Mark out a 20m space -Click the side button to start the test -Shuttle run between your markers when the watch buzzes -Push the button when you need to stop - -## Features - -Buzzing on each shuttle run -Results page with vO2max and total distance covered. - -## Controls - -Side button starts, stops and resets the app. - -## Requests - -bb0x88 on giuthub - -## Creator - -Blade From c0fffbe9664b6b595fe3c2c84cc7cda155f47b12 Mon Sep 17 00:00:00 2001 From: blade Date: Sat, 13 Jul 2024 19:12:38 +1200 Subject: [PATCH 049/133] README --- apps/beeptest/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 apps/beeptest/README.md diff --git a/apps/beeptest/README.md b/apps/beeptest/README.md new file mode 100644 index 000000000..dcb1f1574 --- /dev/null +++ b/apps/beeptest/README.md @@ -0,0 +1,27 @@ +# App Name + +Beep Test + +## Usage + +Mark out a 20m space +Click the side button to start the test +Shuttle run between your markers when the watch buzzes +Push the button when you need to stop + +## Features + +Buzzing on each shuttle run +Results page with vO2max and total distance covered. + +## Controls + +Side button starts, stops and resets the app. + +## Requests + +bb0x88 on giuthub + +## Creator + +Blade From 9402124a3fd09aeefe2c536f0678a85d25f81d42 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 8 Jul 2024 19:00:00 +0100 Subject: [PATCH 050/133] splitsw: add new stopwatch app with split timer Signed-off-by: James Taylor --- apps/splitsw/ChangeLog | 1 + apps/splitsw/README.md | 30 +++++++ apps/splitsw/app-icon.js | 1 + apps/splitsw/app.js | 167 ++++++++++++++++++++++++++++++++++++ apps/splitsw/app.png | Bin 0 -> 1566 bytes apps/splitsw/metadata.json | 16 ++++ apps/splitsw/screenshot.png | Bin 0 -> 2927 bytes 7 files changed, 215 insertions(+) create mode 100644 apps/splitsw/ChangeLog create mode 100644 apps/splitsw/README.md create mode 100644 apps/splitsw/app-icon.js create mode 100644 apps/splitsw/app.js create mode 100644 apps/splitsw/app.png create mode 100644 apps/splitsw/metadata.json create mode 100644 apps/splitsw/screenshot.png diff --git a/apps/splitsw/ChangeLog b/apps/splitsw/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/splitsw/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/splitsw/README.md b/apps/splitsw/README.md new file mode 100644 index 000000000..5f8fbd54b --- /dev/null +++ b/apps/splitsw/README.md @@ -0,0 +1,30 @@ +# Stopwatch with split times + +A basic stopwatch with support for split times. + +![](screenshot.png) + +## Features + +Implemented: + +- Start stopwatch +- Stop stopwatch +- Show split times +- Reset stopwatch +- Keep display unlocked + +Future: + +- Save state and restore running stopwatch when it reopens +- View all split times +- Duplicate Start/Stop and/or Reset/Split button on the physical button +- Settings, e.g. what the physical button does, and whether to keep the backlight on + +## Creator + +James Taylor ([jt-nti](https://github.com/jt-nti)) + +## Icons + +The same icons as apps/stopwatch! diff --git a/apps/splitsw/app-icon.js b/apps/splitsw/app-icon.js new file mode 100644 index 000000000..32281b7ab --- /dev/null +++ b/apps/splitsw/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")) diff --git a/apps/splitsw/app.js b/apps/splitsw/app.js new file mode 100644 index 000000000..5d70b471d --- /dev/null +++ b/apps/splitsw/app.js @@ -0,0 +1,167 @@ +Bangle.loadWidgets(); +g.clear(true); +Bangle.drawWidgets(); + +Bangle.setLCDTimeout(undefined); + +let renderIntervalId; +let startTime; +let stopTime; +let subtotal; +let currentSplitNumber; +let splitStartTime; +let splitSubtotal; + +var Layout = require("Layout"); +var layout = new Layout( { + type:"v", c: [ + {type:"txt", pad:4, font:"20%", label:"", id:"time", fillx:1}, + {type:"h", c: [ + {type:"btn", pad:4, font:"6x8:2", label:"Start", id:"startStop", cb: l=>startStop() }, + {type:"btn", pad:4, font:"6x8:2", label:"Reset", id:"resetSplit", cb: l=>resetSplit() } + ]}, + { + type:"v", pad:4, c: [ + {type:"txt", font:"6x8:2", label:"", id:"split", fillx:1}, + {type:"txt", font:"6x8:2", label:"", id:"prevSplit", fillx:1}, + ]}, + ] +}, { + lazy: true, + back: load, +}); + +// TODO The code in this function appears in various apps so it might be +// nice to add something to the time_utils module. (There is already a +// formatDuration function but that doesn't quite work the same way.) +const getTime = function(milliseconds) { + let hrs = Math.floor(milliseconds/3600000); + let mins = Math.floor(milliseconds/60000)%60; + let secs = Math.floor(milliseconds/1000)%60; + let tnth = Math.floor(milliseconds/100)%10; + let text; + + if (hrs === 0) { + text = ("0"+mins).slice(-2) + ":" + ("0"+secs).slice(-2) + "." + tnth; + } else { + text = ("0"+hrs) + ":" + ("0"+mins).slice(-2) + ":" + ("0"+secs).slice(-2); + } + + return text; +}; + +const renderIntervalCallback = function() { + if (startTime === undefined) { + return; + } + + updateStopwatch(); +}; + +const buzz = function() { + Bangle.buzz(50, 0.5); +}; + +const startStop = function() { + buzz(); + + if (layout.startStop.label === "Start") { + start(); + } else { + stop(); + } +}; + +const start = function() { + if (stopTime === undefined) { + startTime = Date.now(); + splitStartTime = startTime; + subtotal = 0; + splitSubtotal = 0; + currentSplitNumber = 1; + } else { + subtotal += stopTime - startTime; + splitSubtotal += stopTime - splitStartTime; + startTime = Date.now(); + splitStartTime = startTime; + stopTime = undefined; + } + + layout.startStop.label = "Stop"; + layout.resetSplit.label = "Split"; + updateStopwatch(); + + renderIntervalId = setInterval(renderIntervalCallback, 100); +}; + +const stop = function() { + stopTime = Date.now(); + + layout.startStop.label = "Start"; + layout.resetSplit.label = "Reset"; + updateStopwatch(); + + if (renderIntervalId !== undefined) { + clearInterval(renderIntervalId); + renderIntervalId = undefined; + } +}; + +const resetSplit = function() { + buzz(); + + if (layout.resetSplit.label === "Reset") { + reset(); + } else { + split(); + } +}; + +const reset = function() { + layout.startStop.label = "Start"; + layout.resetSplit.label = "Reset"; + layout.split.label = ""; + layout.prevSplit.label = ""; + + startTime = undefined; + stopTime = undefined; + subtotal = 0; + currentSplitNumber = 1; + splitStartTime = undefined; + splitSubtotal = 0; + + updateStopwatch(); +}; + +const split = function() { + const splitTime = Date.now() - splitStartTime + splitSubtotal; + layout.prevSplit.label = "#" + currentSplitNumber + " " + getTime(splitTime); + + splitStartTime = Date.now(); + splitSubtotal = 0; + currentSplitNumber++; + + updateStopwatch(); +}; + +const updateStopwatch = function() { + let elapsedTime; + + if (startTime === undefined) { + elapsedTime = 0; + } else { + elapsedTime = Date.now() - startTime + subtotal; + } + + layout.time.label = getTime(elapsedTime); + + if (splitStartTime !== undefined) { + const splitTime = Date.now() - splitStartTime + splitSubtotal; + layout.split.label = "#" + currentSplitNumber + " " + getTime(splitTime); + } + + layout.render(); + // layout.debug(); +}; + +updateStopwatch(); diff --git a/apps/splitsw/app.png b/apps/splitsw/app.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffe73b7f172f7019486231d5bc0ba327c13a67 GIT binary patch literal 1566 zcmV+(2I2XMP)rZGih zXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=wMNO+ffoiD_T1#yUim(D(ys%vMfQPei zrVqOZR|L*1ZtO$(Kh4bfZvQzm-^@2KjcNS9p~TbJ{=+BFgR;y*?EJRZOY-C8(-tp7 zVORza@KQWI#+kt5$25We8u2D@-cmyFb37f!H4B+o`PybN-uT(0hvM!pG2RAaMai~Z zb8H5+fL|F7l^*}>gRWTg<2NIR8$39)H7FE$0)fri3@0JG9e^ZV$1ylSfBMXW2&%E$ zPO&yWxOMB+q<~{)6;Ked;unf2IeFmyUmqWV%KAJEV+T+Q%#hR?1dbK`*5J(O?<*bi zO;Z%!IDVk)sEnbh6R5b5f)d(`!dpynDcXcVZikO&eb-4ajOeKPP8=vpld&G6#BUZr!wEAw+((0R3 zrXbzLH_tucuqlj0hL$qu+9ey79D&OEyhyeIpghm3SOpJZ0ylh<6M!&@vIX9#5%D$^ zHQ6$u$q@+W`9uhB*iAW^QhKevc3RtRO1aoFFEin3XYxR#>%x|>M@G*&ki!ig!iN+9 z<#}G0?8a$r^U>1QCMBx?`|`Zb`y*l_6*ZQ>*_wbuTSMf8&2iX+vZ=DKzn?&FFTtQn zcOW38Q<&~zjy+M0be$yF)>!FS6Hq8Jo3G6BI3^Q~Myv1s(rIb4-UlX1veWFN9dY0H zM6f0xl*MMVIK!PJ8_3nan@8J?SO1b#qd3!TkLoU-P%r|SKQP~H&P}mTCOL|}{(ipr z>d0vS%cB+Cx!WzJJ2&|OAM+=YGa>N@@R-ePyWG|3Y)vhL_wLbMu#!7PtICQ=hz{kT!_!z`=nUlJl03sr&j0}&?P-3E-D?`>v1Tssg-)!E}hoy|JDk@}lRu&l- zYRIfV%j~QyRj3EpMT1a)jt8Ss%SYLw_Cy$ zv}^iU-;=_cfKaZ0SOQSht?Gs8E~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@! z_e$Lm9|hmt>bvj55SBaNntrAr?+kNN?GIAf(_O<-+) z5IAObcQpIe7!!pQ3<2AlI-_PsIm(6=ma`>W50nETfTn>V*P-_A5exa4aj@g=$hAS; zoP8Zd*(akXM__&7H4yvF?#}Ce>f^YS>UvamU;ih1=wsFuSY{xIr6LQ8JwOA1AxJ1~ z^l9-RQP-pLYNJmx>tf&nGX~z3)sL;HKUp^iAua-QAzWIPE%35s-uOlRWH1CA?7VC2 zZtlWBm=oEKVJu<83hC#?DLkvxEdp_h2nm2Zr(G@2&2bfG$kht9Ju2IqI@PE(573Um z8a5O*#uCodUgj!lva#8|1C~Y)XWJ9ib=hh5;!L+aCpigk)WcS%7NR;3)K7Nx>QSlE zjN%tYjFMPp{?P~koIaf#emxNH08UH!@u4HcM>q%HlYK@Ri${Olt*FTk7vN$e@QR9l#uqzNePq-K{A831lR^T#FgJGPOIi#jBjcHipUslrouSl-W Q%m4rY07*qoM6N<$g48qYF8}}l literal 0 HcmV?d00001 diff --git a/apps/splitsw/metadata.json b/apps/splitsw/metadata.json new file mode 100644 index 000000000..d5ae19347 --- /dev/null +++ b/apps/splitsw/metadata.json @@ -0,0 +1,16 @@ +{ "id": "splitsw", + "name": "Stopwatch with split times", + "shortName":"Stopwatch", + "version":"0.01", + "description": "A basic stopwatch with support for split times", + "icon": "app.png", + "tags": "tool,outdoors,health", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [{ "url": "screenshot.png" }], + "allow_emulator": true, + "storage": [ + {"name":"splitsw.app.js","url":"app.js"}, + {"name":"splitsw.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/splitsw/screenshot.png b/apps/splitsw/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..184bc4fbcd7e9672ae60f2fb7a769ee7c173a5a3 GIT binary patch literal 2927 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 literal 0 HcmV?d00001 From a42c8036e3cddf24560ad352ca0f8232df7e7e06 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 31 May 2024 19:20:13 +0200 Subject: [PATCH 051/133] [] sixths: Cleanups, use let where possible. --- apps/sixths/README.md | 2 - apps/sixths/sixths.app.js | 78 +++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/apps/sixths/README.md b/apps/sixths/README.md index 17369c7a0..58f704512 100644 --- a/apps/sixths/README.md +++ b/apps/sixths/README.md @@ -54,5 +54,3 @@ possibly allowing scrolling). Todo: *) only turn on compass when needed - -*) adjust draw timeouts to save power \ No newline at end of file diff --git a/apps/sixths/sixths.app.js b/apps/sixths/sixths.app.js index 00c83153f..96f50c04b 100644 --- a/apps/sixths/sixths.app.js +++ b/apps/sixths/sixths.app.js @@ -3,13 +3,11 @@ // Options you'll want to edit const rest_altitude = 354; -const geoid_to_sea_level = 0; // Maybe BangleJS2 already compensates? const W = g.getWidth(); const H = g.getHeight(); -var cx = 100, cy = 105, sc = 70, -temp = 0, alt = 0, bpm = 0; +var cx = 100, cy = 105, sc = 70, temp = 0, alt = 0, bpm = 0; var buzz = "", /* Set this to transmit morse via vibrations */ inm = "", l = "", /* For incoming morse handling */ in_str = "", @@ -45,15 +43,15 @@ var cur_mark = null; // Icons var icon_alt = "\0\x08\x1a\1\x00\x00\x00\x20\x30\x78\x7C\xFE\xFF\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00"; -var icon_m = "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00"; +//var icon_m = "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00"; var icon_km = "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00"; var icon_kph = "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\xFF\x00\xC3\xC3\xFF\xC3\xC3"; var icon_c = "\0\x08\x1a\1\x00\x00\x60\x90\x90\x60\x00\x7F\xFF\xC0\xC0\xC0\xC0\xC0\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; function toMorse(x) { - var r = ""; + let r = ""; for (var i = 0; i < x.length; i++) { - var c = x[i]; + let c = x[i]; if (c == " ") { r += " "; continue; @@ -113,20 +111,15 @@ function gpsHandleFix(fix) { doBuzz(" ."); prev_fix = fix; } - if (0) { - /* GPS altitude fluctuates a lot, not really usable */ - alt_adjust = cur_altitude - (fix.alt + geoid_to_sea_level); - alt_adjust_mode = "g"; - } if (1) { let now1 = Date(); let now2 = fix.time; - var n1 = now1.getMinutes() * 60 + now1.getSeconds(); - var n2 = now2.getMinutes() * 60 + now2.getSeconds(); + let n1 = now1.getMinutes() * 60 + now1.getSeconds(); + let n2 = now2.getMinutes() * 60 + now2.getSeconds(); debug2 = "te "+(n2-n1)+"s"; } loggps(fix); - var d = calcDistance(fix, prev_fix); + let d = calcDistance(fix, prev_fix); if (d > 30) { prev_fix = fix; gps_dist += d/1000; @@ -135,7 +128,7 @@ function gpsHandleFix(fix) { function gpsHandle() { let msg = ""; if (!last_restart) { - var d = (getTime()-last_pause); + let d = (getTime()-last_pause); if (last_fix) msg = "PL"+ fmtTimeDiff(getTime()-last_fix); else @@ -146,7 +139,7 @@ function gpsHandle() { gpsRestart(); } } else { - var fix = Bangle.getGPSFix(); + let fix = Bangle.getGPSFix(); if (fix && fix.fix && fix.lat) { gpsHandleFix(fix); msg = fix.speed.toFixed(1) + icon_kph; @@ -167,8 +160,8 @@ function gpsHandle() { } } - var d = (getTime()-last_restart); - var d2 = (getTime()-last_fstart); + let d = (getTime()-last_restart); + let d2 = (getTime()-last_fstart); print("gps on, restarted ", d, gps_needed, d2, fix.lat); if (getTime() > gps_speed_limit && (d > gps_needed || (last_fstart && d2 > 10))) { @@ -192,7 +185,7 @@ function markNew() { } function markHandle() { let m = cur_mark; - var msg = m.name + ">" + fmtTimeDiff(getTime()- m.time); + let msg = m.name + ">" + fmtTimeDiff(getTime()- m.time); if (m.fix && m.fix.fix) { let s = fmtDist(calcDistance(m.fix, prev_fix)/1000) + icon_km; msg += " " + s; @@ -230,9 +223,9 @@ function inputHandler(s) { return; } switch(s) { - case 'B': + case 'B': { s = ' B'; - var bat = E.getBattery(); + let bat = E.getBattery(); if (bat > 45) s += 'E'; else @@ -240,6 +233,7 @@ function inputHandler(s) { doBuzz(toMorse(s)); show("Bat "+bat+"%", 60); break; + } case 'F': gpsOff(); show("GPS off", 3); break; case 'G': gpsOn(); gps_limit = getTime() + 60*60*4; show("GPS on", 3); break; case 'I': @@ -253,13 +247,14 @@ function inputHandler(s) { case 'N': mode = 1; show(">", 10); mode_time = getTime(); break; case 'O': aload("orloj.app.js"); break; case 'S': gpsOn(); gps_limit = getTime() + 60*30; gps_speed_limit = gps_limit; show("GPS on", 3); break; - case 'T': + case 'T': { s = ' T'; - var d = new Date(); + let d = new Date(); s += d.getHours() % 10; s += add0(d.getMinutes()); doBuzz(toMorse(s)); break; + } case 'R': aload("run.app.js"); break; case 'Y': doBuzz(buzz); Bangle.resetCompass(); break; } @@ -378,7 +373,7 @@ function loggps(fix) { } function hourly() { print("hourly"); - var s = ' T'; + let s = ' T'; if (is_active) doBuzz(toMorse(s)); logstamp(""); @@ -388,8 +383,8 @@ function show(msg, timeout) { } function fivemin() { print("fivemin"); - var s = ' B'; - var bat = E.getBattery(); + let s = ' B'; + let bat = E.getBattery(); if (bat < 25) { if (is_active) doBuzz(toMorse(s)); @@ -470,7 +465,7 @@ function drawDot(h, d, s) { g.fillCircle(x,y, 10); } function drawBackground() { - var acc = Bangle.getAccel(); + let acc = Bangle.getAccel(); is_level = (acc.z < -0.95); if (is_level) { let obj = Bangle.getCompass(); @@ -551,7 +546,7 @@ function draw() { let alt_adjust = cur_altitude - rest_altitude; let abs = Math.abs(alt_adjust); print("adj", alt_adjust); - var o = Bangle.getOptions(); + let o = Bangle.getOptions(); if (abs > 10 && abs < 150) { let a = 0.01; // FIXME: draw is called often compared to alt reading @@ -588,16 +583,16 @@ function draw_all() { let now = new Date(); g.drawString(now.getHours() + ":" + add0(now.getMinutes()) + ":" + add0(now.getSeconds()), 10, 40); - var acc = Bangle.getAccel(); + let acc = Bangle.getAccel(); let ax = 0 + acc.x, ay = 0.75 + acc.y, az = 0.75 + acc.y; let diff = ax * ax + ay * ay + az * az; diff = diff * 3; if (diff > 1) diff = 1; - var co = Bangle.getCompass(); - var step = Bangle.getStepCount(); - var bat = E.getBattery(); + let co = Bangle.getCompass(); + let step = Bangle.getStepCount(); + let bat = E.getBattery(); Bangle.getPressure().then((x) => { alt = x.altitude; temp = x.temperature; }, print); @@ -618,7 +613,7 @@ function draw_all() { g.fillCircle(cx + sc * co.dx / 300, cy + sc * co.dz / 400, 5); } if (1) { - h = co.heading / 360 * 2 * Math.PI; + let h = co.heading / 360 * 2 * Math.PI; g.setColor(0, 0, 0.5); g.fillCircle(cx + sc * Math.sin(h), cy + sc * Math.cos(h), 5); } @@ -635,10 +630,10 @@ function draw_all() { queueDraw(); } function accelTask() { - var tm = 100; - var acc = Bangle.getAccel(); - var en = !Bangle.isLocked(); - var msg; + let tm = 100; + let acc = Bangle.getAccel(); + let en = !Bangle.isLocked(); + let msg = ""; if (en && acc.z < -0.95) { msg = "Level"; doBuzz(".-.."); @@ -654,14 +649,14 @@ function accelTask() { doBuzz("..-"); tm = 3000; } - + print(msg); setTimeout(accelTask, tm); } function buzzTask() { if (buzz != "") { - var now = buzz[0]; + let now = buzz[0]; buzz = buzz.substring(1); - var dot = 100; + let dot = 100; if (now == " ") { setTimeout(buzzTask, 300); } else if (now == ".") { @@ -730,7 +725,8 @@ function start() { draw(); buzzTask(); - //accelTask(); + if (0) + accelTask(); if (1) { last_acc = Bangle.getAccel(); From dc3742508ed546192393de2cd2e8c184b6fe14e6 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sun, 2 Jun 2024 22:37:38 +0200 Subject: [PATCH 052/133] [] sixths: reduce logging to reduce flash wear. --- apps/sixths/sixths.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sixths/sixths.app.js b/apps/sixths/sixths.app.js index 96f50c04b..66c411e63 100644 --- a/apps/sixths/sixths.app.js +++ b/apps/sixths/sixths.app.js @@ -376,7 +376,7 @@ function hourly() { let s = ' T'; if (is_active) doBuzz(toMorse(s)); - logstamp(""); + //logstamp(""); } function show(msg, timeout) { note = msg; From f8505a93554dd832b62363d178d1d5f816c1333d Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sun, 2 Jun 2024 23:12:06 +0200 Subject: [PATCH 053/133] [] sixths: start implementing reading waypoints from disk --- apps/sixths/README.md | 4 ++++ apps/sixths/sixths.app.js | 40 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/apps/sixths/README.md b/apps/sixths/README.md index 58f704512..e738249ad 100644 --- a/apps/sixths/README.md +++ b/apps/sixths/README.md @@ -54,3 +54,7 @@ possibly allowing scrolling). Todo: *) only turn on compass when needed + +*) only warn about battery low when it crosses thresholds, update battery low message. + +*) rename "show" to something else -- it collides with built-in \ No newline at end of file diff --git a/apps/sixths/sixths.app.js b/apps/sixths/sixths.app.js index 66c411e63..24ea60657 100644 --- a/apps/sixths/sixths.app.js +++ b/apps/sixths/sixths.app.js @@ -185,8 +185,11 @@ function markNew() { } function markHandle() { let m = cur_mark; - let msg = m.name + ">" + fmtTimeDiff(getTime()- m.time); - if (m.fix && m.fix.fix) { + let msg = m.name + ">"; + if (m.time) { + msg += fmtTimeDiff(getTime()- m.time); + } + if (prev_fix && prev_fix.fix && m.fix && m.fix.fix) { let s = fmtDist(calcDistance(m.fix, prev_fix)/1000) + icon_km; msg += " " + s; debug = "wp>" + s; @@ -207,6 +210,34 @@ function entryDone() { in_str = 0; mode = 0; } +var waypoints = [], sel_wp = 0; +function loadWPs() { + waypoints = require("Storage").readJSON(`waypoints.json`)||[{}]; + print("Have waypoints", waypoints); +} +function saveWPs() { + require("Storage").writeJSON(`waypoints.json`,waypoints); +} +function selectWP(i) { + sel_wp += i; + if (sel_wp < 0) + sel_wp = 0; + if (sel_wp >= waypoints.length) + sel_wp = waypoints.length - 1; + if (sel_wp < 0) { + show("No WPs", 60); + } + let wp = waypoints[sel_wp]; + cur_mark = {}; + cur_mark.name = wp.name; + cur_mark.gps_dist = 0; /* HACK */ + cur_mark.fix = {}; + cur_mark.fix.fix = 1; + cur_mark.fix.lat = wp.lat; + cur_mark.fix.lon = wp.lon; + show("WP:"+wp.name, 60); + print("Select waypoint: ", cur_mark); +} function inputHandler(s) { print("Ascii: ", s, s[0], s[1]); if (s[0] == '^') { @@ -234,6 +265,7 @@ function inputHandler(s) { show("Bat "+bat+"%", 60); break; } + case 'D': selectWP(1); break; case 'F': gpsOff(); show("GPS off", 3); break; case 'G': gpsOn(); gps_limit = getTime() + 60*60*4; show("GPS on", 3); break; case 'I': @@ -256,6 +288,7 @@ function inputHandler(s) { break; } case 'R': aload("run.app.js"); break; + case 'U': selectWP(-1); break; case 'Y': doBuzz(buzz); Bangle.resetCompass(); break; } } @@ -695,7 +728,7 @@ function lockHandler(locked) { } function queueDraw() { - if (getTime() - last_unlocked > 5*60) + if (getTime() - last_unlocked > 3*60) next = 60000; else next = 1000; @@ -724,6 +757,7 @@ function start() { } draw(); + loadWPs(); buzzTask(); if (0) accelTask(); From f9ed954f5eb50469c7a9d7473538e2ec90c1ae42 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 15 Jun 2024 00:21:48 +0200 Subject: [PATCH 054/133] [] sixths: update documentation, update version. --- apps/sixths/README.md | 33 +++++++++++++++++++++++++++------ apps/sixths/sixths.app.js | 4 ++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/sixths/README.md b/apps/sixths/README.md index e738249ad..1998d3d14 100644 --- a/apps/sixths/README.md +++ b/apps/sixths/README.md @@ -24,17 +24,35 @@ minutes, real distance will be usually higher than approximation. Useful gestures: -F -- disable GPS. -G -- enable GPS for 4 hours in low power mode. -N -- take a note and write it to the log. -S -- enable GPS for 30 minutes in high power mode. + B -- "Battery", show/buzz battery info +D -- "Down", previous waypoint +F -- "oFf", disable GPS. +G -- "Gps", enable GPS for 4 hours in low power mode. +I -- "Info", toggle info display + L -- "aLtimeter", load altimeter app +M -- "Mark", create mark from current position +N -- "Note", take a note and write it to the log. + O -- "Orloj", run orloj app + R -- "Run", run "runplus" app +S -- "Speed", enable GPS for 30 minutes in high power mode. + T -- "Time", buzz current time +U -- "Up", next waypoint +Y -- "compass", reset compass When application detects watch is being worn, it will use vibrations to communicate back to the user. +B -- battery low. E -- acknowledge, gesture understood. T -- start of new hour. +Three colored dots may appear on display. North is on the 12 o'clock +position (top of the display). + +red: this is direction to the waypoint. +green: this is direction you are moving into, according to GPS. +blue: this is direction top of watch faces, according to the compass. + Written by: [Pavel Machek](https://github.com/pavelmachek) ## Future Development @@ -55,6 +73,9 @@ Todo: *) only turn on compass when needed -*) only warn about battery low when it crosses thresholds, update battery low message. +*) only warn about battery low when it crosses thresholds, update +battery low message -*) rename "show" to something else -- it collides with built-in \ No newline at end of file +*) rename "show" to something else -- it collides with built-in + +*) adjust clock according to gps \ No newline at end of file diff --git a/apps/sixths/sixths.app.js b/apps/sixths/sixths.app.js index 24ea60657..7f71818d2 100644 --- a/apps/sixths/sixths.app.js +++ b/apps/sixths/sixths.app.js @@ -12,7 +12,7 @@ var buzz = "", /* Set this to transmit morse via vibrations */ inm = "", l = "", /* For incoming morse handling */ in_str = "", note = "(NOTEHERE)", - debug = "v1119", debug2 = "(otherdb)", debug3 = "(short)"; + debug = "v0.04.1", debug2 = "(otherdb)", debug3 = "(short)"; var mode = 0, mode_time = 0; // 0 .. normal, 1 .. note, 2.. mark name var disp_mode = 0; // 0 .. normal, 1 .. small time @@ -278,6 +278,7 @@ function inputHandler(s) { case 'M': mode = 2; show("M>", 10); cur_mark = markNew(); mode_time = getTime(); break; case 'N': mode = 1; show(">", 10); mode_time = getTime(); break; case 'O': aload("orloj.app.js"); break; + case 'R': aload("runplus.app.js"); break; case 'S': gpsOn(); gps_limit = getTime() + 60*30; gps_speed_limit = gps_limit; show("GPS on", 3); break; case 'T': { s = ' T'; @@ -287,7 +288,6 @@ function inputHandler(s) { doBuzz(toMorse(s)); break; } - case 'R': aload("run.app.js"); break; case 'U': selectWP(-1); break; case 'Y': doBuzz(buzz); Bangle.resetCompass(); break; } From 51bd82014907e1d41f85568e1ca4fd2519612ca2 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Wed, 19 Jun 2024 00:25:13 +0200 Subject: [PATCH 055/133] [] sixths: more todo items --- apps/sixths/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/sixths/README.md b/apps/sixths/README.md index 1998d3d14..2adc66f82 100644 --- a/apps/sixths/README.md +++ b/apps/sixths/README.md @@ -78,4 +78,8 @@ battery low message *) rename "show" to something else -- it collides with built-in -*) adjust clock according to gps \ No newline at end of file +*) adjust clock according to GPS + +*) show something more reasonable than (NOTEHERE). + +*) hide messages after timeout. \ No newline at end of file From 3cd24a2d9c3e5679c37c07fde93cd106c2389923 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Mon, 24 Jun 2024 19:25:13 +0200 Subject: [PATCH 056/133] [] sixths: make compass & battery usable. --- apps/sixths/sixths.app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/sixths/sixths.app.js b/apps/sixths/sixths.app.js index 7f71818d2..e1c1afbae 100644 --- a/apps/sixths/sixths.app.js +++ b/apps/sixths/sixths.app.js @@ -407,6 +407,10 @@ function loggps(fix) { function hourly() { print("hourly"); let s = ' T'; + if (bat < 25) { + s = ' B'; + show("Bat "+bat+"%", 60); + } if (is_active) doBuzz(toMorse(s)); //logstamp(""); @@ -418,11 +422,6 @@ function fivemin() { print("fivemin"); let s = ' B'; let bat = E.getBattery(); - if (bat < 25) { - if (is_active) - doBuzz(toMorse(s)); - show("Bat "+bat+"%", 60); - } try { Bangle.getPressure().then((x) => { cur_altitude = x.altitude; cur_temperature = x.temperature; }, @@ -500,6 +499,7 @@ function drawDot(h, d, s) { function drawBackground() { let acc = Bangle.getAccel(); is_level = (acc.z < -0.95); + Bangle.setCompassPower(!!is_level, "sixths"); if (is_level) { let obj = Bangle.getCompass(); if (obj) { @@ -745,7 +745,7 @@ function start() { Bangle.on("drag", touchHandler); Bangle.on("lock", lockHandler); if (0) - Bangle.on("accel", accelHandler); + Bangle.on("accel", accelHandler); if (0) { Bangle.setCompassPower(1, "sixths"); Bangle.setBarometerPower(1, "sixths"); From 998d1206851ab9afb841e10478fa1a29a2c93fe2 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Wed, 26 Jun 2024 23:29:48 +0200 Subject: [PATCH 057/133] [] sixths: broken battery reading broke everything, fix it. --- apps/sixths/sixths.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sixths/sixths.app.js b/apps/sixths/sixths.app.js index e1c1afbae..935a9e468 100644 --- a/apps/sixths/sixths.app.js +++ b/apps/sixths/sixths.app.js @@ -407,6 +407,7 @@ function loggps(fix) { function hourly() { print("hourly"); let s = ' T'; + let bat = E.getBattery(); if (bat < 25) { s = ' B'; show("Bat "+bat+"%", 60); @@ -421,7 +422,6 @@ function show(msg, timeout) { function fivemin() { print("fivemin"); let s = ' B'; - let bat = E.getBattery(); try { Bangle.getPressure().then((x) => { cur_altitude = x.altitude; cur_temperature = x.temperature; }, From f2efe03f2cd015801b42e8f72e2ed4b9db2803b8 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Wed, 26 Jun 2024 23:40:06 +0200 Subject: [PATCH 058/133] [] sixths: start displaying hPa instead of (msghere). --- apps/sixths/sixths.app.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/sixths/sixths.app.js b/apps/sixths/sixths.app.js index 935a9e468..4332b3645 100644 --- a/apps/sixths/sixths.app.js +++ b/apps/sixths/sixths.app.js @@ -11,7 +11,7 @@ var cx = 100, cy = 105, sc = 70, temp = 0, alt = 0, bpm = 0; var buzz = "", /* Set this to transmit morse via vibrations */ inm = "", l = "", /* For incoming morse handling */ in_str = "", - note = "(NOTEHERE)", + note = "", debug = "v0.04.1", debug2 = "(otherdb)", debug3 = "(short)"; var mode = 0, mode_time = 0; // 0 .. normal, 1 .. note, 2.. mark name var disp_mode = 0; // 0 .. normal, 1 .. small time @@ -553,7 +553,11 @@ function draw() { if (gps_on) { msg = gpsHandle(); } else { - msg = note; + let o = Bangle.getOptions(); + msg = o.seaLevelPressure.toFixed(1) + "hPa"; + if (note != "") { + msg = note; + } } drawBackground(); From 4c83a6345a39a6515e12ac8e2839e33a0e81b229 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 5 Jul 2024 22:39:33 +0200 Subject: [PATCH 059/133] [] sixths: more todo items. --- apps/sixths/README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/sixths/README.md b/apps/sixths/README.md index 2adc66f82..18aa85beb 100644 --- a/apps/sixths/README.md +++ b/apps/sixths/README.md @@ -82,4 +82,18 @@ battery low message *) show something more reasonable than (NOTEHERE). -*) hide messages after timeout. \ No newline at end of file +*) hide messages after timeout. + +*) show route lengths after the fact + +*) implement longer recording than "G". + +*) Probably T should be G. + +*) sum gps distances for a day + +*) allow setting up home altitude, or at least disable auto-calibration + +*) show time-to-sunset / sunrise? + +*) one-second updates when gps is active \ No newline at end of file From 068e49f1b1128093bd333a7ecbcdb531c7f552c9 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 13 Jul 2024 12:01:44 +0200 Subject: [PATCH 060/133] [] sixths: mark this as 0.05 --- apps/sixths/ChangeLog | 1 + apps/sixths/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/sixths/ChangeLog b/apps/sixths/ChangeLog index 08c4b83b0..510748fb3 100644 --- a/apps/sixths/ChangeLog +++ b/apps/sixths/ChangeLog @@ -2,3 +2,4 @@ 0.02: better GPS support, adding altitude and temperature support 0.03: minor code improvements 0.04: make height auto-calibration useful and slow ticks to save power +0.05: add ability to navigate to waypoints, better documentation diff --git a/apps/sixths/metadata.json b/apps/sixths/metadata.json index 91492fe1c..585f23170 100644 --- a/apps/sixths/metadata.json +++ b/apps/sixths/metadata.json @@ -1,6 +1,6 @@ { "id": "sixths", "name": "Sixth sense", - "version": "0.04", + "version": "0.05", "description": "Clock for outdoor use with GPS support", "icon": "app.png", "readme": "README.md", From 2de968aa9c80f4b9660ce5ef96794cdb77c6d571 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 13 Jul 2024 12:15:51 +0200 Subject: [PATCH 061/133] [] sixths: attempt to fix warnings --- apps/sixths/sixths.app.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/sixths/sixths.app.js b/apps/sixths/sixths.app.js index 4332b3645..c5fb3b9cf 100644 --- a/apps/sixths/sixths.app.js +++ b/apps/sixths/sixths.app.js @@ -530,13 +530,6 @@ function drawTime(now) { dot = "."; g.drawString(now.getHours() + dot + add0(now.getMinutes()), W, 90); } -function adjPressure(a) { - var o = Bangle.getOptions(); - print(o); - o.seaLevelPressure = o.seaLevelPressure * m + a; - Bangle.setOptions(o); - var avr = []; -} function draw() { if (disp_mode == 2) { draw_all(); @@ -707,13 +700,14 @@ function buzzTask() { } else print("Unknown character -- ", now, buzz); } } +var last_acc; function aliveTask() { function cmp(s) { let d = acc[s] - last_acc[s]; return d < -0.03 || d > 0.03; } // HRM seems to detect hand quite nicely - acc = Bangle.getAccel(); + let acc = Bangle.getAccel(); is_active = false; if (cmp("x") || cmp("y") || cmp("z")) { print("active"); @@ -732,6 +726,7 @@ function lockHandler(locked) { } function queueDraw() { + let next; if (getTime() - last_unlocked > 3*60) next = 60000; else @@ -748,8 +743,6 @@ function start() { Bangle.on("drag", touchHandler); Bangle.on("lock", lockHandler); - if (0) - Bangle.on("accel", accelHandler); if (0) { Bangle.setCompassPower(1, "sixths"); Bangle.setBarometerPower(1, "sixths"); From f38f183b1e02853237807423439cc39ec17892ff Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Wed, 19 Jun 2024 00:25:24 +0200 Subject: [PATCH 062/133] [] skyspy: start libgps --- apps/skyspy/skyspy.app.js | 53 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index ff6e29712..3278c9710 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -3,6 +3,44 @@ 1 .. DD MM.mmm' 2 .. DD MM'ss" */ + +let libgps = { + state: {}, + on_gps: function(f) { + let fix = this.getGPSFix(); + f(fix); + + /* + "lat": number, // Latitude in degrees + "lon": number, // Longitude in degrees + "alt": number, // altitude in M + "speed": number, // Speed in kph + "course": number, // Course in degrees + "time": Date, // Current Time (or undefined if not known) + "satellites": 7, // Number of satellites + "fix": 1 // NMEA Fix state - 0 is no fix + "hdop": number, // Horizontal Dilution of Precision + */ + this.state.timeout = setTimeout(this.on_gps, 1000, f); + }, + off_gps: function() { + clearTimeout(gps_state.timeout); + }, + getGPSFix: function() { + let fix = {}; + fix.fix = 1; + fix.lat = 50; + fix.lon = 14; + fix.alt = 200; + fix.speed = 5; + fix.course = 30; + fix.time = Date(); + fix.satellites = 5; + fix.hdop = 12; + return fix; + } +}; + var mode = 1; var display = 0; @@ -69,7 +107,8 @@ function updateGps() { if (cancel_gps) return; - fix = Bangle.getGPSFix(); + //fix = Bangle.getGPSFix(); + fix = libgps.getGPSFix(); try { Bangle.getPressure().then((x) => { @@ -81,6 +120,7 @@ function updateGps() { speed = getTime() - gps_start; + print(fix); if (fix && fix.fix && fix.lat) { lat = "" + format(fix.lat); lon = "" + format(fix.lon); @@ -244,10 +284,20 @@ function markGps() { updateGps(); } +function drawBusy() { + g.reset().setFont("Vector", 20) + .setColor(1,1,1) + .fillRect(0, wi, 176, 176) + .setColor(0,0,0) + .drawString(".oO busy", 0, 30); + +} + function onSwipe(dir) { display = display + 1; if (display == 3) display = 0; + drawBusy(); } Bangle.setUI({ @@ -257,4 +307,5 @@ Bangle.setUI({ }); Bangle.loadWidgets(); Bangle.drawWidgets(); +drawBusy(); markGps(); From 4bf47626ffa024f59adf0755a92f0be25439074d Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Wed, 19 Jun 2024 23:31:36 +0200 Subject: [PATCH 063/133] [] skyspy: move lat/lon formatting to library. --- apps/skyspy/skyspy.app.js | 57 ++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 3278c9710..181d0ea51 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -1,11 +1,34 @@ /* Sky spy */ -/* 0 .. DD.ddddd - 1 .. DD MM.mmm' - 2 .. DD MM'ss" -*/ let libgps = { state: {}, + /* 0 .. DD.ddddd + 1 .. DD MM.mmm' + 2 .. DD MM'ss" + */ + mode: 1, + format: function(x) { + switch (this.mode) { + case 0: + return "" + x; + case 1: { + let d = Math.floor(x); + let m = x - d; + m = m*60; + return "" + d + " " + m.toFixed(3) + "'"; + } + case 2: { + let d = Math.floor(x); + let m = x - d; + m = m*60; + let mf = Math.floor(m); + let s = m - mf; + s = s*60; + return "" + d + " " + mf + "'" + s.toFixed(0) + '"'; + } + } + return "bad mode?"; + }, on_gps: function(f) { let fix = this.getGPSFix(); f(fix); @@ -27,6 +50,7 @@ let libgps = { clearTimeout(gps_state.timeout); }, getGPSFix: function() { + if (0) Bangle.getGPSFix(); let fix = {}; fix.fix = 1; fix.lat = 50; @@ -41,7 +65,6 @@ let libgps = { } }; -var mode = 1; var display = 0; var debug = 0; @@ -65,25 +88,6 @@ function radY(p, d) { return h/2 - Math.cos(a)*radD(d) + wi; } -function format(x) { - switch (mode) { - case 0: - return "" + x; - case 1: - d = Math.floor(x); - m = x - d; - m = m*60; - return "" + d + " " + m.toFixed(3) + "'"; - case 2: - d = Math.floor(x); - m = x - d; - m = m*60; - mf = Math.floor(m); - s = m - mf; - s = s*60; - return "" + d + " " + mf + "'" + s.toFixed(0) + '"'; - } -} var qalt = -1; function resetAlt() { min_dalt = 9999; max_dalt = -9999; step = 0; @@ -107,7 +111,6 @@ function updateGps() { if (cancel_gps) return; - //fix = Bangle.getGPSFix(); fix = libgps.getGPSFix(); try { @@ -122,8 +125,8 @@ function updateGps() { print(fix); if (fix && fix.fix && fix.lat) { - lat = "" + format(fix.lat); - lon = "" + format(fix.lon); + lat = "" + libgps.format(fix.lat); + lon = "" + libgps.format(fix.lon); alt = "" + fix.alt.toFixed(1); speed = "" + fix.speed.toFixed(1); hdop = "" + fix.hdop.toFixed(1); From ff46f86b2d72cdd73cba73302bafdee69cf7a00b Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Wed, 19 Jun 2024 23:51:41 +0200 Subject: [PATCH 064/133] [] skyspy: Implement time adjustment (untested). --- apps/skyspy/skyspy.app.js | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 181d0ea51..14e24ac1d 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -77,6 +77,8 @@ var h = 176-wi, w = 176; var fix; +var adj_time = 0; + function radA(p) { return p*(Math.PI*2); } function radD(d) { return d*(h/2); } function radX(p, d) { @@ -112,6 +114,11 @@ function updateGps() { if (cancel_gps) return; fix = libgps.getGPSFix(); + if (adj_time) { + print("Adjusting time"); + setTime(fix.time.getTime()/1000); + adj_time = 0; + } try { Bangle.getPressure().then((x) => { @@ -293,21 +300,39 @@ function drawBusy() { .fillRect(0, wi, 176, 176) .setColor(0,0,0) .drawString(".oO busy", 0, 30); +} + +function nextScreen() { + display = display + 1; + if (display == 3) + display = 0; + drawBusy(); } function onSwipe(dir) { - display = display + 1; - if (display == 3) - display = 0; - drawBusy(); + nextScreen(); } +function touchHandler(d) { + let x = Math.floor(d.x); + let y = Math.floor(d.y); + + if ((xh/2) && (y>w/2)) + nextScreen(); +} + + +Bangle.on("drag", touchHandler); Bangle.setUI({ mode : "custom", swipe : onSwipe, clock : 0 }); + Bangle.loadWidgets(); Bangle.drawWidgets(); drawBusy(); From 78a910544b2d0215ff180ac2a6da396b83826df4 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Thu, 20 Jun 2024 00:04:00 +0200 Subject: [PATCH 065/133] [] skyspy: only use fake data on emulator. --- apps/skyspy/skyspy.app.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 14e24ac1d..db7736ba0 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -1,6 +1,11 @@ -/* Sky spy */ + /* Sky spy */ let libgps = { + emulator: -1, + init: function(x) { + this.emulator = (process.env.BOARD=="EMSCRIPTEN" + || process.env.BOARD=="EMSCRIPTEN2")?1:0; + }, state: {}, /* 0 .. DD.ddddd 1 .. DD MM.mmm' @@ -50,7 +55,8 @@ let libgps = { clearTimeout(gps_state.timeout); }, getGPSFix: function() { - if (0) Bangle.getGPSFix(); + if (!this.emulator) + return Bangle.getGPSFix(); let fix = {}; fix.fix = 1; fix.lat = 50; @@ -130,7 +136,7 @@ function updateGps() { speed = getTime() - gps_start; - print(fix); + //print(fix); if (fix && fix.fix && fix.lat) { lat = "" + libgps.format(fix.lat); lon = "" + libgps.format(fix.lon); @@ -325,6 +331,7 @@ function touchHandler(d) { nextScreen(); } +libgps.init(); Bangle.on("drag", touchHandler); Bangle.setUI({ From ab0b4cbb45bd3a8c2d7bd18d7fb6e56516652449 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Thu, 20 Jun 2024 22:05:28 +0200 Subject: [PATCH 066/133] [] skyspy: display time error. --- apps/skyspy/skyspy.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index db7736ba0..5dec5f148 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -152,7 +152,7 @@ function updateGps() { .setColor(1,1,1) .fillRect(0, wi, 176, 176) .setColor(0,0,0) - .drawString("Acquiring GPS", 0, 30) + .drawString("terr "+(getTime()-fix.time.getTime()/1000), 0, 30) .drawString(lat, 0, 50) .drawString(lon, 0, 70) .drawString("alt "+alt, 0, 90) From a64efa4d1fc0205a072dd10f10d7add23ddab8fc Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Thu, 20 Jun 2024 22:11:05 +0200 Subject: [PATCH 067/133] [] skyspy: fix button handling. --- apps/skyspy/skyspy.app.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 5dec5f148..fd651a6fc 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -320,9 +320,16 @@ function onSwipe(dir) { nextScreen(); } +var last_b = 0; function touchHandler(d) { let x = Math.floor(d.x); let y = Math.floor(d.y); + + if (d.b != 1 || last_b != 0) { + last_b = d.b; + return; + } + last_b = d.b; if ((x Date: Thu, 20 Jun 2024 22:36:06 +0200 Subject: [PATCH 068/133] [] skyspy: switch to bigger font. --- apps/skyspy/skyspy.app.js | 57 +++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index fd651a6fc..24337c3c8 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -72,12 +72,9 @@ let libgps = { }; var display = 0; - var debug = 0; - var cancel_gps, gps_start; var cur_altitude; - var wi = 24; var h = 176-wi, w = 176; @@ -123,6 +120,7 @@ function updateGps() { if (adj_time) { print("Adjusting time"); setTime(fix.time.getTime()/1000); + drawMsg("Time\nadjusted"); adj_time = 0; } @@ -147,38 +145,31 @@ function updateGps() { } let ddalt = calcAlt(alt, cur_altitude); - if (display == 1) - g.reset().setFont("Vector", 20) - .setColor(1,1,1) - .fillRect(0, wi, 176, 176) - .setColor(0,0,0) - .drawString("terr "+(getTime()-fix.time.getTime()/1000), 0, 30) - .drawString(lat, 0, 50) - .drawString(lon, 0, 70) - .drawString("alt "+alt, 0, 90) - .drawString("speed "+speed, 0, 110) - .drawString("hdop "+hdop, 0, 130) - .drawString("balt" + cur_altitude, 0, 150); - + let msg = ""; + if (display == 1) { + msg = lat + "\n" + lon + "\n" + alt + "m" + speed + "km/h\n" + + "hdop "+hdop + "m\nbalt" + cur_altitude + "m"; + } if (display == 2) { - g.reset().setFont("Vector", 20) - .setColor(1,1,1) - .fillRect(0, wi, 176, 176) - .setColor(0,0,0) - .drawString("GPS status", 0, 30) - .drawString("speed "+speed, 0, 50) - .drawString("hdop "+hdop, 0, 70) - .drawString("dd "+qalt.toFixed(0) + " (" + ddalt.toFixed(0) + ")", 0, 90) - .drawString("alt "+alt, 0, 110) - .drawString("balt " + cur_altitude, 0, 130) - .drawString(step, 0, 150); + msg = speed + "km/h\n" + + "hdop "+hdop + + "\ndd "+qalt.toFixed(0) + " (" + ddalt.toFixed(0) + ")" + + "\nalt "+alt + + "\nbalt "+cur_altitude + + "/" + step; step++; if (step == 10) { qalt = max_dalt - min_dalt; resetAlt(); } } - + if (display > 0) { + g.reset().setFont("Vector", 31) + .setColor(1,1,1) + .fillRect(0, wi, 176, 176) + .setColor(0,0,0) + .drawString(msg, 3, 25) + } if (debug > 0) print(fix); setTimeout(updateGps, 1000); @@ -299,13 +290,15 @@ function markGps() { gps_start = getTime(); updateGps(); } - -function drawBusy() { - g.reset().setFont("Vector", 20) +function drawMsg(msg) { + g.reset().setFont("Vector", 35) .setColor(1,1,1) .fillRect(0, wi, 176, 176) .setColor(0,0,0) - .drawString(".oO busy", 0, 30); + .drawString(msg, 5, 30); +} +function drawBusy() { + drawMsg("\n.oO busy"); } function nextScreen() { From eebc99fa6c4d637c161493925f0e38a5d178b6e9 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Thu, 20 Jun 2024 23:05:46 +0200 Subject: [PATCH 069/133] [] skyspy: fit data into 5 lines. --- apps/skyspy/skyspy.app.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 24337c3c8..17824eae6 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -71,7 +71,7 @@ let libgps = { } }; -var display = 0; +var display = 1; var debug = 0; var cancel_gps, gps_start; var cur_altitude; @@ -111,8 +111,9 @@ function calcAlt(alt, cur_altitude) { return ddalt; } function updateGps() { - let /*have = false,*/ lat = "lat", lon = "lon", alt = "alt", - speed = "speed", hdop = "hdop"; // balt = "balt"; + let have = false, lat = "lat ", lon = "lon ", alt = "alt ", + speed = "speed ", hdop = "hdop ", adelta = "adelta ", + tdelta = "tdelta "; if (cancel_gps) return; @@ -129,7 +130,7 @@ function updateGps() { cur_altitude = x.altitude; }, print); } catch (e) { - print("Altimeter error", e); + //print("Altimeter error", e); } speed = getTime() - gps_start; @@ -138,17 +139,20 @@ function updateGps() { if (fix && fix.fix && fix.lat) { lat = "" + libgps.format(fix.lat); lon = "" + libgps.format(fix.lon); - alt = "" + fix.alt.toFixed(1); + alt = "" + fix.alt.toFixed(0); + adelta = "" + (cur_altitude - fix.alt).toFixed(0); + tdelta = "" + (getTime() - fix.time.getTime()/1000).toFixed(0); speed = "" + fix.speed.toFixed(1); - hdop = "" + fix.hdop.toFixed(1); - //have = true; + hdop = "" + fix.hdop.toFixed(0); + have = true; } let ddalt = calcAlt(alt, cur_altitude); let msg = ""; if (display == 1) { - msg = lat + "\n" + lon + "\n" + alt + "m" + speed + "km/h\n" - + "hdop "+hdop + "m\nbalt" + cur_altitude + "m"; + msg = lat + "\n" + lon + + "\ne" + hdop + "m "+tdelta+"s\n" + + speed + "km/h\n"+ alt + "m+" + adelta + "\nmsghere"; } if (display == 2) { msg = speed + "km/h\n" + @@ -168,7 +172,7 @@ function updateGps() { .setColor(1,1,1) .fillRect(0, wi, 176, 176) .setColor(0,0,0) - .drawString(msg, 3, 25) + .drawString(msg, 3, 25); } if (debug > 0) print(fix); From 7ec2dbb17bc955288843261d3e8469642b60500a Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Thu, 20 Jun 2024 23:13:05 +0200 Subject: [PATCH 070/133] [] skyspy: Provide some info with no fix. --- apps/skyspy/skyspy.app.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 17824eae6..5d0a2fdb8 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -111,8 +111,8 @@ function calcAlt(alt, cur_altitude) { return ddalt; } function updateGps() { - let have = false, lat = "lat ", lon = "lon ", alt = "alt ", - speed = "speed ", hdop = "hdop ", adelta = "adelta ", + let have = false, lat = "lat ", lon = "lon ", alt = "?", + speed = "speed ", hdop = "?", adelta = "adelta ", tdelta = "tdelta "; if (cancel_gps) @@ -133,18 +133,24 @@ function updateGps() { //print("Altimeter error", e); } - speed = getTime() - gps_start; //print(fix); + if (fix && fix.time) { + tdelta = "" + (getTime() - fix.time.getTime()/1000).toFixed(0); + } if (fix && fix.fix && fix.lat) { lat = "" + libgps.format(fix.lat); lon = "" + libgps.format(fix.lon); alt = "" + fix.alt.toFixed(0); adelta = "" + (cur_altitude - fix.alt).toFixed(0); - tdelta = "" + (getTime() - fix.time.getTime()/1000).toFixed(0); speed = "" + fix.speed.toFixed(1); hdop = "" + fix.hdop.toFixed(0); have = true; + } else { + lat = "NO FIX"; + lon = "" + (getTime() - gps_start).toFixed(0) + "s"; + if (cur_altitude) + adelta = "" + cur_altitude.toFixed(0); } let ddalt = calcAlt(alt, cur_altitude); From 2dc4537dc04d41d8b5082b24b740bb0ea15855ce Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Thu, 20 Jun 2024 23:45:01 +0200 Subject: [PATCH 071/133] [] skyspy: show sats used/total when no signal --- apps/skyspy/skyspy.app.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 5d0a2fdb8..857da2410 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -71,15 +71,13 @@ let libgps = { } }; -var display = 1; +var display = 0; var debug = 0; var cancel_gps, gps_start; var cur_altitude; var wi = 24; var h = 176-wi, w = 176; - var fix; - var adj_time = 0; function radA(p) { return p*(Math.PI*2); } @@ -147,8 +145,9 @@ function updateGps() { hdop = "" + fix.hdop.toFixed(0); have = true; } else { - lat = "NO FIX"; - lon = "" + (getTime() - gps_start).toFixed(0) + "s"; + lat = "NO FIX "; + lon = "" + (getTime() - gps_start).toFixed(0) + "s " + + sats_used + "/" + snum; if (cur_altitude) adelta = "" + cur_altitude.toFixed(0); } @@ -241,7 +240,7 @@ function drawSats(sats) { var sats = []; var snum = 0; -//var sats_receiving = 0; +var sats_used = 0; function parseRaw(msg, lost) { if (lost) @@ -256,6 +255,7 @@ function parseRaw(msg, lost) { if (s[2] == "1") { snum = 0; sats = []; + sats_used = 0; } let view = 1 * s[3]; @@ -274,6 +274,8 @@ function parseRaw(msg, lost) { sat.ele = 1*s[i++]; sat.azi = 1*s[i++]; sat.snr = s[i++]; + if (sat.snr != "") + sats_used++; if (debug > 0) print(" ", sat); sats[snum++] = sat; From 2e60c8b5ecf28c060a1252cc6a91eed2678cddde Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 21 Jun 2024 00:04:13 +0200 Subject: [PATCH 072/133] [] skyspy: better summary for 3rd screen, too. --- apps/skyspy/skyspy.app.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 857da2410..c9e22d580 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -161,11 +161,9 @@ function updateGps() { } if (display == 2) { msg = speed + "km/h\n" + - "hdop "+hdop + - "\ndd "+qalt.toFixed(0) + " (" + ddalt.toFixed(0) + ")" + - "\nalt "+alt + - "\nbalt "+cur_altitude + - "/" + step; + "e"+hdop + "m/"+step + +"\ndd "+qalt.toFixed(0) + "\n(" + ddalt.toFixed(0) + ")" + + "\n"+alt + "m+" + adelta; step++; if (step == 10) { qalt = max_dalt - min_dalt; From 22d6bdbcc6e46e9647064b736d2c0ad6afc356e5 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 21 Jun 2024 00:18:55 +0200 Subject: [PATCH 073/133] [] skyspy: better debug output, add documentation --- apps/skyspy/skyspy.app.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index c9e22d580..2b0965148 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -160,9 +160,17 @@ function updateGps() { speed + "km/h\n"+ alt + "m+" + adelta + "\nmsghere"; } if (display == 2) { + /* FIXME: ddalt/qalt should be updated in all modes */ + /* qalt is altitude quality estimate -- over ten seconds, + computes differences between GPS and barometric altitude. + The lower the better. + + ddalt is just a debugging -- same estimate, but without + waiting 10 seconds, so will be always optimistic at start + of the cycle */ msg = speed + "km/h\n" + - "e"+hdop + "m/"+step - +"\ndd "+qalt.toFixed(0) + "\n(" + ddalt.toFixed(0) + ")" + + "e"+hdop + "m" + +"\ndd "+qalt.toFixed(0) + "\n(" + step + "/" + ddalt.toFixed(0) + ")" + "\n"+alt + "m+" + adelta; step++; if (step == 10) { From 6eaad6f13a5e9b65aae151f7dbf01d7a70ff79e2 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 21 Jun 2024 09:56:52 +0200 Subject: [PATCH 074/133] [] skyspy: Compute altitude quality all the time, fix "clock adjust" message --- apps/skyspy/skyspy.app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 2b0965148..964e66f6a 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -119,7 +119,6 @@ function updateGps() { if (adj_time) { print("Adjusting time"); setTime(fix.time.getTime()/1000); - drawMsg("Time\nadjusted"); adj_time = 0; } @@ -160,7 +159,6 @@ function updateGps() { speed + "km/h\n"+ alt + "m+" + adelta + "\nmsghere"; } if (display == 2) { - /* FIXME: ddalt/qalt should be updated in all modes */ /* qalt is altitude quality estimate -- over ten seconds, computes differences between GPS and barometric altitude. The lower the better. @@ -172,12 +170,12 @@ function updateGps() { "e"+hdop + "m" +"\ndd "+qalt.toFixed(0) + "\n(" + step + "/" + ddalt.toFixed(0) + ")" + "\n"+alt + "m+" + adelta; + } step++; if (step == 10) { qalt = max_dalt - min_dalt; resetAlt(); } - } if (display > 0) { g.reset().setFont("Vector", 31) .setColor(1,1,1) @@ -342,8 +340,10 @@ function touchHandler(d) { } last_b = d.b; - if ((xh/2) && (y>w/2)) nextScreen(); From 1fa0c219e30b1aad8f49b618e1cb5ab8b4e6ed84 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 22 Jun 2024 23:20:47 +0200 Subject: [PATCH 075/133] [] skyspy: Create formatting library, and use it where suitable. --- apps/skyspy/skyspy.app.js | 169 ++++++++++++++++++++++---------------- 1 file changed, 96 insertions(+), 73 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 964e66f6a..ae1109fe8 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -1,74 +1,97 @@ - /* Sky spy */ +/* Sky spy */ + +let fmt = { + icon_alt : "\0\x08\x1a\1\x00\x00\x00\x20\x30\x78\x7C\xFE\xFF\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00", + icon_m : "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00", + icon_km : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00", + icon_kph : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\xFF\x00\xC3\xC3\xFF\xC3\xC3", + icon_c : "\0\x08\x1a\1\x00\x00\x60\x90\x90\x60\x00\x7F\xFF\xC0\xC0\xC0\xC0\xC0\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", -let libgps = { - emulator: -1, - init: function(x) { - this.emulator = (process.env.BOARD=="EMSCRIPTEN" - || process.env.BOARD=="EMSCRIPTEN2")?1:0; - }, - state: {}, /* 0 .. DD.ddddd 1 .. DD MM.mmm' 2 .. DD MM'ss" */ - mode: 1, - format: function(x) { - switch (this.mode) { - case 0: - return "" + x; - case 1: { - let d = Math.floor(x); - let m = x - d; - m = m*60; - return "" + d + " " + m.toFixed(3) + "'"; - } - case 2: { - let d = Math.floor(x); - let m = x - d; - m = m*60; - let mf = Math.floor(m); - let s = m - mf; - s = s*60; - return "" + d + " " + mf + "'" + s.toFixed(0) + '"'; - } - } - return "bad mode?"; - }, - on_gps: function(f) { - let fix = this.getGPSFix(); - f(fix); + geo_mode : 1, + + init: function() {}, + fmtDist: function(km) { return km.toFixed(1) + this.icon_km; }, + fmtSteps: function(n) { return fmtDist(0.001 * 0.719 * n); }, + fmtAlt: function(m) { return m.toFixed(0) + this.icon_alt; }, + fmtTimeDiff: function(d) { + if (d < 180) + return ""+d.toFixed(0); + d = d/60; + return ""+d.toFixed(0)+"m"; + }, + fmtAngle: function(x) { + switch (this.geo_mode) { + case 0: + return "" + x; + case 1: { + let d = Math.floor(x); + let m = x - d; + m = m*60; + return "" + d + " " + m.toFixed(3) + "'"; + } + case 2: { + let d = Math.floor(x); + let m = x - d; + m = m*60; + let mf = Math.floor(m); + let s = m - mf; + s = s*60; + return "" + d + " " + mf + "'" + s.toFixed(0) + '"'; + } + } + return "bad mode?"; + }, + fmtPos: function(pos) { + return this.fmtAngle(pos.lat) + "\n" + this.fmtAngle(pos.lon); + }, +}; - /* - "lat": number, // Latitude in degrees - "lon": number, // Longitude in degrees - "alt": number, // altitude in M - "speed": number, // Speed in kph - "course": number, // Course in degrees - "time": Date, // Current Time (or undefined if not known) - "satellites": 7, // Number of satellites - "fix": 1 // NMEA Fix state - 0 is no fix - "hdop": number, // Horizontal Dilution of Precision - */ - this.state.timeout = setTimeout(this.on_gps, 1000, f); - }, - off_gps: function() { - clearTimeout(gps_state.timeout); - }, - getGPSFix: function() { - if (!this.emulator) - return Bangle.getGPSFix(); - let fix = {}; - fix.fix = 1; - fix.lat = 50; - fix.lon = 14; - fix.alt = 200; - fix.speed = 5; - fix.course = 30; - fix.time = Date(); - fix.satellites = 5; - fix.hdop = 12; - return fix; - } +let gps = { + emulator: -1, + init: function(x) { + this.emulator = (process.env.BOARD=="EMSCRIPTEN" + || process.env.BOARD=="EMSCRIPTEN2")?1:0; + }, + state: {}, + on_gps: function(f) { + let fix = this.getGPSFix(); + f(fix); + + /* + "lat": number, // Latitude in degrees + "lon": number, // Longitude in degrees + "alt": number, // altitude in M + "speed": number, // Speed in kph + "course": number, // Course in degrees + "time": Date, // Current Time (or undefined if not known) + "satellites": 7, // Number of satellites + "fix": 1 // NMEA Fix state - 0 is no fix + "hdop": number, // Horizontal Dilution of Precision + */ + this.state.timeout = setTimeout(this.on_gps, 1000, f); + }, + off_gps: function() { + clearTimeout(gps_state.timeout); + }, + getGPSFix: function() { + if (!this.emulator) + return Bangle.getGPSFix(); + let fix = {}; + fix.fix = 1; + fix.lat = 50; + fix.lon = 14; + fix.alt = 200; + fix.speed = 5; + fix.course = 30; + fix.time = Date(); + fix.satellites = 5; + fix.hdop = 12; + return fix; + } }; var display = 0; @@ -109,13 +132,13 @@ function calcAlt(alt, cur_altitude) { return ddalt; } function updateGps() { - let have = false, lat = "lat ", lon = "lon ", alt = "?", + let have = false, lat = "lat ", alt = "?", speed = "speed ", hdop = "?", adelta = "adelta ", tdelta = "tdelta "; if (cancel_gps) return; - fix = libgps.getGPSFix(); + fix = gps.getGPSFix(); if (adj_time) { print("Adjusting time"); setTime(fix.time.getTime()/1000); @@ -136,16 +159,15 @@ function updateGps() { tdelta = "" + (getTime() - fix.time.getTime()/1000).toFixed(0); } if (fix && fix.fix && fix.lat) { - lat = "" + libgps.format(fix.lat); - lon = "" + libgps.format(fix.lon); + lat = "" + fmt.fmtPos(fix); alt = "" + fix.alt.toFixed(0); adelta = "" + (cur_altitude - fix.alt).toFixed(0); speed = "" + fix.speed.toFixed(1); hdop = "" + fix.hdop.toFixed(0); have = true; } else { - lat = "NO FIX "; - lon = "" + (getTime() - gps_start).toFixed(0) + "s " + lat = "NO FIX\n" + + "" + (getTime() - gps_start).toFixed(0) + "s " + sats_used + "/" + snum; if (cur_altitude) adelta = "" + cur_altitude.toFixed(0); @@ -154,7 +176,7 @@ function updateGps() { let ddalt = calcAlt(alt, cur_altitude); let msg = ""; if (display == 1) { - msg = lat + "\n" + lon + + msg = lat + "\ne" + hdop + "m "+tdelta+"s\n" + speed + "km/h\n"+ alt + "m+" + adelta + "\nmsghere"; } @@ -349,7 +371,8 @@ function touchHandler(d) { nextScreen(); } -libgps.init(); +gps.init(); +fmt.init(); Bangle.on("drag", touchHandler); Bangle.setUI({ From cd87342bd9c07dfc5da143cc927bbcdabcbb7705 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sun, 23 Jun 2024 14:43:35 +0200 Subject: [PATCH 076/133] [] skyspy: display N/E for a fix, too. --- apps/skyspy/skyspy.app.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index ae1109fe8..7cfa5145a 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -46,7 +46,19 @@ let fmt = { return "bad mode?"; }, fmtPos: function(pos) { - return this.fmtAngle(pos.lat) + "\n" + this.fmtAngle(pos.lon); + let x = pos.lat; + let c = "N"; + if (x<0) { + c = "S"; + x = -x; + } + let s = c+this.fmtAngle(pos.lat) + "\n"; + c = "E"; + if (x<0) { + c = "W"; + x = -x; + } + return s + c + this.fmtAngle(pos.lon); }, }; From c2692e179b432a30fc6586f740a5f4b48b7cee1f Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sun, 23 Jun 2024 15:11:11 +0200 Subject: [PATCH 077/133] [] skyspy: add automatic altitude adjustment --- apps/skyspy/skyspy.app.js | 70 +++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 7cfa5145a..9d74890ff 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -113,7 +113,7 @@ var cur_altitude; var wi = 24; var h = 176-wi, w = 176; var fix; -var adj_time = 0; +var adj_time = 0, adj_alt = 0; function radA(p) { return p*(Math.PI*2); } function radD(d) { return d*(h/2); } @@ -156,6 +156,26 @@ function updateGps() { setTime(fix.time.getTime()/1000); adj_time = 0; } + if (adj_alt) { + print("Adjust altitude"); + if (qalt < 5) { + let rest_altitude = fix.alt; + let alt_adjust = cur_altitude - rest_altitude; + let abs = Math.abs(alt_adjust); + print("adj", alt_adjust); + let o = Bangle.getOptions(); + if (abs > 10 && abs < 150) { + let a = 0.01; + // FIXME: draw is called often compared to alt reading + if (cur_altitude > rest_altitude) + a = -a; + o.seaLevelPressure = o.seaLevelPressure + a; + Bangle.setOptions(o); + } + msg = o.seaLevelPressure.toFixed(1) + "hPa"; + print(msg); + } + } try { Bangle.getPressure().then((x) => { @@ -351,36 +371,50 @@ function drawBusy() { drawMsg("\n.oO busy"); } +var numScreens = 3; + function nextScreen() { display = display + 1; - if (display == 3) - display = 0; - drawBusy(); + if (display == numScreens) + display = 0; + drawBusy(); +} +function prevScreen() { + display = display - 1; + if (display < 0) + display = numScreens - 1; + drawBusy(); } function onSwipe(dir) { - nextScreen(); + nextScreen(); } var last_b = 0; function touchHandler(d) { - let x = Math.floor(d.x); - let y = Math.floor(d.y); - - if (d.b != 1 || last_b != 0) { + let x = Math.floor(d.x); + let y = Math.floor(d.y); + + if (d.b != 1 || last_b != 0) { + last_b = d.b; + return; + } last_b = d.b; - return; - } - last_b = d.b; - if ((xh/2) && (yh/2) && (y>w/2)) - nextScreen(); + if ((xw/2)) + prevScreen(); + if ((x>h/2) && (y>w/2)) + nextScreen(); } gps.init(); From 0eab3bdcc804c609d0175dcee6eb84551b7a9846 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Mon, 24 Jun 2024 21:26:03 +0200 Subject: [PATCH 078/133] [] skyspy: mark library versions --- apps/skyspy/skyspy.app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 9d74890ff..29c0a1360 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -1,5 +1,6 @@ /* Sky spy */ +/* fmt library v0.1 */ let fmt = { icon_alt : "\0\x08\x1a\1\x00\x00\x00\x20\x30\x78\x7C\xFE\xFF\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00", icon_m : "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00", @@ -62,6 +63,7 @@ let fmt = { }, }; +/* gps library v0.1 */ let gps = { emulator: -1, init: function(x) { From 6014001307dd35b7dfd2d7411919cd0d87cd639f Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 13 Jul 2024 12:05:52 +0200 Subject: [PATCH 079/133] [] skyspy: increase version. --- apps/skyspy/ChangeLog | 1 + apps/skyspy/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/skyspy/ChangeLog b/apps/skyspy/ChangeLog index 79da4daf2..15a83ceeb 100644 --- a/apps/skyspy/ChangeLog +++ b/apps/skyspy/ChangeLog @@ -1,2 +1,3 @@ 0.01: attempt to import 0.02: Minor code improvements +0.03: big rewrite, adding time-adjust and altitude-adjust functionality diff --git a/apps/skyspy/metadata.json b/apps/skyspy/metadata.json index 07bc9280a..d8e9d1356 100644 --- a/apps/skyspy/metadata.json +++ b/apps/skyspy/metadata.json @@ -1,6 +1,6 @@ { "id": "skyspy", "name": "Sky Spy", - "version": "0.02", + "version": "0.03", "description": "Application for debugging GPS problems", "icon": "app.png", "readme": "README.md", From 3e7c1b6710695eb145c6f605d30291d3bebb00f9 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 13 Jul 2024 12:23:52 +0200 Subject: [PATCH 080/133] [] skyspy: attempt warning fixes. --- apps/skyspy/skyspy.app.js | 174 ++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 91 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 29c0a1360..0e6654c62 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -16,25 +16,25 @@ let fmt = { init: function() {}, fmtDist: function(km) { return km.toFixed(1) + this.icon_km; }, - fmtSteps: function(n) { return fmtDist(0.001 * 0.719 * n); }, + fmtSteps: function(n) { return this.fmtDist(0.001 * 0.719 * n); }, fmtAlt: function(m) { return m.toFixed(0) + this.icon_alt; }, fmtTimeDiff: function(d) { - if (d < 180) - return ""+d.toFixed(0); - d = d/60; - return ""+d.toFixed(0)+"m"; + if (d < 180) + return ""+d.toFixed(0); + d = d/60; + return ""+d.toFixed(0)+"m"; }, fmtAngle: function(x) { - switch (this.geo_mode) { - case 0: + switch (this.geo_mode) { + case 0: return "" + x; - case 1: { + case 1: { let d = Math.floor(x); let m = x - d; m = m*60; return "" + d + " " + m.toFixed(3) + "'"; - } - case 2: { + } + case 2: { let d = Math.floor(x); let m = x - d; m = m*60; @@ -42,24 +42,24 @@ let fmt = { let s = m - mf; s = s*60; return "" + d + " " + mf + "'" + s.toFixed(0) + '"'; - } - } - return "bad mode?"; + } + } + return "bad mode?"; }, fmtPos: function(pos) { - let x = pos.lat; - let c = "N"; - if (x<0) { - c = "S"; - x = -x; - } - let s = c+this.fmtAngle(pos.lat) + "\n"; - c = "E"; - if (x<0) { - c = "W"; - x = -x; - } - return s + c + this.fmtAngle(pos.lon); + let x = pos.lat; + let c = "N"; + if (x<0) { + c = "S"; + x = -x; + } + let s = c+this.fmtAngle(pos.lat) + "\n"; + c = "E"; + if (x<0) { + c = "W"; + x = -x; + } + return s + c + this.fmtAngle(pos.lon); }, }; @@ -67,50 +67,50 @@ let fmt = { let gps = { emulator: -1, init: function(x) { - this.emulator = (process.env.BOARD=="EMSCRIPTEN" - || process.env.BOARD=="EMSCRIPTEN2")?1:0; + this.emulator = (process.env.BOARD=="EMSCRIPTEN" + || process.env.BOARD=="EMSCRIPTEN2")?1:0; }, state: {}, on_gps: function(f) { - let fix = this.getGPSFix(); - f(fix); + let fix = this.getGPSFix(); + f(fix); - /* - "lat": number, // Latitude in degrees - "lon": number, // Longitude in degrees - "alt": number, // altitude in M - "speed": number, // Speed in kph - "course": number, // Course in degrees - "time": Date, // Current Time (or undefined if not known) - "satellites": 7, // Number of satellites - "fix": 1 // NMEA Fix state - 0 is no fix - "hdop": number, // Horizontal Dilution of Precision - */ - this.state.timeout = setTimeout(this.on_gps, 1000, f); + /* + "lat": number, // Latitude in degrees + "lon": number, // Longitude in degrees + "alt": number, // altitude in M + "speed": number, // Speed in kph + "course": number, // Course in degrees + "time": Date, // Current Time (or undefined if not known) + "satellites": 7, // Number of satellites + "fix": 1 // NMEA Fix state - 0 is no fix + "hdop": number, // Horizontal Dilution of Precision + */ + this.state.timeout = setTimeout(this.on_gps, 1000, f); }, off_gps: function() { - clearTimeout(gps_state.timeout); + clearTimeout(this.state.timeout); }, getGPSFix: function() { - if (!this.emulator) - return Bangle.getGPSFix(); - let fix = {}; - fix.fix = 1; - fix.lat = 50; - fix.lon = 14; - fix.alt = 200; - fix.speed = 5; - fix.course = 30; - fix.time = Date(); - fix.satellites = 5; - fix.hdop = 12; - return fix; + if (!this.emulator) + return Bangle.getGPSFix(); + let fix = {}; + fix.fix = 1; + fix.lat = 50; + fix.lon = 14; + fix.alt = 200; + fix.speed = 5; + fix.course = 30; + fix.time = Date(); + fix.satellites = 5; + fix.hdop = 12; + return fix; } }; var display = 0; var debug = 0; -var cancel_gps, gps_start; +var gps_start; var cur_altitude; var wi = 24; var h = 176-wi, w = 176; @@ -128,7 +128,7 @@ function radY(p, d) { return h/2 - Math.cos(a)*radD(d) + wi; } -var qalt = -1; +var qalt = -1, min_dalt, max_dalt, step; function resetAlt() { min_dalt = 9999; max_dalt = -9999; step = 0; } @@ -150,8 +150,6 @@ function updateGps() { speed = "speed ", hdop = "?", adelta = "adelta ", tdelta = "tdelta "; - if (cancel_gps) - return; fix = gps.getGPSFix(); if (adj_time) { print("Adjusting time"); @@ -161,21 +159,21 @@ function updateGps() { if (adj_alt) { print("Adjust altitude"); if (qalt < 5) { - let rest_altitude = fix.alt; - let alt_adjust = cur_altitude - rest_altitude; - let abs = Math.abs(alt_adjust); - print("adj", alt_adjust); - let o = Bangle.getOptions(); - if (abs > 10 && abs < 150) { - let a = 0.01; - // FIXME: draw is called often compared to alt reading - if (cur_altitude > rest_altitude) - a = -a; - o.seaLevelPressure = o.seaLevelPressure + a; - Bangle.setOptions(o); - } - msg = o.seaLevelPressure.toFixed(1) + "hPa"; - print(msg); + let rest_altitude = fix.alt; + let alt_adjust = cur_altitude - rest_altitude; + let abs = Math.abs(alt_adjust); + print("adj", alt_adjust); + let o = Bangle.getOptions(); + if (abs > 10 && abs < 150) { + let a = 0.01; + // FIXME: draw is called often compared to alt reading + if (cur_altitude > rest_altitude) + a = -a; + o.seaLevelPressure = o.seaLevelPressure + a; + Bangle.setOptions(o); + } + msg = o.seaLevelPressure.toFixed(1) + "hPa"; + print(msg); } } @@ -350,13 +348,7 @@ function parseRaw(msg, lost) { } } -function stopGps() { - cancel_gps=true; - Bangle.setGPSPower(0, "skyspy"); -} - function markGps() { - cancel_gps = false; Bangle.setGPSPower(1, "skyspy"); Bangle.on('GPS-raw', parseRaw); gps_start = getTime(); @@ -378,14 +370,14 @@ var numScreens = 3; function nextScreen() { display = display + 1; if (display == numScreens) - display = 0; + display = 0; drawBusy(); } function prevScreen() { display = display - 1; if (display < 0) - display = numScreens - 1; + display = numScreens - 1; drawBusy(); } @@ -399,24 +391,24 @@ function touchHandler(d) { let y = Math.floor(d.y); if (d.b != 1 || last_b != 0) { - last_b = d.b; - return; + last_b = d.b; + return; } last_b = d.b; if ((xh/2) && (yw/2)) - prevScreen(); + prevScreen(); if ((x>h/2) && (y>w/2)) - nextScreen(); + nextScreen(); } gps.init(); From 5160c2eedaf9b02e55b8601bedf51a5ae2e76e6c Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 13 Jul 2024 12:26:29 +0200 Subject: [PATCH 081/133] [] skyspy: fix remaining warning --- apps/skyspy/skyspy.app.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/skyspy/skyspy.app.js b/apps/skyspy/skyspy.app.js index 0e6654c62..a3a0d8776 100644 --- a/apps/skyspy/skyspy.app.js +++ b/apps/skyspy/skyspy.app.js @@ -146,7 +146,7 @@ function calcAlt(alt, cur_altitude) { return ddalt; } function updateGps() { - let have = false, lat = "lat ", alt = "?", + let lat = "lat ", alt = "?", speed = "speed ", hdop = "?", adelta = "adelta ", tdelta = "tdelta "; @@ -196,7 +196,6 @@ function updateGps() { adelta = "" + (cur_altitude - fix.alt).toFixed(0); speed = "" + fix.speed.toFixed(1); hdop = "" + fix.hdop.toFixed(0); - have = true; } else { lat = "NO FIX\n" + "" + (getTime() - gps_start).toFixed(0) + "s " From 61723ddf0d9310b123e8edc09c2d524629136947 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 13 Jul 2024 12:45:53 +0200 Subject: [PATCH 082/133] [] iconbits: initial import --- apps/iconbits/ChangeLog | 1 + apps/iconbits/README.md | 3 + apps/iconbits/app-icon.js | 2 + apps/iconbits/app.png | Bin 0 -> 2459 bytes apps/iconbits/iconbits.app.js | 345 ++++++++++++++++++++++++++++++++++ apps/iconbits/metadata.json | 14 ++ 6 files changed, 365 insertions(+) create mode 100644 apps/iconbits/ChangeLog create mode 100644 apps/iconbits/README.md create mode 100644 apps/iconbits/app-icon.js create mode 100644 apps/iconbits/app.png create mode 100644 apps/iconbits/iconbits.app.js create mode 100644 apps/iconbits/metadata.json diff --git a/apps/iconbits/ChangeLog b/apps/iconbits/ChangeLog new file mode 100644 index 000000000..263d4078d --- /dev/null +++ b/apps/iconbits/ChangeLog @@ -0,0 +1 @@ +0.01: attempt to import diff --git a/apps/iconbits/README.md b/apps/iconbits/README.md new file mode 100644 index 000000000..21acfa5a0 --- /dev/null +++ b/apps/iconbits/README.md @@ -0,0 +1,3 @@ +# Icon Bits ![](app.png) + +Bitmap editor suitable for creating icons and fonts for BangleJS2. diff --git a/apps/iconbits/app-icon.js b/apps/iconbits/app-icon.js new file mode 100644 index 000000000..d32528cb6 --- /dev/null +++ b/apps/iconbits/app-icon.js @@ -0,0 +1,2 @@ +require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/ACkAgAuuGFovuL2Qu/F2amDABRdgC5QKDHSIvdOZQuaApYuGJBYvPJYwvLPBgvOJY4hGRYwvYJZAFGXQy/mdRAvqZpguSF5waMF8IaNF74aPF6ghTF/4UFAApfuGoyPwABoufSEQv/BpowRF6i6IF8oEDHAwueF/4vwD6gvZbyA/VCwIXHL8hQZZ8CQSIRhAiGIbhEcxQvaGo4YCAgQAJF7pBUIQgvnVY4vrAHQ=")) + diff --git a/apps/iconbits/app.png b/apps/iconbits/app.png new file mode 100644 index 0000000000000000000000000000000000000000..7a3ee2e90600531bd4df39ad517637ecdc854745 GIT binary patch literal 2459 zcmdT^`(M)M7YF>5q^T&GEfvKXX3l2W@&Y==TV!hTRYzF#Y3-{c6iH11ub<|vEUkHK zX}P@46lEFeM(C%jIi{s0G{AJawB}SS6&;yh-2RI1dA-ha&Uw!Jea?BFAI`bMp+QCl z+YJy1gc0R{KUHUi>x$l}JKH+{VCW1oi5f&kv<_Ik(FH&fnG%84Njmzg0t8}H0L9-o zg3Fk>R+w1lzB%C8+S;Jxk}gmVz&$h1 z5Ei5|28WNX2v9ExMO;=MXSSPdq23QReNRetr%8tOIt{LDxwjps2*!1Vz^Vt78)Heu z`s`*hmymCSd&;%78+R^?jKuFNxnYD8@)5{ruRv`wPF--N9rNa%yC&xoyUBeD6`dmC z59l=-o0PDAN3zH1+%oTkcd*^qqE}H!@B0sTCeGUg&fgEKLvOFKPI^WL3y2HmQ}aMK zcW|+T6h8|wq`bE&Uo7`xUrP{XRJ{vCzvrM> z3C5zCB1>OZY{rqutblB|m>`SMz*>EEAb?EdYI z+z=9*bunxWP3UE9Q^o704x^&o@0 z+X`md(N|K(Jtf%u?4Grx!dKHwd7Gzt&1Y-?DB5_~KvLeKND(C-nd4^3gN;=?^jj@p z)+Sc(V26}2HVH7$SaINi0_OV_2jQ!Ph56P2r)|@T^`~o^EIJ{&V`kj3mnMgJ#z z`cLUxWBm)RTaA484#+jih8~G;$$7@UeX>V6Dmna=4YXk-DOm}(+UCO9cn%v`%-d?W z3nP$pjPTAlWLDi;;eq|%Y;MlhB&*E=gZDf3zAc(Fx|8|x$9Ely6{b>TCfriJ04#Yv zCFJn_gk@WLr?c8d)E?@O1Z`->spDqq(V*s}VJHa8Nkyd%=}X^fPp)ZuSdVZ5gA?Z8 zN8~4!3ynw1tyAs)3Hi}C=Sk%3dO=5?YB-ZkrH zj4YruM-c^|jKCLl-~Kx#0+}ix(_W_q^mm|eAM0O_gon&;G0ki8vUdz|AFi$)O|06L z^M{G!E44S`w@1T@w5jiI>UygODZLM$|5}{2fwU2-bxVjsmQ#zw8&nrznKjL9j-XJi z%)bU?vY$S~JYD$j2tu~p*h1!_-j}*)qTj0lD3voNX^3 zs{7zQJ{PQ8KhB_|H!~4@6BBnVrr%6C*&#-!-)ZvgGUKfUd<`91G2kseys-WB@k~)^ zUiO*=l^0HBrxQ5+%R|P+vJj2QmrF_huqQHjez0KS;2*<4OY{KGiFIneQ#J7fDkeW~ zy)WK#d-e0iV?$P5QP*6y?_}l&ll|a9^yYHCSW~vZs;g9a@Lq!DlEdQlY*@SN{%}9d zq*HFkag@*e*x+!{@nIw1IQZ5;w1iI zqbD>+i|F5D%Lz&7cs(71>ZFmS-u2eTP&EJeZ1i~(fStURY;5G5jLU{AGQUl62V{LM zkxq`@x~6Ry?#yf4#}|NS0FZjn)A*B2LN-Bp`XaRaZGFGlwcO+s+S2Gx75 zuE-qX^R#sIW=Q`FcMtVdO2+JG);)cY_D>rW(O+yq^xzyvmgo`t#OJo5H0CaVG4@4u zW?Ut>8uOh-TOyrTPU?p#Vks6Cuf4^_ai;@WvCp!s2)*173$Mk#WUFO?rXdHuYgHMd zO2zt(fkh*gKJCROx}lr9ufO_beR?fEe50u!s2jocuLr*+-8~}LrCoH-%m}8qYHd)u z6v-7C)9=;ZV!0k{hWem1(~tgj*MT{HB|?h_?uK`dpa;kcKc~B}SKX}mm!A`N>xy8u znUTq#`qrlCzVsh*t^K0nA4f-BGTV1b|KDa9_k+|nuYU+S-2VY#O@9YOAGJ()7lP0U MB_Pzll? g.getWidth() - 32 && tap.y < 32) { + if (tap.b === 1) { + if (tapTimer === null) { + tapTimer = setTimeout(function () { + g.clear(); + drawUtil(); + oldX = -1; oldY = -1; + + tapTimer = null; + }, 800); + } + if (discard) { + clearTimeout(discard); + discard = null; + return; + } + nextPen(); + } + drawUtil(); + return; + } else if (tap.y < 32) { + if (mode == "draw") + nextColor(); + else + selectColor(tap.x); + drawUtil(); + return; + } + oldX = to.x; + oldY = to.y; + sg.setColor(kule[0], kule[1], kule[2]); + g.setColor(kule[0], kule[1], kule[2]); + + do_draw(from, to); + drawUtil(); + } + function on_btn_old(n) { + } + function on_btn(n) { + function f(i) { + return "\\x" + i.toString(16).padStart(2, '0'); + } + print("on_btn", n); + print(g.getPixel(0, 0)); + s = f(0) + f(font_width) + f(font_height) + f(1); + // 0..black, 65535..white + for (let y = 0; y < font_height; y++) { + let v = 0; + for (let x = 0; x < font_width; x++) { + let p = sg.getPixel(x, y); + v = v << 1 | (p==0); + } + s += f(v); + } + print("Manual bitmap\n"); + print('ft("' + s + '");'); + if (1) { + s = ""; + var im = sg.asImage("string"); + for (var v of im) { + //print("val", v, typeof v); + s += f(v); + } + //print("wh", im, typeof im, im[0], typeof im[0]); + //print("Image:", im.length, s); + print('fi("'+btoa(im)+'");'); + } + + + } + + setup("icon"); + drawArea(); + Bangle.setUI({ + "mode": "custom", + "drag": on_drag, + "btn": on_btn, + }); + drawUtil(); + + +function ft(icon) { + g.reset().clear(); + g.setFont("Vector", 26).drawString("Hellord" + icon, 0, 0); +} + +function fi(icon) { + g.reset().clear(); + g.drawImage(atob(icon), 40, 40); +} + +icon_gps = "\x00\x08\x16\x01\x00\x00\x00\x99\xbd\xff\xbd\x99\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; +icon_hpa = "\x00\x08\x16\x01\x00\x80\xb0\xc8\x88\x88\x88\x00\xf0\x88\x84\x84\x88\xf0\x80\x8c\x92\x22\x25\x19\x00\x00"; +icon_foot = "\x00\x08\x16\x01\x00\x18\x3c\x7e\x7e\x7e\x7c\x78\x78\x00\x78\x78\x78\x30\x00\x00\x00\x00\x00\x00\x00\x00"; +icon_9 = "\x00\x08\x16\x01\x00\x00\x00\x00\x38\x44\x44\x4c\x34\x04\x04\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; +icon_10 = "\x00\x08\x16\x01\x00\x08\x18\x28\x08\x08\x08\x00\x00\x18\x24\x24\x24\x24\x18\x00\x00\x00\x00\x00\x00\x00"; +icon_sunrise = "\x00\x08\x16\x01\x00\x00\x00\x24\x00\x99\x24\x42\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; +icon_sunset = "\x00\x08\x16\x01\x00\x00\x24\x00\x81\x00\xff\x42\x24\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; +icon_gps_no_paused = "\x00\x08\x16\x01\x00\x24\x24\x24\x24\x24\x00\x80\x9c\xa2\xe3\xa2\x9c\x80\x00\x00\x00\x00\x00\x00\x00\x00"; + +//ft(icon_10 + "23.1" + icon_hpa); \ No newline at end of file diff --git a/apps/iconbits/metadata.json b/apps/iconbits/metadata.json new file mode 100644 index 000000000..b98a43953 --- /dev/null +++ b/apps/iconbits/metadata.json @@ -0,0 +1,14 @@ +{ "id": "iconbits", + "name": "Icon bits", + "version": "0.01", + "description": "Bitmap editor suitable for creating icons", + "icon": "app.png", + "readme": "README.md", + "supports" : ["BANGLEJS2"], + "allow_emulator": true, + "tags": "tools", + "storage": [ + {"name":"iconbits.app.js","url":"iconbits.app.js"}, + {"name":"iconbits.img","url":"app-icon.js","evaluate":true} + ] +} From 422892cb4866581eb6886a7aa59c3208a6642c81 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 13 Jul 2024 13:09:00 +0200 Subject: [PATCH 083/133] [] iconbits: fix warnings --- apps/iconbits/iconbits.app.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index c45af382b..6d9ff2973 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -64,6 +64,7 @@ Bangle.on("lock", function() { kule[2] = Math.random(); } function selectColor (x) { + let c; if (x < g.getWidth()/2) { c = 0; } else { @@ -114,7 +115,6 @@ Bangle.on("lock", function() { g.clear(); if (mode == "draw") return; - const w = g.getWidth; g.setColor(0, 0, 0.5); g.fillRect(0, 0, g.getWidth(), g.getHeight()); g.setColor(1, 1, 1); @@ -278,15 +278,13 @@ Bangle.on("lock", function() { do_draw(from, to); drawUtil(); } - function on_btn_old(n) { - } function on_btn(n) { function f(i) { return "\\x" + i.toString(16).padStart(2, '0'); } print("on_btn", n); print(g.getPixel(0, 0)); - s = f(0) + f(font_width) + f(font_height) + f(1); + let s = f(0) + f(font_width) + f(font_height) + f(1); // 0..black, 65535..white for (let y = 0; y < font_height; y++) { let v = 0; @@ -342,4 +340,4 @@ icon_sunrise = "\x00\x08\x16\x01\x00\x00\x00\x24\x00\x99\x24\x42\xff\x00\x00\x00 icon_sunset = "\x00\x08\x16\x01\x00\x00\x24\x00\x81\x00\xff\x42\x24\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; icon_gps_no_paused = "\x00\x08\x16\x01\x00\x24\x24\x24\x24\x24\x00\x80\x9c\xa2\xe3\xa2\x9c\x80\x00\x00\x00\x00\x00\x00\x00\x00"; -//ft(icon_10 + "23.1" + icon_hpa); \ No newline at end of file +//ft(icon_10 + "23.1" + icon_hpa); From b6f02430741f02c0c07d14b742339cb3b9178ee1 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 13 Jul 2024 17:37:18 +0200 Subject: [PATCH 084/133] [] iconbits: disable unused warnings. --- apps/iconbits/iconbits.app.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index 6d9ff2973..625070dd5 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -1,5 +1,7 @@ // Icon bits, thanks to tinydraw - +/* eslint-disable no-unused-vars */ +/* We have functions we expect user to call from command line, so they + * appear "unused" to lint */ // font, draw, icon let mode; @@ -331,13 +333,4 @@ function fi(icon) { g.drawImage(atob(icon), 40, 40); } -icon_gps = "\x00\x08\x16\x01\x00\x00\x00\x99\xbd\xff\xbd\x99\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; -icon_hpa = "\x00\x08\x16\x01\x00\x80\xb0\xc8\x88\x88\x88\x00\xf0\x88\x84\x84\x88\xf0\x80\x8c\x92\x22\x25\x19\x00\x00"; -icon_foot = "\x00\x08\x16\x01\x00\x18\x3c\x7e\x7e\x7e\x7c\x78\x78\x00\x78\x78\x78\x30\x00\x00\x00\x00\x00\x00\x00\x00"; -icon_9 = "\x00\x08\x16\x01\x00\x00\x00\x00\x38\x44\x44\x4c\x34\x04\x04\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; -icon_10 = "\x00\x08\x16\x01\x00\x08\x18\x28\x08\x08\x08\x00\x00\x18\x24\x24\x24\x24\x18\x00\x00\x00\x00\x00\x00\x00"; -icon_sunrise = "\x00\x08\x16\x01\x00\x00\x00\x24\x00\x99\x24\x42\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; -icon_sunset = "\x00\x08\x16\x01\x00\x00\x24\x00\x81\x00\xff\x42\x24\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; -icon_gps_no_paused = "\x00\x08\x16\x01\x00\x24\x24\x24\x24\x24\x00\x80\x9c\xa2\xe3\xa2\x9c\x80\x00\x00\x00\x00\x00\x00\x00\x00"; - //ft(icon_10 + "23.1" + icon_hpa); From afc7744eeaf5dad0ad9a56c1a2de90ae4bc1d864 Mon Sep 17 00:00:00 2001 From: bb0x88 <163612910+bb0x88@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:48:48 +1200 Subject: [PATCH 085/133] Update metadata.json --- apps/beeptest/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/beeptest/metadata.json b/apps/beeptest/metadata.json index 87a815c3e..c0bd7744a 100644 --- a/apps/beeptest/metadata.json +++ b/apps/beeptest/metadata.json @@ -5,11 +5,11 @@ "version": "0.01", "description": "Aerobic fitness test created by Léger & Lambert", "icon": "beeptest.png", - "tags": "", + "tags": "Health", "supports": ["BANGLEJS2"], "readme": "README.md", "storage": [ { "name": "beeptest.app.js", "url": "beeptest.js" }, - { "name": "beeptest.img", "url": "app-icon.js", "evaluate": true } + { "name": "beeptest.png", "url": "app-icon.js", "evaluate": true } ] } From ee9e81d699bf6ba6912fc0560919df215fdf8b30 Mon Sep 17 00:00:00 2001 From: bb0x88 <163612910+bb0x88@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:49:18 +1200 Subject: [PATCH 086/133] Update app-icon.js --- apps/beeptest/app-icon.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/beeptest/app-icon.js b/apps/beeptest/app-icon.js index de5ca57f2..b29e2b459 100644 --- a/apps/beeptest/app-icon.js +++ b/apps/beeptest/app-icon.js @@ -1,5 +1,2 @@ -require("heatshrink").decompress( - atob( - "mEwwcBkmSpIC/yEACJ5bCBZGf/IFDgVJCJH/AAPkAoMgiQCDCIeTCAX//gRLz4RD//yCJvyv///w1MAoNPCoJcBLJRcFPpgCBn5uCUJoRCNwQRO/4RNQAYLFwBOCKAaSFCJR9CCIcnCgoCDCIikBn4CBCI6zBCIgCCCIzsEAAPPJQwRHQAxZDhL+E/MnUgoRFEQfJAoXkUJARFFIKwIF4RlDyf5CJibJcw4RILITUFTZARILIQ1JaggRSNBLmGCKSeJfA4RKcwY1NCILyDARbyLag4gMCIZ3LcwoRRX5YCDepIRI+QRPn6MOAWg=", - ), -); +require("heatshrink").decompress(atob("mEw4UA///+f8lky6f8HFmqBRMK1WgBAtUBYUABYtVqtAgEoAIQACioLBqALHBQIABBZMFEgIjHgEBqtUHY4aDKZA+CoBrIBYJJBBZJuCAA3VBYkC1QABGoJhDBYxTBBYUFEQoLDoEVSgIADO4ILCUASdGqtRGIYLFKoY7CIwdUEwJtBBYY6CqADBFwoLDDYIuFIwQUBigLITJQLFHYKNEHAgLGXw6NDBZbKHTIYLLKg6lDBY4KDEY5EIIwahFHQoKIBYIrHIwYLLuALJHRTcHAAjcGAEwA==")) + From 249baf427daceda5e9692ef6127c5603fc2d5314 Mon Sep 17 00:00:00 2001 From: bb0x88 <163612910+bb0x88@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:54:09 +1200 Subject: [PATCH 087/133] Update metadata.json --- apps/beeptest/metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/beeptest/metadata.json b/apps/beeptest/metadata.json index c0bd7744a..a9856bc93 100644 --- a/apps/beeptest/metadata.json +++ b/apps/beeptest/metadata.json @@ -10,6 +10,6 @@ "readme": "README.md", "storage": [ { "name": "beeptest.app.js", "url": "beeptest.js" }, - { "name": "beeptest.png", "url": "app-icon.js", "evaluate": true } + { "name": "beeptest.img", "url": "app-icon.js", "evaluate": true } ] } From 502755b253fba0cdc4350a6fc6b7540d5c41e145 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sun, 14 Jul 2024 14:17:54 +0200 Subject: [PATCH 088/133] feat(measuretime): add outline for 3d effect --- apps/measuretime/measuretime.app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/measuretime/measuretime.app.js b/apps/measuretime/measuretime.app.js index c7865bffe..0780ba9bd 100644 --- a/apps/measuretime/measuretime.app.js +++ b/apps/measuretime/measuretime.app.js @@ -27,10 +27,10 @@ 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 = []; + var polyLeftTop = [0, 0]; + var polyLeftBottom = [0, g.getHeight()]; + var polyRightTop = [g.getWidth() - 1, 0]; + var polyRightBottom = [g.getWidth() - 1, g.getHeight()]; let xL = 0; let xR = g.getWidth() - 1; let yT = centerY - 13; From eda83a74f17155867a2e3e7d3b1b711a729e1ee2 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sun, 14 Jul 2024 14:19:16 +0200 Subject: [PATCH 089/133] fix(measuretime): make sure big numbers are aligned as beautiful as small numbers --- apps/measuretime/measuretime.app.js | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/measuretime/measuretime.app.js b/apps/measuretime/measuretime.app.js index 0780ba9bd..975996aa4 100644 --- a/apps/measuretime/measuretime.app.js +++ b/apps/measuretime/measuretime.app.js @@ -1,7 +1,5 @@ { require("Font7x11Numeric7Seg").add(Graphics); - g.setFont("7x11Numeric7Seg"); - g.setFontAlign(0, 0); const centerY = g.getHeight() / 2; //88 const lineStart = 25; @@ -70,6 +68,16 @@ g.fillPolyAA(polyRightBottom, true); }; + let hourStringXOffset = function (hour) { + if (hour == 1) { + return lineEndFull - 5; + } + if (hour < 10 || hour > 20) { + return lineEndFull + 5; + } + return lineEndFull - 5; + }; + let drawTime = function () { g.clear(); var d = new Date(); @@ -86,19 +94,19 @@ var lineEnd = lineEndDefault; g.setFont("7x11Numeric7Seg", 2); - g.setFontAlign(0, 0); + g.setFontAlign(-1, 0); // gone do { switch (yTopLines - 88 + mins) { case -60: lineEnd = lineEndFull; - g.drawString(d.getHours() - 1, lineEnd + 10, yTopLines, true); + g.drawString(d.getHours()-1, hourStringXOffset(d.getHours()-1), yTopLines, true); break; case 0: case 60: lineEnd = lineEndFull; - g.drawString(d.getHours(), lineEnd + 10, yTopLines, true); + g.drawString(d.getHours(), hourStringXOffset(d.getHours()), yTopLines, true); break; case 45: case -45: @@ -128,11 +136,11 @@ case 0: case 60: lineEnd = lineEndFull; - g.drawString(d.getHours() + 1, lineEnd + 10, yBottomLines, true); + g.drawString(d.getHours() + 1, hourStringXOffset(d.getHours()+1), yBottomLines, true); break; case 120: lineEnd = lineEndFull; - g.drawString(d.getHours() + 2, lineEnd + 10, yBottomLines, true); + g.drawString(d.getHours() + 2, hourStringXOffset(d.getHours()+2), yBottomLines, true); break; case 15: case 75: From effd862cafa8b9e91783ba7ce75eb6739a94caab Mon Sep 17 00:00:00 2001 From: Bernhard Date: Sun, 14 Jul 2024 14:28:23 +0200 Subject: [PATCH 090/133] fix(measuretime): update changelog, update versionnumber --- apps/measuretime/ChangeLog | 3 ++- apps/measuretime/metadata.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/measuretime/ChangeLog b/apps/measuretime/ChangeLog index 81fba8e15..d0ac21aac 100644 --- a/apps/measuretime/ChangeLog +++ b/apps/measuretime/ChangeLog @@ -1 +1,2 @@ -0.1: Initial release \ No newline at end of file +0.1: Initial release +0.2: Draw line for 3d effect, fix number alignment diff --git a/apps/measuretime/metadata.json b/apps/measuretime/metadata.json index 4c0db8b32..536d08ac9 100644 --- a/apps/measuretime/metadata.json +++ b/apps/measuretime/metadata.json @@ -1,7 +1,7 @@ { "id": "measuretime", "name": "Measure Time", - "version": "0.1", + "version": "0.2", "description": "Measure Time in a fancy way.", "icon": "small_measuretime.png", "screenshots": [{ "url": "measuretime.png" }], From b2ffbac7b1f1fd89b440485b21f59f3c144d78da Mon Sep 17 00:00:00 2001 From: Bernhard Date: Mon, 15 Jul 2024 06:20:08 +0200 Subject: [PATCH 091/133] fix(measuretime): align everything starting with a 2 --- apps/measuretime/measuretime.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/measuretime/measuretime.app.js b/apps/measuretime/measuretime.app.js index 975996aa4..97b56c710 100644 --- a/apps/measuretime/measuretime.app.js +++ b/apps/measuretime/measuretime.app.js @@ -72,7 +72,7 @@ if (hour == 1) { return lineEndFull - 5; } - if (hour < 10 || hour > 20) { + if (hour < 10 || hour >= 20) { return lineEndFull + 5; } return lineEndFull - 5; From 91c489bada4cb1fff31f0abfe55f534270ebfb35 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Tue, 16 Jul 2024 11:16:30 +0200 Subject: [PATCH 092/133] [] iconbits: provide better README. --- apps/iconbits/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/iconbits/README.md b/apps/iconbits/README.md index 21acfa5a0..c5c524ab0 100644 --- a/apps/iconbits/README.md +++ b/apps/iconbits/README.md @@ -1,3 +1,7 @@ # Icon Bits ![](app.png) Bitmap editor suitable for creating icons and fonts for BangleJS2. + +You'll want to run a copy of this in simulator, and another one on +watch to view the results. Draw using the provided tools, then press +the button, and you'll get result on the console. From 84c332b8a9b585e251fe6b2866c191cd4d5a7f33 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Tue, 16 Jul 2024 14:31:23 +0200 Subject: [PATCH 093/133] feat: add new app icon, add new screenshots, update metadata --- apps/measuretime/README.md | 6 ++++-- apps/measuretime/measuretime-icon.js | 2 +- .../{measuretime.png => measuretime_icon.png} | Bin 5005 -> 5669 bytes apps/measuretime/metadata.json | 7 +++++-- apps/measuretime/screenshot_dark.png | Bin 0 -> 896 bytes apps/measuretime/screenshot_light.png | Bin 0 -> 1268 bytes apps/measuretime/small_measuretime.png | Bin 5456 -> 0 bytes 7 files changed, 10 insertions(+), 5 deletions(-) rename apps/measuretime/{measuretime.png => measuretime_icon.png} (51%) create mode 100644 apps/measuretime/screenshot_dark.png create mode 100644 apps/measuretime/screenshot_light.png delete mode 100644 apps/measuretime/small_measuretime.png diff --git a/apps/measuretime/README.md b/apps/measuretime/README.md index 78d04f30d..f672457f4 100644 --- a/apps/measuretime/README.md +++ b/apps/measuretime/README.md @@ -1,7 +1,9 @@ # Measure Time Measure time in a fancy way. Inspired by a Watchface I had on my first Pebble Watch. +Icon from [Flaticon created by Smartline]("https://www.flaticon.com/free-icons/scale") -Written by [prefectAtEarth](https://www.github.com/prefectAtEarth/) +Watchface written by [prefectAtEarth](https://www.github.com/prefectAtEarth/) -![](measuretime.png) \ No newline at end of file +![](screenshot_light.png) +![](screenshot_dark.png) diff --git a/apps/measuretime/measuretime-icon.js b/apps/measuretime/measuretime-icon.js index 4592548a7..059a5c744 100644 --- a/apps/measuretime/measuretime-icon.js +++ b/apps/measuretime/measuretime-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEw4n/AAIHB/fe8EHrvv333xVS221jnnlFC7//9NP997zXWjHGn+EGJsu9wAC0AHBgugq99C5d0kUq1WtoAHBgnaw8nC5d9mdwgEN7QHBxvQ5nhGwQXNiQHB19A41xC5dy3YXCwAHBwkqx3tI5d3AAV8L4UIDYRkBogADpTOQhWqAAZOLAAuoxAABfyYXXI4pKRO4oACqBHl0QXWAC8IF4QABwpHRkUilALHgutvwvMBY8NoEHKakCqtHR5gAH1FY7wUFcYS/LI5Fwd4r7IqXuJ4uUAYMK1QABKhEKIAQAC1kW7SnDAAUlPxnBiN9xEnu93vx6KAAeHyMdI5wAGox3OS5GAU4oAEoAXJhTXGfigAWhAvWX6QvcT5nog5HJF5QXLX5AAC0levwXId5cNoAvJhWqAAILHgVAhxHMQaZfFwoXQI5YALO5ZHPC6bXDAAmADqYARhBHXkUilC/oA=")) \ No newline at end of file +require("heatshrink").decompress(atob("wGAwMB//+BQk//4AB8YDCAAP8B4kPBIWAB4n4B5EAv4PJg4IBHAI0DB5I4Bj4PMBAI0DB5PwAgYPK8EAgYPECoYED4EARgIPLwAlBB5g1CQAYPHW4aADB463DQAYPHM4aADB45+BKAJKHAgfggOAoCADB4/AgeAsCADB4+Ag+AuCADB45MBwF4QAYPGPwMfwF8QAZnDv4PBPwM/wF+QAYPIv+Av4PKF4JRBCIIvJ/5rBB4PAJ4YPGwYPC8BaDB43HB4XxB5QABB4IECB6LUIB/4P/B+zOCB/4P8X/4P/B/7PBaAIP/B/a//B/4P/ZwQP/B/i//B/4P/Z4LQBB6XvB53HB4XxB5WDB4XgB5UBB4XAGIQPG/0AB4WAn4PI/gEBB4MAj4PKn+AvwPER4ovBj+AvgTBT5MAh+AvAoBB5OAg+AuBTBB5PAgeAsBzBB5PggOAoEDB5XwgGAgDqIAgR3Dh4PKQAIABPwQPIQAIABPwQPIQAKnCB5ZOBPwYPJ4EAPwZXEAAIPC8EAPwYPJQAIpDB5IIBPwYPJQAJ+DB5KABPwYPGFQZ+EU4gPFwAODU4ipF8YODA")) diff --git a/apps/measuretime/measuretime.png b/apps/measuretime/measuretime_icon.png similarity index 51% rename from apps/measuretime/measuretime.png rename to apps/measuretime/measuretime_icon.png index 67425e1dc50b48638ab462ed71b245a1dc8898b7..c7f9cb8b8a975efb60f3bd967a7e8fa37547d370 100644 GIT binary patch delta 2416 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 B>PY|q diff --git a/apps/measuretime/metadata.json b/apps/measuretime/metadata.json index 536d08ac9..6ba022dc0 100644 --- a/apps/measuretime/metadata.json +++ b/apps/measuretime/metadata.json @@ -3,8 +3,11 @@ "name": "Measure Time", "version": "0.2", "description": "Measure Time in a fancy way.", - "icon": "small_measuretime.png", - "screenshots": [{ "url": "measuretime.png" }], + "icon": "measuretime_icon.png", + "screenshots": [ + { "url": "screenshot_light.png" }, + { "url": "screenshot_dark.png" } + ], "type": "clock", "tags": "clock", "supports": ["BANGLEJS2"], diff --git a/apps/measuretime/screenshot_dark.png b/apps/measuretime/screenshot_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c9f41e464f9ac4a02bae1ec206599d4d90967d26 GIT binary patch literal 896 zcmeAS@N?(olHy`uVBq!ia0vp^8$g(Y2}mkjE#?GLjKx9jP7LeL$-D$|n2Vh}LpV4% zZa?&Y0OVpSL;BZYM1*E6L$r!RW^3sKCMG7@)=JrF!s$ z0tX9_Tk4(4pPOdBbW%c!083NBlM@g2-nR-{85q4iWZEtt^PuO84<6Myz3Jnvy!t0M zYg+rJIRKR{2ocKB;shIKHZ}dHr9-Y({_-mq-#roWPYOOM3N`qtDAz6aO)FI-Bv~32 zZiLJ`x-4kro)#r*BdrZ9m#7K=1+VB%wH3K^7iJcd zT}y+MC7o896u{KX|{DeWY{y_`7=P3wz%S6crfWXaA)p`MOb{HM{yRw|DZ>UN{^xt%p8_U~%G&HH~&z*lWwT)n}g?}>Y^FaG{K|4@$i*~#xiHXSP0@;p5K`IU!h zAGWYG1#A!bb|I}S7#a;U47w8&4^|(3zhblN-Jr-mf z{Is-T-%kH$AvssB=}w)$0+J*)>6|{6z5DOP^kQbwC}>Ppwl;lhgvC274eg!+N^vRf zZmecgfnHP)XxX)NQ@hiy*}h20b*YG16%GeF+`uS}`^MHI8&zVvbrTjaF)B-e(JbABX%- z&0*~46PUI4Veh&6_s=b*LCW)4uV5JKcAz^DWXw$_IbFfcOZ1dCuTWf7d^d;jurtV3 zm^9o`HG+#}RZ7b@u-aH1hzyeN$Xfi_-xlZ?2UaFG69|evuvqocnxN^kS7U}o#IAKm z#S8)(!Ql%IVgqj(ts6{iS^@@(1i|PgQ<-jI`f=;l;+q)4I-b{)P-9|-DxYW$BeN)n z#KnC~b^k>6v~{W6&VBj!rXHhmuW7<{A#>^TUeCX(C#*Z<@$qldk5X>O10EmE5|obJ z{JT%~Zz1cwg7@vlRa?FH@>G1A`s1qjx&!+6cHJ}mV)o#s;NpAV6!Q0<*l%B#{(k@K zPcNB%7$)RtZdxPuVR?c1Zl-^I%U^zuQhZ>0v98=A;m6y(8E3eDWVp=bW8d@Lp#IOa zJM-h0&HJ=>ZA#dIxqaIsvu|JAWxvm7p zRBRmEnXBgWr50IT&-^{_r+pzeC_1eonL*JR^Ec836pLS!AFQ_juo|3RZ1=zazg>W3 z4;ymg0;cOUZls_Dh9(f@Fz%kV<7P$epF7YHMhII>TbI2Nk*E_~HnW})YUkS4(8$Ho zlL0~78e+Zg-2CYGFa|?d#{1G%v`A0ubY?|MM(b4b%4VT~X4o zd5N9CRk!E!tMaxrJlxp2i}mO4$5#bDBtcxsvExKW=M2?S$(gON#MaxeaS2fSF6$d{ z>~gXSCe??f=l;KUzSsV!U_k&i%N3FpCFGE$ zdmF?Tx2*u_2IuYF&AJaRGM=0C%OOO1ZvCsBXvr8DG3irx-HB;MD?P3Y9lunn3ks_9 a+>h-Yd%JDT^;d$*GzL#sKbLh*2~7Zj04DYT literal 0 HcmV?d00001 diff --git a/apps/measuretime/small_measuretime.png b/apps/measuretime/small_measuretime.png deleted file mode 100644 index 74f476dad35df04fb500d366a34ffe88822f36e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5456 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 From ab3d6fe4682fa351b4761c04240dda6ae3943962 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Wed, 17 Jul 2024 07:02:28 +0200 Subject: [PATCH 094/133] fix(measuretime): shrink app icon to 48px square size --- apps/measuretime/measuretime-icon.js | 2 +- apps/measuretime/measuretime_icon.png | Bin 5669 -> 5109 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/measuretime/measuretime-icon.js b/apps/measuretime/measuretime-icon.js index 059a5c744..7dc5b31d3 100644 --- a/apps/measuretime/measuretime-icon.js +++ b/apps/measuretime/measuretime-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("wGAwMB//+BQk//4AB8YDCAAP8B4kPBIWAB4n4B5EAv4PJg4IBHAI0DB5I4Bj4PMBAI0DB5PwAgYPK8EAgYPECoYED4EARgIPLwAlBB5g1CQAYPHW4aADB463DQAYPHM4aADB45+BKAJKHAgfggOAoCADB4/AgeAsCADB4+Ag+AuCADB45MBwF4QAYPGPwMfwF8QAZnDv4PBPwM/wF+QAYPIv+Av4PKF4JRBCIIvJ/5rBB4PAJ4YPGwYPC8BaDB43HB4XxB5QABB4IECB6LUIB/4P/B+zOCB/4P8X/4P/B/7PBaAIP/B/a//B/4P/ZwQP/B/i//B/4P/Z4LQBB6XvB53HB4XxB5WDB4XgB5UBB4XAGIQPG/0AB4WAn4PI/gEBB4MAj4PKn+AvwPER4ovBj+AvgTBT5MAh+AvAoBB5OAg+AuBTBB5PAgeAsBzBB5PggOAoEDB5XwgGAgDqIAgR3Dh4PKQAIABPwQPIQAIABPwQPIQAKnCB5ZOBPwYPJ4EAPwZXEAAIPC8EAPwYPJQAIpDB5IIBPwYPJQAJ+DB5KABPwYPGFQZ+EU4gPFwAODU4ipF8YODA")) +require("heatshrink").decompress(atob("mEwwMB/EAgf/5//AoMB/+HAogNB/H/+P+A4IFC/kAAoYUBv4FC+GMn4FC8OOj/4gPx8ePAof3x+/CIX/x4jDAqsPAqAvdKYIFJLP5ZPVoa5FYorRHborpFeocAAYMDAomHAoQJB5//A=")) diff --git a/apps/measuretime/measuretime_icon.png b/apps/measuretime/measuretime_icon.png index c7f9cb8b8a975efb60f3bd967a7e8fa37547d370..94d295aee811068b30d639e7414a1f4912934a58 100644 GIT binary patch delta 1527 zcmV47_=WdsTS^cf3CW{HS0Sd+K>>H0kHuv4hM8is&Ycr#)zVM zo)w_@B)X7)dpZz485>!VTqnb30004gX+uL$Nkc;*aB^>EX>4Tx0C=2zkvT{MQ4~eb zOq7TyA_f$(m?B!4Mn$yKC~@6YNsl8z3N0?g{xUTWFMfUn^_=DfsI;ruo+N7=^ zw7`y(TV@ zwg3PQTeE!viUK4!Ff%h_F=b>eFfm~=Ei^M_Wi4T0VK*%>GB-G7VrFDAVl!rwD+Q?w zIWjUeHZwUlI5Iht-vx0BH&ie-IyE;sGd7c21|$kMR4_I=H8(mlHnWHZ5do9723#aL zWn(g9WH&S|GG#esEi^J^Vl6o^HDoP0Fg9amIb=0vHDx!GHU~=zH&ie-IyE;sGd8n} z2W|)iU`k;Qll>1ee-Q*ZBw5HF5&!@Kz)3_wRA}Dqn$2nyK@fny8sa7>crp3_c?l84 z72-t^QKM(^pawVQ;0tKZ{$CQ%gBZb!nu|mahQu|#fq2b9Pm1{wl$ZpkJZwWcli8W> z*&(wdQ_$?N(>+^XRdr8oSHpJM3(Nu`pc&gDa2f{@5dnsQe-&UF=%+)ecc8f4rX7AO z07ijzYg`l9*9pL&^{~Idsz+P1#=di~{Ags14bh(mz^sV$ib$VJ15UaC80D`=EFW!i zSs`qI8PBoD9RT9OCTw$^8cqQ_D8}+5z-?fc`ML>wHrEO~GjIS$fvF@S_lOq(i`!_x~hg{@jzWwS5$S`v?WzNSSk+yeUV8RlnEe=tQs2kGU;Z3 zrih$~nFHnVfU2%l%V>cBO3B{?xuw%Z0_2rWS92h1HkxNTT?oKfnOC7Iovza9DxI#< z=_;MBf70nHo&Mj_>6*{(oY|J|Az^opgbGbk3xUJ`P9(5k-dp|#h49{7=B`K!P~q*vzl|j zbNPt=FBiQWxI4H|8Z8hD3Ik%v7RF)`oKiIpOb+gt&b dfw#Vv{|5~Xw_FuTl}`Ww002ovPDHLkV1f&!m+t@o delta 2265 zcmZXWcU;nY7sr46Lg5k)z0w15HC*Idi2A$CWfOP0N+i&>uL;EEPwNa#os_ znhlW367zACvz{~?5ajJ*w^YhhDvXEjAJ4t7=lSD%#_Rhz@AJnwf1E|5M*PMz{=V)g zC0!)|08pMDu7NU7`}GhCGTTesKlIBLZ`6lgqk6F_l9>ELn!dn7H5T2XmnvB|2`hSmFX1tXZ~x#Cn27P!*{3}Gf`@4D20YHH3_q$n+iI#eTfc#!+_aX0Fcw#g4$ zQHO$OX3s3vHvjqky}|GLp7VKvc+;S8k16!;;7EUdOZmyQ`?=F=`+R3ohrivb5{5&T ztj;ZB_sVpGGvwk(l!v44m7d4sk{oBWHI%sI+rfZNTtGM);`f!BT!PpD0LzGVaq;(b zals}gAECs?M*{%0I-}U$!+F2T@WR2dc)ajVMd5j@PD@llID_E>ffU+D==d_!m zY1Ici4A`5d@Ihbyudd1v*_O zty`NbpI_O0)bX8KX3WO}O@?} zTVnx+25F4m2)V$LTN7$7-o7_5foXaSuq&l^?wav_#6f*+w@E(4OG#GyyvenF{}KKE zTl|QR9qE224ojD;7Jk4gqLZ0VypP(*7K?hD2Ihmf1C*$!$OGmUTg(ZRty`>Y36@(T zZK4TVqAVzu1iP&i>quL>Qd9XBWy`G=1cHtERvQ~a={xx(C7U1%t8ErG+bjsB;R?8a zq_@v1tb*N!5M%^=wfyPM(rNXrkh+Tcs+E!yMF5bW^mKI!qQ07~-WjjwCL4RwG~;&j<1?SQ6qI%k_N(_}mJXALMGLX*yfrmHVZL-(M4I>cnYqE3+g& zo775v$EDg**S8Lysac#GKKzAP(uk>^pzvY_-k$4Lxx&%5gkcoR={68qS>i?0`}J;WG7JzpIVuVSu1XhM?1W)ZQ80d4&&9-*4oG;y4BG^fs#=bvayjuWkTm`!+B8~ za~^h;b}&z6oxCZO%c##n=wv1$aU`)Q+e`g@;s|Rq%h^KJ=R96Ag65YObPgGFR!0qg zZ0CO|WG=xIdBRWS0b(cIV6;N>?(6BVX48zuRmL|7O7(rf37}8;?V+7mSia6ff()6| zyS$zbA0$Xr{nPC;LvK2#$5`jV(b%`5Zjh*p<-CCAwkIKE!b}15VUsvij(0!qT$NBd zkLvQm!XBMcBQZn5t^2D{1~Lj!V)m?++s15DM|;pd^*WwUn>NgWAH(nOpSI=2oO>K+ z)REAZa2_ieqYK@+%-0M~h_VRuA4wT7SA*5ECW+;OMN>Z?pRT(s*5v1xy=&G>h!9$v z|CNE>%ZHC6h;r;byM69n?J=_C$Z@7<%Wd~X!dtu>>FDA6-5J`*ImI~p)g)_@v`4B( zMelt@L3P(ImUf?qyL{3JQ><;Ch=ZCD!-a3@1D%X|g2di#$krq4Bj;vF8!PO_i#GUR zL{@>}=$l@B=d9vK6@YyOHc0@DeN%$;kULcM)!rVMHAEKPy~me(D_AyBk%E~rqGJI% zbKh|=v|sa*@(M-MO<02apw1#CxCNvdc$WA+_$|ilE1Z=v2a9)~5iPKbxKkR&L2xA` z7Dr52)qTy09F(;97UhC1j~io zrw$uukka$8+(jpT+~uciKOvlADUya9s;CAgU~15K!1BUl{2oP9Nhhu1^xoj3vb4q|1*IqC5N%C{H)FkIo;J-%ScFq@m}wK*DU-8K)KX5a)JF7-A%Nx|N!{JZ zzG(JH9X0PJrp3@h=VwZ6Zv`MRjS-09ye8hWNt*|f1OFcR678&O`%;j*_GyNa*D z@`E*~YO(_NEUe(_>Uw0^Vm!1**{XkFAc2lUXk~_|!Zsf^A6kloo&G%aSUIfz^~Q<$ z%t`tYovzTmm`ldGa$0m|Np9Wv?CuNyO0g%1h#jwlnGa2Z#Sf%4&YjB9$Mo)>)s&$4 wDqOShu3arbhO`(il}WqE0Xxi6E2XgIwb1lz+^GTCl>&IW`MS0{gXFCL05=@z6951J From d777bce4a94337e36e0e195a7393f6f73dec3a13 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Wed, 17 Jul 2024 07:12:59 +0200 Subject: [PATCH 095/133] fix(measuretime): improve app icon once more for dark theme --- 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 7dc5b31d3..635e88152 100644 --- a/apps/measuretime/measuretime-icon.js +++ b/apps/measuretime/measuretime-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwwMB/EAgf/5//AoMB/+HAogNB/H/+P+A4IFC/kAAoYUBv4FC+GMn4FC8OOj/4gPx8ePAof3x+/CIX/x4jDAqsPAqAvdKYIFJLP5ZPVoa5FYorRHborpFeocAAYMDAomHAoQJB5//A=")) +require("heatshrink").decompress(atob("mEwgX/6AHCh////hAQIAB4ALCg4GBwF/BZH+gEfBYXBAYQLB/AbDBY3ggEBBZGAqFAGAQLE/0B6HgGAXB1WrBYP4g/QAQJHDgYLB/kf6H+BY//v/Q/+fEYwGBBYJHIBdJHBBcJTvR5anFBaKz/Wf6zhv4LBz4LG/kf6H+DgQjE/EH6ACBHY3+gPQ8EfI4+AqFAv4LH8EAgJfI/EAFwRHCAAIHB/0AFwQLGGAIuCOAILCh4GB8IKC")) From 27af11a9ca6cd3fdd408d69e817e11e99996fb23 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 8 Jul 2024 18:03:12 -0500 Subject: [PATCH 096/133] Refactor entire app so that it can have a settings.js --- apps/timestamplog/ChangeLog | 1 + apps/timestamplog/app.js | 319 +++++--------------------------- apps/timestamplog/lib.js | 245 ++++++++++++++++++++++++ apps/timestamplog/metadata.json | 3 +- apps/timestamplog/settings.js | 7 + 5 files changed, 304 insertions(+), 271 deletions(-) create mode 100644 apps/timestamplog/ChangeLog create mode 100644 apps/timestamplog/lib.js create mode 100644 apps/timestamplog/settings.js diff --git a/apps/timestamplog/ChangeLog b/apps/timestamplog/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/timestamplog/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 87f227f46..b4e5a0ee4 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -1,10 +1,9 @@ -Layout = require('Layout'); -locale = require('locale'); -storage = require('Storage'); +const Layout = require('Layout'); +const locale = require('locale'); +const storage = require('Storage'); + +const tsl = require('timestamplog'); -// 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; @@ -13,35 +12,6 @@ const DRAG_THRESHOLD = 30; const SCROLL_BAR_WIDTH = 12; -// Settings - -const SETTINGS = Object.assign({ - logFont: '12x20', - logFontHSize: 1, - 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'); - } -} - -function fontSpec(name, hsize, vsize) { - return name + ':' + hsize + 'x' + vsize; -} - - // Fetch a stringified image function getIcon(id) { if (id == 'add') { @@ -88,120 +58,13 @@ function getIcon(id) { } -//// 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, 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; - // 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) { - 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 { - console.log('stamplog: save to storage FAILED'); - this.emit('saveError'); - } - } 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() { - // 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() - }); - 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(); - } - - // Does the log currently contain the maximum possible number of entries? - isFull() { - return this.log.length >= this.maxLength; - } -} - - -//// 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(fontSpec(SETTINGS.logFont, - SETTINGS.logFontHSize, SETTINGS.logFontVSize)) + .setFont(tsl.fontSpec(tsl.SETTINGS.logFont, + tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize)) .setFontAlign(-1, -1) .setColor(g.theme.fg) .drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y) @@ -254,9 +117,7 @@ function renderScrollBar(elem, scroll) { // Main app screen interface, launched by calling start() class MainScreen { - constructor(stampLog) { - this.stampLog = stampLog; - + constructor() { // Values set up by start() this.itemsPerPage = null; this.scrollPos = null; @@ -320,7 +181,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: launchSettingsMenu}, + cb: () => launchSettingsMenu(returnFromSettings)}, ], }, ], @@ -330,8 +191,8 @@ class MainScreen { // Calculate how many log items per page we have space to display layout.update(); let availableHeight = layout.placeholder.h; - g.setFont(fontSpec(SETTINGS.logFont, - SETTINGS.logFontHSize, SETTINGS.logFontVSize)); + g.setFont(tsl.fontSpec(tsl.SETTINGS.logFont, + tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize)); let logItemHeight = g.getFontHeight() * 2; this.itemsPerPage = Math.max(1, Math.floor(availableHeight / logItemHeight)); @@ -357,7 +218,7 @@ class MainScreen { let logIdx = this.scrollPos - this.itemsPerPage; for (let elem of layLogItems.c) { logIdx++; - elem.item = this.stampLog.log[logIdx]; + elem.item = stampLog.log[logIdx]; elem.itemIdx = logIdx; } this.layout.render(layLogItems); @@ -367,7 +228,7 @@ class MainScreen { if (!item || item == 'buttons') { let addBtn = this.layout.addBtn; - if (!SETTINGS.rotateLog && this.stampLog.isFull()) { + if (!tsl.SETTINGS.rotateLog && 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); @@ -433,7 +294,7 @@ class MainScreen { 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)); + switchUI(new LogEntryScreen(stampLog, logUIObj.itemIdx)); break; } } @@ -443,14 +304,14 @@ class MainScreen { Bangle.on('touch', this.listeners.touch); function buttonHandler() { - let act = SETTINGS.buttonAction; + let act = tsl.SETTINGS.buttonAction; if (act == 'Log time') { if (currentUI != mainUI) { switchUI(mainUI); } mainUI.addTimestamp(); - } else if (act == 'Show menu') { - launchSettingsMenu(); + } else if (act == 'Open settings') { + launchSettingsMenu(returnFromSettings); } else if (act == 'Quit app') { Bangle.showClock(); } @@ -462,8 +323,8 @@ class MainScreen { // Add current timestamp to log if possible and update UI display addTimestamp() { - if (SETTINGS.rotateLog || !this.stampLog.isFull()) { - this.stampLog.addEntry(); + if (tsl.SETTINGS.rotateLog || !stampLog.isFull()) { + stampLog.addEntry(); this.scroll('b'); this.render('buttons'); } @@ -473,8 +334,8 @@ class MainScreen { scrollInfo() { return { pos: this.scrollPos, - min: (this.stampLog.log.length - 1) % this.itemsPerPage, - max: this.stampLog.log.length - 1, + min: (stampLog.log.length - 1) % this.itemsPerPage, + max: stampLog.log.length - 1, itemsPerPage: this.itemsPerPage }; } @@ -524,12 +385,12 @@ class MainScreen { class LogEntryScreen { constructor(stampLog, logIdx) { - this.stampLog = stampLog; + stampLog = stampLog; this.logIdx = logIdx; this.logItem = stampLog.log[logIdx]; - this.defaultFont = fontSpec( - SETTINGS.logFont, SETTINGS.logFontHSize, SETTINGS.logFontVSize); + this.defaultFont = tsl.fontSpec( + tsl.SETTINGS.logFont, tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize); } start() { @@ -577,7 +438,7 @@ class LogEntryScreen { } refresh() { - this.logItem = this.stampLog.log[this.logIdx]; + this.logItem = stampLog.log[this.logIdx]; this.layout.date.label = locale.date(this.logItem.stamp, 1); this.layout.time.label = locale.time(this.logItem.stamp).trim(); this.layout.update(); @@ -585,22 +446,22 @@ class LogEntryScreen { } prevLogItem() { - this.logIdx = this.logIdx ? this.logIdx-1 : this.stampLog.log.length-1; + this.logIdx = this.logIdx ? this.logIdx-1 : stampLog.log.length-1; this.refresh(); } nextLogItem() { - this.logIdx = this.logIdx == this.stampLog.log.length-1 ? 0 : this.logIdx+1; + this.logIdx = this.logIdx == stampLog.log.length-1 ? 0 : this.logIdx+1; this.refresh(); } delLogItem() { - this.stampLog.deleteEntries([this.logItem]); - if (!this.stampLog.log.length) { + stampLog.deleteEntries([this.logItem]); + if (!stampLog.log.length) { this.back(); return; - } else if (this.logIdx > this.stampLog.log.length - 1) { - this.logIdx = this.stampLog.log.length - 1; + } else if (this.logIdx > stampLog.log.length - 1) { + this.logIdx = stampLog.log.length - 1; } // Create a brief “blink” on the screen to provide user feedback @@ -612,104 +473,10 @@ class LogEntryScreen { } -function launchSettingsMenu() { - const fonts = g.getFonts(); - - function topMenu() { - E.showMenu({ - '': { - title: 'Stamplog', - back: endMenu, - }, - '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]; - }, - }, - }); - } - - 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; - } - }, - 'Clear log': clearLogPrompt, - }); - } - - 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(); - } - - 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(); - } - }); - } - +function switchUI(newUI) { currentUI.stop(); - topMenu(); + currentUI = newUI; + currentUI.start(); } @@ -725,17 +492,29 @@ function saveErrorAlert() { } -function switchUI(newUI) { +function launchSettingsMenu(backCb) { currentUI.stop(); - currentUI = newUI; + stampLog.save(); + tsl.launchSettingsMenu(backCb); +} + +function returnFromSettings() { + // Reload stampLog to pick up any changes made from settings UI + stampLog = loadStampLog(); currentUI.start(); } +function loadStampLog() { + // Create a StampLog object with its data loaded from storage + return new tsl.StampLog(tsl.LOG_FILENAME, tsl.SETTINGS.maxLogLength); +} + + Bangle.loadWidgets(); Bangle.drawWidgets(); -stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength); +var stampLog = loadStampLog(); E.on('kill', stampLog.save.bind(stampLog)); stampLog.on('saveError', saveErrorAlert); diff --git a/apps/timestamplog/lib.js b/apps/timestamplog/lib.js new file mode 100644 index 000000000..13cb291ef --- /dev/null +++ b/apps/timestamplog/lib.js @@ -0,0 +1,245 @@ +const storage = require('Storage'); + +// Storage filenames + +const LOG_FILENAME = 'timestamplog.json'; +const SETTINGS_FILENAME = 'timestamplog.settings.json'; + + +// Settings + +const SETTINGS = Object.assign({ + logFont: '12x20', + logFontHSize: 1, + logFontVSize: 1, + maxLogLength: 30, + rotateLog: false, + buttonAction: 'Log time', +}, storage.readJSON(SETTINGS_FILENAME, true) || {}); + +const SETTINGS_BUTTON_ACTION = [ + 'Log time', + 'Open settings', + 'Quit app', + 'Do nothing', +]; + + +function fontSpec(name, hsize, vsize) { + return name + ':' + hsize + 'x' + vsize; +} + + +//// 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, 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; + // 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(); + } + + // Read in 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); + } + this.log = 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) { + 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 { + console.log('stamplog: save to storage FAILED'); + this.emit('saveError'); + } + } 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() { + // 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() + }); + 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(); + } + + // Does the log currently contain the maximum possible number of entries? + isFull() { + return this.log.length >= this.maxLength; + } +} + +function launchSettingsMenu(backCb) { + const fonts = g.getFonts(); + const stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength); + + function saveSettings() { + stampLog.save(); + if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { + E.showAlert('Trouble saving settings'); + } + } + + function endMenu() { + saveSettings(); + backCb(); + } + + function topMenu() { + E.showMenu({ + '': { + title: 'Stamplog', + back: endMenu, + }, + '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]; + }, + }, + }); + } + + 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; + } + }, + 'Clear log': doClearLog, + }); + } + + 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 doClearLog() { + E.showPrompt('Erase ALL log entries?', { + title: 'Clear log', + buttons: {'Erase':1, "Don't":0} + }).then((yes) => { + if (yes) { + stampLog.deleteEntries(stampLog.log); + } + logMenu(); + }); + } + + topMenu(); +} + +exports = {LOG_FILENAME, SETTINGS_FILENAME, SETTINGS, SETTINGS_BUTTON_ACTION, fontSpec, StampLog, + launchSettingsMenu}; diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index 858fab237..d5df5c54b 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -10,7 +10,8 @@ "interface": "interface.html", "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}, + {"name": "lib.js", "url": "lib.js"} ], "data": [ {"name": "timestamplog.settings"}, diff --git a/apps/timestamplog/settings.js b/apps/timestamplog/settings.js new file mode 100644 index 000000000..137ed31db --- /dev/null +++ b/apps/timestamplog/settings.js @@ -0,0 +1,7 @@ +const tsl = require('timestamplog'); + +( + function(backCb) { + tsl.launchSettingsMenu(backCb); + } +); From 7cfdf1b58ebf3ed46f41f7d5ce4bda71ad281467 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 9 Jul 2024 13:23:34 -0500 Subject: [PATCH 097/133] Add log position indicator to log entry screen --- apps/timestamplog/app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index b4e5a0ee4..35845c5e0 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -412,6 +412,7 @@ class LogEntryScreen { let layout = new Layout( {type: 'v', c: [ + {type: 'txt', font: this.defaultFont, id: 'entryno', label: 'Entry ?/?'}, {type: 'txt', font: this.defaultFont, id: 'date', label: '?'}, {type: 'txt', font: this.defaultFont, id: 'time', label: '?'}, {type: '', id: 'placeholder', fillx: 1, filly: 1}, @@ -439,6 +440,7 @@ class LogEntryScreen { refresh() { this.logItem = stampLog.log[this.logIdx]; + this.layout.entryno.label = 'Entry ' + (this.logIdx+1) + '/' + stampLog.log.length; this.layout.date.label = locale.date(this.logItem.stamp, 1); this.layout.time.label = locale.time(this.logItem.stamp).trim(); this.layout.update(); From 58e710d2da5c50c2c83ecf0f7417e69a2833767c Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 9 Jul 2024 16:58:53 -0500 Subject: [PATCH 098/133] Fix the metadata file again --- apps/timestamplog/metadata.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index d5df5c54b..e9b23e0fd 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -11,7 +11,8 @@ "storage": [ {"name": "timestamplog.app.js", "url": "app.js"}, {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true}, - {"name": "lib.js", "url": "lib.js"} + {"name": "timestamplog", "url": "lib.js"}, + {"name": "timestamplog.settings.js", "url": "settings.js"} ], "data": [ {"name": "timestamplog.settings"}, From 2ee7ba26ed261df049d1d1385ff25fbbe8fe9c79 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 9 Jul 2024 17:09:59 -0500 Subject: [PATCH 099/133] Cleanup --- apps/timestamplog/app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 35845c5e0..c44ab719b 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -1,6 +1,5 @@ const Layout = require('Layout'); const locale = require('locale'); -const storage = require('Storage'); const tsl = require('timestamplog'); @@ -198,7 +197,7 @@ class MainScreen { Math.floor(availableHeight / logItemHeight)); // Populate log items in layout - for (i = 0; i < this.itemsPerPage; i++) { + for (let i = 0; i < this.itemsPerPage; i++) { layout.logItems.c.push( {type: 'custom', render: renderLogItem, item: undefined, itemIdx: undefined, fillx: 1, height: logItemHeight} @@ -385,7 +384,6 @@ class MainScreen { class LogEntryScreen { constructor(stampLog, logIdx) { - stampLog = stampLog; this.logIdx = logIdx; this.logItem = stampLog.log[logIdx]; From 60662811803843b4d86109e857a99a7d53403bab Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 11 Jul 2024 19:30:51 -0500 Subject: [PATCH 100/133] Add screenshot and README --- apps/timestamplog/README.md | 55 +++++++++++++++++++++++++++++++ apps/timestamplog/metadata.json | 2 ++ apps/timestamplog/screenshot.png | Bin 0 -> 18773 bytes 3 files changed, 57 insertions(+) create mode 100644 apps/timestamplog/README.md create mode 100644 apps/timestamplog/screenshot.png diff --git a/apps/timestamplog/README.md b/apps/timestamplog/README.md new file mode 100644 index 000000000..96565e2d7 --- /dev/null +++ b/apps/timestamplog/README.md @@ -0,0 +1,55 @@ +# Timestamp Log + +Timestamp Log provides a convenient way to record points in time for later reference. Each time a button is tapped a date/time-stamped marker is logged. By default up to 30 entries can be stored at once; this can be increased up to 100 in the settings menu. + +![Timestamp Log screenshot](screenshot.png) + +## Usage and controls + +When the app starts you will see the log display. Initially the log is empty. The large button on the bottom left displays the current time and will add a date- and time-stamp when tapped. Each tap of the button adds a new entry to the bottom of the log. The small button on the bottom right opens the app settings menu. + +If the log contains more entries than can be displayed at once, swiping up and down will move through the entries one screenfull at a time. + +To delete an individual entry, display it on the screen and then tap on it. The entry's position in the list will be shown along with a Delete button. Tap this button to remove the entry. The Up and Down arrows on the right side of the screen can be used to move between log entries. Further deletions can be made. Finally, click the Back button in the upper-left to finish and return to the main log screen. + +## Settings + +The settings menu provides the following settings: + +### Log + +**Max entries:** Select the maximum number of entries that the log can hold. + +**Auto-delete oldest:** If turned on, adding a log entry when the log is full will cause the oldest entry to automatically be deleted to make room. Otherwise, it is not possible to add another log entry until some entries are manually deleted or the “Max entries” setting is increased. + +**Clear log:** Remove all log entries, leaving the log empty. + +### Appearance + +**Log font:** Select the font used to display log entries. + +**Log font H size** and **Log font V size**: Select the horizontal and vertical sizes, respectively, of the font. Reasonable values for bitmapped fonts are 1 or 2 for either setting. For Vector, values around 15 to 25 work best. Setting both sizes the same will display the font with normal proportions; varying the values will change the relative height or width of the font. + +### Button + +You can choose the action that the physical button (Bangle.js v2) performs when the screen is unlocked. + +**Log time:** Add a date/time stamp to the log. Same as tapping the large button on the touch screen. + +**Open settings:** Open this app settings menu. + +**Quit app:** Return to the main clock app. + +**Do nothing:** Perform no action. + +## Web interface + +Currently the web interface displays the list of dates and times, which can be copied and pasted as desired. The log cannot currently be edited with this interface, only displayed. + +## Support + +Issues and questions may be posted at: https://github.com/espruino/BangleApps/issues + +## Creator + +Travis Evans diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index e9b23e0fd..e1aa0eb23 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -5,6 +5,8 @@ "icon": "app.png", "version": "0.01", "description": "Conveniently record a series of date/time stamps", + "screenshots": [ {"url": "screenshot.png" } ], + "readme": "README.md", "tags": "timestamp, log", "supports": ["BANGLEJS2"], "interface": "interface.html", diff --git a/apps/timestamplog/screenshot.png b/apps/timestamplog/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..27f36ac3292b08ce060d95748df2ca52a930ac56 GIT binary patch literal 18773 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 QjsO4v07*qoM6N<$f- Date: Mon, 15 Jul 2024 16:19:30 -0500 Subject: [PATCH 101/133] Add source for app icon --- apps/timestamplog/app.xcf | Bin 0 -> 3379 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/timestamplog/app.xcf diff --git a/apps/timestamplog/app.xcf b/apps/timestamplog/app.xcf new file mode 100644 index 0000000000000000000000000000000000000000..27406b1ed881c36aafbb85586e0e33907efb704f GIT binary patch literal 3379 zcmeHI&2Jk;6n`_j-d%5;IC1MnAvj*gcH*>g6hlgz6OnR29FTJ4nmUea;nQeb#7BU%6nHeLe-`*3B#xDIkbInWJ_*)I?Ob@16XQr4UO zlap3&1Ub%-+aI(JN3Emivd2T?6%^+EgIVYI!qvvYUn&9`>%+}^pf+j!&l?%n^aYiuCsy(@w?qWEF+ zgW>66v(sxw4G0fyLF4*|z0N4=^qTiuL-y|<9}iojXq(+FoWQ?3^E|B~zDk7DrwLDg zVHpiH`<8;jKWO!uoi3X^5bp+WT8Z)7kMYKifmqKwlhzoFRxSLxg+mKR7M`(i#=^4} z&H{5jGkgHf&GD-mgHeSselxDMY+>_9aU5&NFD=1LpMP5n`F+CUx3RJQsnI_Y5h<}n zmoj;6(zzrlZqb=wev_Wb7;KP`iK<3YqjhpLS|eAZDy1~4kf%|Ze2rEq(5Qs021lIT z2t~RO6KMtISYm<8`oCo4ndCc%Bvy2KPd*b4p0UD}Pvg3zC~6le|BPMOtw_IVE7#5s z7Clk1IeDP^eafo@jWH*@ge`euf*?#8XU~&~i>SbU7Tcv*gY5z~7srh+Hps(fx?&UB zJ&Jc-ZIMuQl4_G2bql}TO-d>FsT%oeodUH+X;r0ARcJ<)DWg_tR+VT@MU+)VnpZ2d zpbC`x7g~sYR^ZG2MSREKMu@?2N{GjCE6BmQP-mPUb)qJr_6TDM#2w2G?HwGRwgv#C z|L+&7uNP5Q0rb3mJ;xK94DYiE9TlgQ*2Ks9mTG#igX2 zcr5;a@04xqDW;}3j_~S>@fw*mdCAslNn}-k6T`;|&cANxGo~E#i87 zB;V&dvZg6MA@;ld<_FvaFVc-~ZvE#y8O&+N^TG%}Pk8(`v)4Z1H&@vBo1ct6i`!rs zH$e)NM|;hJa<~dBH4=_T=X`K1ku)$3?F__#EkdNpaePoZE>Jotn7Gh6aGVv+9I(LT0!rZ%7dVkI z5hv7g6}l?E=L__bz~Y=^cE%N0)lMol)${CmRy<>#rHATy*1U?-E5fTZy>j-dW~>#@ z!q@CX4tn05Jc{PsTE@<03Hzs(af{~B6FKxlmX=h8mQ_dv^h^;wQ$){{&@W~5OBwxA pMZZi4eIxeJJrfOErS^95rKh6=h!`Sr1{t!*ltacmGNTU2e*u~9U3dTh literal 0 HcmV?d00001 From f7badead2c42110c8ef818d60ff056c699bb19b8 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 16 Jul 2024 17:20:04 -0500 Subject: [PATCH 102/133] Don't crash if timestamplog.json doesn't exist yet --- apps/timestamplog/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timestamplog/interface.html b/apps/timestamplog/interface.html index 6febe7849..b54dfb010 100644 --- a/apps/timestamplog/interface.html +++ b/apps/timestamplog/interface.html @@ -14,7 +14,7 @@ function HTMLStampLog(stampLog) { html = '' - if (stampLog.length) { + if (stampLog !== undefined && stampLog.length) { html += '
    '; for (const logItem of stampLog) { html += ('
  1. ' + new Date(logItem.stamp).toLocaleString() + '
  2. '); From 8f22d8aa35ccc05e4d29ad7419a866b2faeac94c Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Wed, 17 Jul 2024 15:07:49 -0500 Subject: [PATCH 103/133] Ensure settings are saved upon Bangle2 button long-press --- apps/timestamplog/lib.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/timestamplog/lib.js b/apps/timestamplog/lib.js index 13cb291ef..59590bfb7 100644 --- a/apps/timestamplog/lib.js +++ b/apps/timestamplog/lib.js @@ -141,14 +141,17 @@ function launchSettingsMenu(backCb) { const stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength); function saveSettings() { + console.log('Saving timestamp log and settings'); stampLog.save(); if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { E.showAlert('Trouble saving settings'); } } + E.on('kill', saveSettings); function endMenu() { saveSettings(); + E.removeListener('kill', saveSettings); backCb(); } From 7ae71e95066d108b8acc829423ac3da9945d25da Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 09:58:01 +0200 Subject: [PATCH 104/133] iconbits: introduce color support --- apps/iconbits/iconbits.app.js | 52 +++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index 625070dd5..d046bf1df 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -10,6 +10,7 @@ let kule = [0, 0, 0]; // R, G, B var font_height = 22, font_width = 8; var zoom_x = 64, zoom_y = 24, zoom_f = 6; + var color = true; let oldLock = false; let sg = null; @@ -17,7 +18,7 @@ sg.setColor(1,1,1).fillRect(0,0, font_width, font_height); } - function setup(m) { + function __setup(m) { mode = m; switch (m) { case 'font': @@ -37,10 +38,23 @@ zoom_f = 2; break; } + } + function setup(m) { + __setup(m); sg = Graphics.createArrayBuffer(font_width, font_height, 8, {}); clear(); } + function icon_big() { + zoom_x = 16; + zoom_y = 25; + zoom_f = 3; + } + + function icon_small() { + __setup("icon"); + } + function updateLock() { if (oldLock) { return; @@ -66,7 +80,11 @@ Bangle.on("lock", function() { kule[2] = Math.random(); } function selectColor (x) { - let c; + if (color) { + i = Math.floor((x - 25) / 4); + kule = toColor(i); + return; + } if (x < g.getWidth()/2) { c = 0; } else { @@ -117,6 +135,7 @@ Bangle.on("lock", function() { g.clear(); if (mode == "draw") return; + const w = g.getWidth; g.setColor(0, 0, 0.5); g.fillRect(0, 0, g.getWidth(), g.getHeight()); g.setColor(1, 1, 1); @@ -129,6 +148,16 @@ Bangle.on("lock", function() { update(); } + function toColor(i) { + let r = [0, 0, 0]; + r[0] = (i % 3) / 2; + i = Math.floor(i / 3); + r[1] = (i % 3) / 2; + i = Math.floor(i / 3); + r[2] = (i % 3) / 2; + return r; + } + function drawUtil () { if (Bangle.isLocked()) { updateLock(); @@ -136,6 +165,11 @@ Bangle.on("lock", function() { // titlebar g.setColor(kule[0], kule[1], kule[2]); g.fillRect(0, 0, g.getWidth(), 20); + for (let i = 0; i < 3*3*3; i++) { + r = toColor(i); + g.setColor(r[0], r[1], r[2]); + g.fillRect(25+4*i, 20, 25+4*i+3, 24); + } // clear button g.setColor('#000'); // black g.fillCircle(10, 10, 8, 8); @@ -173,7 +207,7 @@ Bangle.on("lock", function() { var XS = (to.x - from.x) / 32; var YS = (to.y - from.y) / 32; for (let i = 0; i < 32; i++) { - g.fillCircle(from.x + (i * XS), from.y + (i * YS), 4, 4); + g.fillCircle(from.x + (i * XS), from.y + (i * YS), 2, 2); } break; case 'square': @@ -182,7 +216,7 @@ Bangle.on("lock", function() { for (let i = 0; i < 32; i++) { const posX = from.x + (i * XS); const posY = from.y + (i * YS); - g.fillRect(posX - 10, posY - 10, posX + 10, posY + 10); + g.fillRect(posX - 4, posY - 4, posX + 4, posY + 4); } break; } @@ -190,7 +224,7 @@ Bangle.on("lock", function() { } function update() { - g.drawImage(sg, 0, 64, {}); + g.drawImage(sg, 4, 64, {}); g.drawImage(sg, zoom_x, zoom_y, { scale: zoom_f }); } @@ -226,8 +260,9 @@ Bangle.on("lock", function() { oldY = -1; }, 100); + let top_bar = 20; // tap and hold the clear button - if (tap.x < 32 && tap.y < 32) { + if (tap.x < 32 && tap.y < top_bar) { if (tap.b === 1) { if (tapTimer === null) { tapTimer = setTimeout(function () { @@ -244,7 +279,7 @@ Bangle.on("lock", function() { } return; } - if (tap.x > g.getWidth() - 32 && tap.y < 32) { + if (tap.x > g.getWidth() - 32 && tap.y < top_bar) { if (tap.b === 1) { if (tapTimer === null) { tapTimer = setTimeout(function () { @@ -264,7 +299,7 @@ Bangle.on("lock", function() { } drawUtil(); return; - } else if (tap.y < 32) { + } else if (tap.y < top_bar) { if (mode == "draw") nextColor(); else @@ -308,6 +343,7 @@ Bangle.on("lock", function() { //print("wh", im, typeof im, im[0], typeof im[0]); //print("Image:", im.length, s); print('fi("'+btoa(im)+'");'); + print(btoa(require('heatshrink').compress(im))); } From aedb898177876ff8bca37c5674c741c7b63f1dbd Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 10:04:32 +0200 Subject: [PATCH 105/133] iconbits: cleanups. --- apps/iconbits/iconbits.app.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index d046bf1df..d40cc0285 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -194,6 +194,9 @@ Bangle.on("lock", function() { } function __draw (g, from, to) { + let XS = (to.x - from.x) / 32; + let YS = (to.y - from.y) / 32; + switch (pen) { case 'pixel': g.drawLine(from.x, from.y, to.x, to.y); @@ -204,15 +207,11 @@ Bangle.on("lock", function() { g.drawLine(from.x + 2, from.y + 2, to.x, to.y + 2); break; case 'circle': - var XS = (to.x - from.x) / 32; - var YS = (to.y - from.y) / 32; for (let i = 0; i < 32; i++) { g.fillCircle(from.x + (i * XS), from.y + (i * YS), 2, 2); } break; case 'square': - var XS = (to.x - from.x) / 32; - var YS = (to.y - from.y) / 32; for (let i = 0; i < 32; i++) { const posX = from.x + (i * XS); const posY = from.y + (i * YS); @@ -220,7 +219,6 @@ Bangle.on("lock", function() { } break; } - } function update() { From 9697902e40bee4f32dc25a75fb387a4c1e99fb0e Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 10:12:57 +0200 Subject: [PATCH 106/133] iconbits: cleanups, finish color selector --- apps/iconbits/iconbits.app.js | 37 ++++++++++++++++------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index d40cc0285..b5f26d4df 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -13,6 +13,7 @@ var color = true; let oldLock = false; let sg = null; + const top_bar = 20; function clear(m) { sg.setColor(1,1,1).fillRect(0,0, font_width, font_height); @@ -61,9 +62,9 @@ } g.setColor('#fff'); g.fillRect(0, 0, g.getWidth(), 20); - g.setFont('6x8', 2); + g.setFont('Vector', 22); g.setColor('#000'); - g.drawString('PLEASE UNLOCK', 10, 2); + g.drawString('PLEASE\nUNLOCK', 10, 2); oldLock = true; } Bangle.on("lock", function() { @@ -74,14 +75,14 @@ Bangle.on("lock", function() { drawUtil(); } }); - function nextColor () { + function nextColor() { kule[0] = Math.random(); kule[1] = Math.random(); kule[2] = Math.random(); } - function selectColor (x) { + function selectColor(x) { if (color) { - i = Math.floor((x - 25) / 4); + i = Math.floor((x - 32) / 4); kule = toColor(i); return; } @@ -131,7 +132,7 @@ Bangle.on("lock", function() { } } - function drawArea () { + function drawArea() { g.clear(); if (mode == "draw") return; @@ -158,17 +159,17 @@ Bangle.on("lock", function() { return r; } - function drawUtil () { + function drawUtil() { if (Bangle.isLocked()) { updateLock(); } // titlebar g.setColor(kule[0], kule[1], kule[2]); - g.fillRect(0, 0, g.getWidth(), 20); + g.fillRect(0, 0, g.getWidth(), top_bar); for (let i = 0; i < 3*3*3; i++) { r = toColor(i); g.setColor(r[0], r[1], r[2]); - g.fillRect(25+4*i, 20, 25+4*i+3, 24); + g.fillRect(32+4*i, 12, 32+4*i+3, top_bar); } // clear button g.setColor('#000'); // black @@ -183,7 +184,7 @@ Bangle.on("lock", function() { drawBrushIcon(); } - function transform (p) { + function transform(p) { if (p.x < zoom_x || p.y < zoom_y) return p; p.x = ((p.x - zoom_x) / zoom_f); @@ -193,7 +194,7 @@ Bangle.on("lock", function() { return p; } - function __draw (g, from, to) { + function __draw(g, from, to) { let XS = (to.x - from.x) / 32; let YS = (to.y - from.y) / 32; @@ -258,7 +259,6 @@ Bangle.on("lock", function() { oldY = -1; }, 100); - let top_bar = 20; // tap and hold the clear button if (tap.x < 32 && tap.y < top_bar) { if (tap.b === 1) { @@ -343,19 +343,16 @@ Bangle.on("lock", function() { print('fi("'+btoa(im)+'");'); print(btoa(require('heatshrink').compress(im))); } - - } - setup("icon"); - drawArea(); - Bangle.setUI({ +setup("icon"); +drawArea(); +Bangle.setUI({ "mode": "custom", "drag": on_drag, "btn": on_btn, - }); - drawUtil(); - +}); +drawUtil(); function ft(icon) { g.reset().clear(); From 5cc09c832d21147f4b203868a7189db88fa60675 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 10:31:08 +0200 Subject: [PATCH 107/133] iconbits: cleanup preview functions --- apps/iconbits/iconbits.app.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index b5f26d4df..bb99e7c34 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -330,18 +330,15 @@ Bangle.on("lock", function() { s += f(v); } print("Manual bitmap\n"); - print('ft("' + s + '");'); + print('show_font("' + s + '");'); if (1) { s = ""; var im = sg.asImage("string"); for (var v of im) { - //print("val", v, typeof v); s += f(v); } - //print("wh", im, typeof im, im[0], typeof im[0]); - //print("Image:", im.length, s); - print('fi("'+btoa(im)+'");'); - print(btoa(require('heatshrink').compress(im))); + //print('show_unc_icon("'+btoa(im)+'");'); + print('show_icon("'+btoa(require('heatshrink').compress(im))+'");'); } } @@ -354,14 +351,22 @@ Bangle.setUI({ }); drawUtil(); -function ft(icon) { +function show_font(icon) { g.reset().clear(); g.setFont("Vector", 26).drawString("Hellord" + icon, 0, 0); } -function fi(icon) { +function show_bin_icon(icon) { g.reset().clear(); - g.drawImage(atob(icon), 40, 40); + g.drawImage(icon, 40, 40); +} + +function show_unc_icon(icon) { + show_bin_icon(atob(icon)); +} + +function show_icon(icon) { + unc = require("heatshrink").decompress(atob(icon)); + show_bin_icon(unc); } -//ft(icon_10 + "23.1" + icon_hpa); From 24ebb8a583e70e0125e1e35a05fac3b9e612d006 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 11:13:32 +0200 Subject: [PATCH 108/133] iconbits: update png icon --- apps/iconbits/app.png | Bin 2459 -> 2010 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/iconbits/app.png b/apps/iconbits/app.png index 7a3ee2e90600531bd4df39ad517637ecdc854745..d4e822ac7d47958ae769d3f09024801c10b8dc62 100644 GIT binary patch literal 2010 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 MB_Pzll? Date: Fri, 19 Jul 2024 11:14:18 +0200 Subject: [PATCH 109/133] iconbits: add loading/preview functions --- apps/iconbits/iconbits.app.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index bb99e7c34..2be08e3d4 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -223,7 +223,8 @@ Bangle.on("lock", function() { } function update() { - g.drawImage(sg, 4, 64, {}); + if (zoom_f < 3) + g.drawImage(sg, 4, 64, {}); g.drawImage(sg, zoom_x, zoom_y, { scale: zoom_f }); } @@ -370,3 +371,20 @@ function show_icon(icon) { show_bin_icon(unc); } +function load_bin_icon(i) { + sg.reset().clear(); + sg.drawImage(i, 0, 0); + drawArea(); +} + +function load_icon(icon) { + unc = require("heatshrink").decompress(atob(icon)); + load_bin_icon(unc); +} + +function for_screen() { + g.reset().clear(); + icon_big(); + update(); +} + From b51a85657628dfb19aaa6b59d7cd701760b78dc9 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 11:48:02 +0200 Subject: [PATCH 110/133] iconbits: cleanups, implement line drawing. --- apps/iconbits/iconbits.app.js | 53 ++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index 2be08e3d4..1777a8af3 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -98,8 +98,8 @@ Bangle.on("lock", function() { function nextPen () { switch (pen) { case 'circle': pen = 'pixel'; break; - case 'pixel': pen = 'crayon'; break; - case 'crayon': pen = 'square'; break; + case 'pixel': pen = 'line'; break; + case 'line': pen = 'square'; break; case 'square': pen = 'circle'; break; default: pen = 'pixel'; break; } @@ -108,8 +108,8 @@ Bangle.on("lock", function() { discard = setTimeout(function () { oldX = -1; oldY = -1; console.log('timeout'); discard = null; }, 500); } - var oldX = -1; - var oldY = -1; + var oldX = -1, oldY = -1; + var line_from = null; function drawBrushIcon () { const w = g.getWidth(); @@ -129,6 +129,9 @@ Bangle.on("lock", function() { g.drawLine(w - 14, 6, w - 10, 12); g.drawLine(w - 6, 6, w - 10, 12); break; + case 'line': + g.drawLine(w - 5, 5, w - 15, 15); + break; } } @@ -199,6 +202,7 @@ Bangle.on("lock", function() { let YS = (to.y - from.y) / 32; switch (pen) { + case 'line': case 'pixel': g.drawLine(from.x, from.y, to.x, to.y); break; @@ -219,6 +223,8 @@ Bangle.on("lock", function() { g.fillRect(posX - 4, posY - 4, posX + 4, posY + 4); } break; + default: + print("Unkown pen ", pen); } } @@ -229,9 +235,11 @@ Bangle.on("lock", function() { } function do_draw(from, to) { + print("do-draw", from, to); from = transform(from); to = transform(to); if (from && to) { + print("__draw", from, to); __draw(sg, from, to); } update(); @@ -306,20 +314,31 @@ Bangle.on("lock", function() { drawUtil(); return; } - oldX = to.x; - oldY = to.y; sg.setColor(kule[0], kule[1], kule[2]); g.setColor(kule[0], kule[1], kule[2]); + oldX = to.x; + oldY = to.y; - do_draw(from, to); + if (pen != "line") { + do_draw(from, to); + } else { + if (tap.b == 1) { + print(line_from); + if (!line_from) { + line_from = to; + } else { + print("draw -- ", line_from, to); + do_draw(line_from, to); + line_from = null; + } + } + } drawUtil(); } function on_btn(n) { function f(i) { return "\\x" + i.toString(16).padStart(2, '0'); } - print("on_btn", n); - print(g.getPixel(0, 0)); let s = f(0) + f(font_width) + f(font_height) + f(1); // 0..black, 65535..white for (let y = 0; y < font_height; y++) { @@ -330,17 +349,11 @@ Bangle.on("lock", function() { } s += f(v); } - print("Manual bitmap\n"); - print('show_font("' + s + '");'); - if (1) { - s = ""; - var im = sg.asImage("string"); - for (var v of im) { - s += f(v); - } - //print('show_unc_icon("'+btoa(im)+'");'); - print('show_icon("'+btoa(require('heatshrink').compress(im))+'");'); - } + if (mode == "font") + print('show_font("' + s + '");'); + var im = sg.asImage("string"); + //print('show_unc_icon("'+btoa(im)+'");'); + print('show_icon("'+btoa(require('heatshrink').compress(im))+'");'); } setup("icon"); From 62cb337105f4ec196d6c1b6b7f8678de3bdc0a19 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 13:29:25 +0200 Subject: [PATCH 111/133] iconbits: cleanups, add documentation. --- apps/iconbits/README.md | 17 +++++++++++++++-- apps/iconbits/iconbits.app.js | 8 +++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/apps/iconbits/README.md b/apps/iconbits/README.md index c5c524ab0..90bc0f5a3 100644 --- a/apps/iconbits/README.md +++ b/apps/iconbits/README.md @@ -3,5 +3,18 @@ Bitmap editor suitable for creating icons and fonts for BangleJS2. You'll want to run a copy of this in simulator, and another one on -watch to view the results. Draw using the provided tools, then press -the button, and you'll get result on the console. +watch to view the results. + +Draw using the provided tools, then press the button, and you'll get +result on the console; you can also use "dump();" on command +line. show_icon() takes same parameter as is used in app-icon.js +files, you can just copy&paste it to get an icon. By using +"for_screen();" command, then taking a screenshot, you can easily +generate app.png file. + +It is also possible to load existing icon into editor, using +"load_icon("");" command. At the end of iconbits.app.js file there are +more utility functions. + + + diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index 1777a8af3..83eb8ee89 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -235,11 +235,9 @@ Bangle.on("lock", function() { } function do_draw(from, to) { - print("do-draw", from, to); from = transform(from); to = transform(to); if (from && to) { - print("__draw", from, to); __draw(sg, from, to); } update(); @@ -327,7 +325,6 @@ Bangle.on("lock", function() { if (!line_from) { line_from = to; } else { - print("draw -- ", line_from, to); do_draw(line_from, to); line_from = null; } @@ -335,7 +332,8 @@ Bangle.on("lock", function() { } drawUtil(); } - function on_btn(n) { + +function dump(n) { function f(i) { return "\\x" + i.toString(16).padStart(2, '0'); } @@ -361,7 +359,7 @@ drawArea(); Bangle.setUI({ "mode": "custom", "drag": on_drag, - "btn": on_btn, + "btn": dump, }); drawUtil(); From 4561572aad3de1aee941e6da990e4acacd9e9b48 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 21:50:37 +0200 Subject: [PATCH 112/133] iconbits: mark this as version 0.02. --- apps/iconbits/ChangeLog | 1 + apps/iconbits/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/iconbits/ChangeLog b/apps/iconbits/ChangeLog index 263d4078d..10f5d311c 100644 --- a/apps/iconbits/ChangeLog +++ b/apps/iconbits/ChangeLog @@ -1 +1,2 @@ 0.01: attempt to import +0.02: implement colors and lines diff --git a/apps/iconbits/metadata.json b/apps/iconbits/metadata.json index b98a43953..edb4b4d6a 100644 --- a/apps/iconbits/metadata.json +++ b/apps/iconbits/metadata.json @@ -1,6 +1,6 @@ { "id": "iconbits", "name": "Icon bits", - "version": "0.01", + "version": "0.02", "description": "Bitmap editor suitable for creating icons", "icon": "app.png", "readme": "README.md", From 9ae99c5cf2529cec89642a4a8e49945fa5c29972 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Sat, 11 Nov 2023 12:28:57 +0100 Subject: [PATCH 113/133] [] settings: cleanups, go to full brightness. --- apps/setting/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index c5149ab5a..94c8e26a7 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -651,11 +651,11 @@ function showUtilMenu() { E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.'); Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); + Bangle.setLCDBrightness(1); if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat"); if (Bangle.setHRMPower) Bangle.setHRMPower(1,"flat"); if (Bangle.setCompassPower) Bangle.setCompassPower(1,"flat"); if (Bangle.setBarometerPower) Bangle.setBarometerPower(1,"flat"); - if (Bangle.setHRMPower) Bangle.setGPSPower(1,"flat"); setInterval(function() { var i=1000;while (i--); }, 1); From 1c6928ae60b9d5f17fb2499205f272cef75e8309 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 22:12:44 +0200 Subject: [PATCH 114/133] settings: fix whitespace --- apps/setting/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 94c8e26a7..d9d77d052 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -651,7 +651,7 @@ function showUtilMenu() { E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.'); Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); - Bangle.setLCDBrightness(1); + Bangle.setLCDBrightness(1); if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat"); if (Bangle.setHRMPower) Bangle.setHRMPower(1,"flat"); if (Bangle.setCompassPower) Bangle.setCompassPower(1,"flat"); From d85ec84ae32d35405eaaf27e9dc781e9660c14b1 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 19 Jul 2024 22:01:24 +0200 Subject: [PATCH 115/133] iconbits: fix warnings. --- apps/iconbits/iconbits.app.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/iconbits/iconbits.app.js b/apps/iconbits/iconbits.app.js index 83eb8ee89..5fc10423c 100644 --- a/apps/iconbits/iconbits.app.js +++ b/apps/iconbits/iconbits.app.js @@ -82,14 +82,13 @@ Bangle.on("lock", function() { } function selectColor(x) { if (color) { - i = Math.floor((x - 32) / 4); + let i = Math.floor((x - 32) / 4); kule = toColor(i); return; } + let c = 255; if (x < g.getWidth()/2) { c = 0; - } else { - c = 255; } kule[0] = c; kule[1] = c; @@ -170,7 +169,7 @@ Bangle.on("lock", function() { g.setColor(kule[0], kule[1], kule[2]); g.fillRect(0, 0, g.getWidth(), top_bar); for (let i = 0; i < 3*3*3; i++) { - r = toColor(i); + let r = toColor(i); g.setColor(r[0], r[1], r[2]); g.fillRect(32+4*i, 12, 32+4*i+3, top_bar); } @@ -378,7 +377,7 @@ function show_unc_icon(icon) { } function show_icon(icon) { - unc = require("heatshrink").decompress(atob(icon)); + let unc = require("heatshrink").decompress(atob(icon)); show_bin_icon(unc); } @@ -389,7 +388,7 @@ function load_bin_icon(i) { } function load_icon(icon) { - unc = require("heatshrink").decompress(atob(icon)); + let unc = require("heatshrink").decompress(atob(icon)); load_bin_icon(unc); } From f520a8ae7b0fc16c28fa1c8c0727168b46c0b5a2 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Sat, 20 Jul 2024 18:56:34 +0100 Subject: [PATCH 116/133] initial commit --- apps/quarterclock/app-icon.js | 1 + apps/quarterclock/app.js | 139 +++++++++++++++++++++++++++++++ apps/quarterclock/app.png | Bin 0 -> 252 bytes apps/quarterclock/metadata.json | 20 +++++ apps/quarterclock/screenshot.png | Bin 0 -> 1475 bytes apps/quarterclock/settings.js | 66 +++++++++++++++ 6 files changed, 226 insertions(+) create mode 100644 apps/quarterclock/app-icon.js create mode 100644 apps/quarterclock/app.js create mode 100644 apps/quarterclock/app.png create mode 100644 apps/quarterclock/metadata.json create mode 100644 apps/quarterclock/screenshot.png create mode 100644 apps/quarterclock/settings.js diff --git a/apps/quarterclock/app-icon.js b/apps/quarterclock/app-icon.js new file mode 100644 index 000000000..d6ed783ec --- /dev/null +++ b/apps/quarterclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4X/AwX48AFCqoAEC4oL/Bf4L/Bf4LTAH4A/ADGqAAIL/Bf4LD")) diff --git a/apps/quarterclock/app.js b/apps/quarterclock/app.js new file mode 100644 index 000000000..da7efed16 --- /dev/null +++ b/apps/quarterclock/app.js @@ -0,0 +1,139 @@ +{ + const minute_boxes = [ + {x:0.5, y:0}, + {x:0.5, y:0.5}, + {x:0, y:0.5}, + {x:0, y:0}, + ]; + + const hour_boxes = [ + {x:0.5, y:0}, + {x:0.75, y:0}, + {x:0.75, y:0.25}, + {x:0.75, y:0.5}, + {x:0.75, y:0.75}, + {x:0.5, y:0.75}, + {x:0.25, y:0.75}, + {x:0, y:0.75}, + {x:0, y:0.5}, + {x:0, y:0.25}, + {x:0, y:0}, + {x:0.25, y:0}, + ]; + + let drawTimeout; + + // schedule a draw for the next 15 minute period + let queueDraw = function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, (60000 * 15) - (Date.now() % (60000 * 15))); + }; + + // Main draw function + let draw = function draw() { + var d = new Date(); + var h = d.getHours(), m = d.getMinutes(); + + g.setBgColor(settings.backgroundColour); + g.clearRect(Bangle.appRect); + + if (settings.showBattery) { + drawBattery(); + } + + // Draw minute box + drawBox(Math.floor(m/15), minute_boxes, Bangle.appRect.h/2, settings.minuteColour); + + // Draw an hour box or write the number + if (settings.digital) { + g.setColor(settings.hourColour); + g.setFont("Vector:60"); + g.setFontAlign(0,0); + g.drawString(h, Bangle.appRect.x + Bangle.appRect.w/2, Bangle.appRect.y + Bangle.appRect.h/2); + } else { + drawBox(h % 12, hour_boxes, Bangle.appRect.h/4, settings.hourColour); + } + + queueDraw(); + }; + + // Draw battery box + let drawBattery = function drawBattery() { + // Round battery up to 10% interval + let battery = (Math.floor(E.getBattery()/10)+1)/10; + + // Maximum battery box + let batterySize = 30; + + // Draw outer box at full brightness + g.setColor(settings.batteryColour); + g.drawRect( + (Bangle.appRect.w / 2) - batterySize, + (Bangle.appRect.h / 2) - batterySize + Bangle.appRect.y, + (Bangle.appRect.w / 2) + batterySize, + (Bangle.appRect.h / 2) + batterySize + Bangle.appRect.y + ); + + // Fade battery colour and draw inner box + g.setColor(settings.batteryColour.split('').map((c) => { + return c=='f' ? Math.ceil(15 * battery).toString(16) : c; + }).join('')); + g.fillRect( + (Bangle.appRect.w / 2) - (batterySize * battery), + (Bangle.appRect.h / 2) - (batterySize * battery) + Bangle.appRect.y, + (Bangle.appRect.w / 2) + (batterySize * battery), + (Bangle.appRect.h / 2) + (batterySize * battery) + Bangle.appRect.y + ); + }; + + // Draw hour or minute boxes + let drawBox = function drawBox(current, boxes, size, colour) { + x1 = (boxes[current].x * Bangle.appRect.h) + (Bangle.appRect.y/2); + y1 = (boxes[current].y * Bangle.appRect.h) + Bangle.appRect.y; + x2 = x1 + size; + y2 = y1 + size; + g.setColor(colour); + g.fillRect(x1, y1, x2, y2); + }; + + let settings = Object.assign({ + // Default values + minuteColour: '#f00', + hourColour: '#ff0', + backgroundColour: 'theme', + showWidgets: true, + showBattery: true, + digital: false, + batteryColour: '#0f0' + }, require('Storage').readJSON('quarterclock.json', true) || {}); + + if (settings.backgroundColour == 'theme') { + settings.backgroundColour = g.theme.bg; + } + + // Set minuteColour to a darker shade if same as hourColour + if (settings.minuteColour == settings.hourColour) { + settings.minuteColour = settings.minuteColour.split('').map((c) => { + return c=='f' ? '7' : c; + }).join(''); + } + + // Show launcher when middle button pressed + // Remove handler to allow fast loading + Bangle.setUI({mode:"clock", remove:function() { + if (drawTimeout) clearTimeout(drawTimeout); + }}); + + // Load and display widgets + if (settings.showWidgets) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); + } + + // draw initial boxes and queue subsequent redraws + draw(); +} + diff --git a/apps/quarterclock/app.png b/apps/quarterclock/app.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a1c7fc0db4e32f7602bbb68af544cf5a7b665b GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}TRdGHLo%G- z&T!;vFyLXi{oj7qC4P&R6vu)+%h-%}OE3AorZnus&4c&0e=TEBSi{8P!7Zrh{KxNr zt_xowci^!*PA(lb*VafUF=cd|T+}v$W8(2yrOrMKyoQli1cVhoMQqON;7qVNqFtl! z!d&)RJm5>x>feoD_Av`AEQw{k)Ut9v7YgfdG1r_&{k_aS(JrjZI>UaibeMYMOQq-x owWzntnL?8;Kd)bYfZ1YM{L6~#HK{=NGkCiCxvXFWXG+iEl@n0-z_3Z#iizdjjafDd4i6Yp+)G$E?rb!zbZ9uhe4_6Khro{H>`x60 zjqDR-FA6FY9KZI2k&%gC!FrLh!-KxHCz+X8f?+~=;!N>H?{lAm%%koY32m1B0*|UBBl^hbBWPBSAxUWC|l=DpE zVore{FTU=yX|yabVPuluYd2lpfW1%6;lbkT&p)v|J8+SOqvp%6n0W_eEM^^e&GDxz zKe8(4`|SEX(+=owxWFGTe}9AihbqoLvD~f?^0(a-k6gi2Z=4^WyKw!tDqE)?ZML%* z8vEn!t52_==&r^j{3C;j#jdKB%O`_pLjc9`&ZhY`x%h%l`R4-Ce8W-rv8c{I80&j;%oV zqj7oA1ML-eD~^S_u6nxn*F5vMlcM3l57ITR3E3B`v+irU)tep@yKT33&DGa?KYGc7 zZD)K|JYwMBocTO6A5WSLT^Z3=KyRnae z7WRg}lKgS=>7CfP^9Ro70d`FNq7(`FoKKD+OPAlDt;C}5(KpDeng+2=vu+x*7s{ru~{1atiF ze9as7pl>5E5sFqE`Sy{2PXB}krt$*O9m%T?C~xDEX%2h9w*ev>)|h;O>kcDO`C6v_ z8(IZCzr6n?Ec@C{{34pso+?BU%-R)3;x-j4|$Ncp6kxW&6A6U$I^uK-60p>3( z?-Yc0B>&!e{y_8f(spGIn;T6` labels[v], + min: 0, + max: values.length - 1, + wrap: true, + step: 1, + onchange: v => { + setSetting(key,values[v]); + } + }; + } + + // Helper method which breaks string set settings down to local settings object + function stringInSettings(name, values, labels) { + return stringItems(name,settings[name], values, labels); + } + + // Show the menu + E.showMenu({ + '' : { 'title' : 'Quarter Clock' }, + '< Back' : () => back(), + 'Hour Colour': stringInSettings('hourColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']), + 'Minute Colour': stringInSettings('minuteColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']), + 'Background Colour': stringInSettings('backgroundColour', ['theme', '#000', '#fff'],['theme', 'Black', 'White']), + 'Digital': { + value: !!settings.digital, // !! converts undefined to false + onchange: v => { + setSetting('digital', v); + }, + }, + 'Show Widgets': { + value: !!settings.showWidgets, + onchange: v => { + setSetting('showWidgets', v); + }, + }, + 'Show Battery': { + value: !!settings.showBattery, + onchange: v => { + setSetting('showBattery', v); + }, + }, + 'Battery Colour': stringInSettings('batteryColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']), + }); +}) From 2b6839bedbee413e577802b9581b7a0d02ea09f5 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Sat, 20 Jul 2024 19:52:36 +0100 Subject: [PATCH 117/133] define variables --- apps/quarterclock/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/quarterclock/app.js b/apps/quarterclock/app.js index da7efed16..837cac6b6 100644 --- a/apps/quarterclock/app.js +++ b/apps/quarterclock/app.js @@ -91,10 +91,10 @@ // Draw hour or minute boxes let drawBox = function drawBox(current, boxes, size, colour) { - x1 = (boxes[current].x * Bangle.appRect.h) + (Bangle.appRect.y/2); - y1 = (boxes[current].y * Bangle.appRect.h) + Bangle.appRect.y; - x2 = x1 + size; - y2 = y1 + size; + let x1 = (boxes[current].x * Bangle.appRect.h) + (Bangle.appRect.y/2); + let y1 = (boxes[current].y * Bangle.appRect.h) + Bangle.appRect.y; + let x2 = x1 + size; + let y2 = y1 + size; g.setColor(colour); g.fillRect(x1, y1, x2, y2); }; From ec9bc29295b7935bffbc8b18d8d88e135902ce8b Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Sun, 21 Jul 2024 19:05:12 +0100 Subject: [PATCH 118/133] Correctly handle hiding/showing widgets --- apps/quarterclock/app.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/quarterclock/app.js b/apps/quarterclock/app.js index 837cac6b6..7899ff2c7 100644 --- a/apps/quarterclock/app.js +++ b/apps/quarterclock/app.js @@ -125,12 +125,15 @@ // Remove handler to allow fast loading Bangle.setUI({mode:"clock", remove:function() { if (drawTimeout) clearTimeout(drawTimeout); + require("widget_utils").show(); }}); // Load and display widgets + Bangle.loadWidgets(); if (settings.showWidgets) { - Bangle.loadWidgets(); - Bangle.drawWidgets(); + require("widget_utils").show(); + } else { + require("widget_utils").hide(); } // draw initial boxes and queue subsequent redraws From 7e5c67a2b6041f5822cec42e901cf54ace762605 Mon Sep 17 00:00:00 2001 From: Ian Ward Date: Tue, 23 Jul 2024 09:16:11 +0100 Subject: [PATCH 119/133] fix bug with 100% battery --- apps/quarterclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/quarterclock/app.js b/apps/quarterclock/app.js index 7899ff2c7..a668860cd 100644 --- a/apps/quarterclock/app.js +++ b/apps/quarterclock/app.js @@ -63,7 +63,7 @@ // Draw battery box let drawBattery = function drawBattery() { // Round battery up to 10% interval - let battery = (Math.floor(E.getBattery()/10)+1)/10; + let battery = Math.min((Math.floor(E.getBattery()/10)+1)/10, 1); // Maximum battery box let batterySize = 30; From 3e10fff37325b29652e1bed507d741bb8885be93 Mon Sep 17 00:00:00 2001 From: Alexander Minges Date: Wed, 24 Jul 2024 13:54:47 +0200 Subject: [PATCH 120/133] use weather codes if language is not English --- apps/ffcniftyapp/app.js | 43 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/apps/ffcniftyapp/app.js b/apps/ffcniftyapp/app.js index 840dd72ff..517c0e2c5 100644 --- a/apps/ffcniftyapp/app.js +++ b/apps/ffcniftyapp/app.js @@ -1,5 +1,5 @@ const w = require("weather"); -//const locale = require("locale"); +const locale = require("locale"); // Weather icons from https://icons8.com/icon/set/weather/color function getSun() { @@ -67,6 +67,33 @@ function chooseIcon(condition) { return getPartSun; } else return getErr; } + +/* +* Choose weather icon to display based on weather conditition code +* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 +*/ +function chooseIconByCode(code) { + const codeGroup = Math.round(code / 100); + switch (codeGroup) { + case 2: return getStorm; + case 3: return getRain; + case 5: + switch (code) { + case 511: return getSnow; + default: return getRain; + } + case 6: return getSnow; + case 7: return getPartSun; + case 8: + switch (code) { + case 800: return getSun; + case 804: return getCloud; + default: return getPartSun; + } + default: return getCloud; + } +} + /*function condenseWeather(condition) { condition = condition.toLowerCase(); if (condition.includes("thunderstorm") || @@ -143,8 +170,18 @@ const clock = new ClockFace({ //let cWea =(curr === "no data" ? "no data" : curr.txt); let cTemp= (curr === "no data" ? 273 : curr.temp); // const temp = locale.temp(curr.temp - 273.15).match(/^(\D*\d*)(.*)$/); - let w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt ); - //let w_icon = chooseIcon(curr.txt); + + if (locale == "en" || locale == "en_GB" || locale == "en_US") { + let w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt); + } else { + // cannot use condition string to determine icon of language is not English; use weather code instead + const code = curr.code || -1; + if (code > 0) { + let w_icon = chooseIconByCode(curr.code); + } else { + let w_icon = getErr(); + } + } g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale); g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale); From 1a3c94579d38018c5c0811eb56dea3e8e6df69d0 Mon Sep 17 00:00:00 2001 From: Alexander Minges Date: Wed, 24 Jul 2024 14:15:32 +0200 Subject: [PATCH 121/133] fix setting icon based on weather code --- apps/ffcniftyapp/app.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/ffcniftyapp/app.js b/apps/ffcniftyapp/app.js index 517c0e2c5..4a11caca6 100644 --- a/apps/ffcniftyapp/app.js +++ b/apps/ffcniftyapp/app.js @@ -171,15 +171,14 @@ const clock = new ClockFace({ let cTemp= (curr === "no data" ? 273 : curr.temp); // const temp = locale.temp(curr.temp - 273.15).match(/^(\D*\d*)(.*)$/); - if (locale == "en" || locale == "en_GB" || locale == "en_US") { - let w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt); + let w_icon = getErr; + if (locale.name === "en" || locale.name === "en_GB" || locale.name === "en_US") { + w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt); } else { - // cannot use condition string to determine icon of language is not English; use weather code instead + // cannot use condition string to determine icon of language is not English; use weather code instead const code = curr.code || -1; if (code > 0) { - let w_icon = chooseIconByCode(curr.code); - } else { - let w_icon = getErr(); + w_icon = chooseIconByCode(curr.code); } } From 883958dc50360c2d736e90df0b1e0bfeba871682 Mon Sep 17 00:00:00 2001 From: Alexander Minges Date: Wed, 24 Jul 2024 14:21:15 +0200 Subject: [PATCH 122/133] update metadata and changelog --- apps/ffcniftyapp/ChangeLog | 1 + apps/ffcniftyapp/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ffcniftyapp/ChangeLog b/apps/ffcniftyapp/ChangeLog index 30dcec467..ef797827e 100644 --- a/apps/ffcniftyapp/ChangeLog +++ b/apps/ffcniftyapp/ChangeLog @@ -1,3 +1,4 @@ 0.01: New Clock Nifty A ++ >> adding more information on the right side of the clock +0.02: Fix weather icon for languages other than English diff --git a/apps/ffcniftyapp/metadata.json b/apps/ffcniftyapp/metadata.json index bbe8e7e69..6f368160b 100644 --- a/apps/ffcniftyapp/metadata.json +++ b/apps/ffcniftyapp/metadata.json @@ -1,7 +1,7 @@ { "id": "ffcniftyapp", "name": "Nifty-A Clock ++", - "version": "0.01", + "version": "0.02", "description": "A nifty clock with time and date and more", "dependencies": {"weather":"app"}, "icon": "app.png", From e9d2478e8117aee2eedc310d58ff7e21da8928f5 Mon Sep 17 00:00:00 2001 From: Alexander Minges Date: Wed, 24 Jul 2024 14:23:11 +0200 Subject: [PATCH 123/133] fix typo in comment --- apps/ffcniftyapp/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ffcniftyapp/app.js b/apps/ffcniftyapp/app.js index 4a11caca6..5ca48c2f1 100644 --- a/apps/ffcniftyapp/app.js +++ b/apps/ffcniftyapp/app.js @@ -175,7 +175,7 @@ const clock = new ClockFace({ if (locale.name === "en" || locale.name === "en_GB" || locale.name === "en_US") { w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt); } else { - // cannot use condition string to determine icon of language is not English; use weather code instead + // cannot use condition string to determine icon if language is not English; use weather code instead const code = curr.code || -1; if (code > 0) { w_icon = chooseIconByCode(curr.code); From ac899251dfc7240efd8d3dc34afd5678e4ee3012 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Wed, 24 Jul 2024 22:13:43 +0200 Subject: [PATCH 124/133] dedreckon: add ded reckoning application --- apps/dedreckon/ChangeLog | 1 + apps/dedreckon/README.md | 17 ++ apps/dedreckon/app-icon.js | 1 + apps/dedreckon/app.png | Bin 0 -> 1841 bytes apps/dedreckon/dedreckon.app.js | 442 ++++++++++++++++++++++++++++++++ apps/dedreckon/metadata.json | 13 + 6 files changed, 474 insertions(+) create mode 100644 apps/dedreckon/ChangeLog create mode 100644 apps/dedreckon/README.md create mode 100644 apps/dedreckon/app-icon.js create mode 100644 apps/dedreckon/app.png create mode 100644 apps/dedreckon/dedreckon.app.js create mode 100644 apps/dedreckon/metadata.json diff --git a/apps/dedreckon/ChangeLog b/apps/dedreckon/ChangeLog new file mode 100644 index 000000000..263d4078d --- /dev/null +++ b/apps/dedreckon/ChangeLog @@ -0,0 +1 @@ +0.01: attempt to import diff --git a/apps/dedreckon/README.md b/apps/dedreckon/README.md new file mode 100644 index 000000000..e46c6fd69 --- /dev/null +++ b/apps/dedreckon/README.md @@ -0,0 +1,17 @@ +# Ded Reckon + +Dead Reckoning using compass and step counter. + +This allows logging track using "dead reckoning" -- that's logging +angles from compass and distances from step counter. You need to mark +turns, and point watch to direction of the turn. Simultaneously, it +tries to log positions using GPS. You can use it to calibrate your +step length by comparing GPS and step counter data. It can also get +pretty accurate recording of track walked in right circumstances. + +Tap bottom part of the screen to select display (text or map for +now). Point watch to new direction, then tap top left part of screen +to indicate a turn. + +Map shows blue line for track from dead reckonging, and green line for +track from GPS. \ No newline at end of file diff --git a/apps/dedreckon/app-icon.js b/apps/dedreckon/app-icon.js new file mode 100644 index 000000000..39b72f00b --- /dev/null +++ b/apps/dedreckon/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AFsAFtoADF1wwqF4wwhEI5goGGIjFYN4wFF1KbHGUolIMc4lGSdIwJd9DstAH7FrBywwgad4veDwojJBIIvcFwIACGBYICGDYvEGBYvdFwqyLL8i+LF7oxFRxgveGAQ0EF5IwfMY4vpL5AFLAEYv/F8owoE44vrAY4vmAQIEEF85dGGE0AE4gvoFwpmHd0oINAH4A/AH4AvA")) diff --git a/apps/dedreckon/app.png b/apps/dedreckon/app.png new file mode 100644 index 0000000000000000000000000000000000000000..db3fcfb8897226665afffca643527bf49ac8b5fc GIT binary patch literal 1841 zcmdT_c~Dd57QeZE%4Ml01OzecSo#7{TG3LV6k;GmG|>PFF#%$0sVtQxhNc?9t3DO9 zra^EDLpm#XYEIy&D{}5yj@t>>jP9hXZ#%D7j!%bQ*VE`GE6NeB_iEP*l zL318*SP^lGq=B-BIn)Td*-vI>Hb&33S&r*9$^%g+?!a@muU!gWi7h4(l^WPV5{7n7%6(SCqjc%e}Yn=YUM~<9wVh-KlV4E zhlTkuhj~7Q10BahVc)};btw?g4qzON;0kDu=$4@lBiA8w;&cxsA;JI5dFbaug%}(S z=Ho~B^^~#x;Owcbkha5jxnBVB)^HZm77LO1Zkn1S)TQV`;mbiEe^Xd`3Gs*KR(fSYw?)X?q%*xO9r7JK zyH)Ql(?sC26pvmU^LKdQ6#&~#ygYO96!xb52o?#Ui9S_rOb8WMx83<~*1>kayk^En z%wVOt_0}F1{$5pAHudw8AlYUHLR<7tjKfcFrX=8@JsUzMzdISCd`?3t2_C;x6k^SD zgV5QE({W%q+G(;A?k%hTPsU+D9^}CV#oL{ZX2Hj}QZV1&flqNpO-y9T@zJ>ZwGrc+Pr%IXE#!i# zk{hCt0iG00pNj2%^upoicw5wcp40U+^6!7zTWV03@Pl@nGnZJ@>swMA__*%d4#m#Q zQF}yUiIj{|t?vtJm?=*ZWx>xq0$`a{?!^4n8umj^*I@~v_t_)68GEMgHnK--dWdW|4h5J;S2>?g^2^K>%)ABf_ zKqr}Xs1x4xxCo&|InHC@L4M2Z(fA#_VTuvz<8Z*@6j!xq+@bl^y}`4w`$w9F5y>hV znojc0Vsy;|hS~eETIF@^5054i<=5QDD&`;!M~$Y6m{6hX$dkn46?hD5tCjI5*@U^Vq+1bzX8m{Lc%7Tr9&|!rkSexo;Ni#pqTRbF@x2nDp%|(|`23*&8CPm($ z7b23#+_k^jbkNfTkHc~Lg?)X3D}uE(Hde4u&CrP%3G!{eE+F@|Bu=}ox^IHJ3hA#j z{Yy|Qz5A-Hgxt=Nt<~$KcZc@5O{OjcFDhp~9Zd7Ad6lfRi*bR)0R~2qqb@jape=uJ zH&*sCx!;vC>>qV?TXyH|K(M$)94k?;xvKx#MNfy*r`BC?wAOSM?`hmvh=pwGNlV=` z6F)sRkLjJ`7Q;4szh8z>j0dOpHEJdqa1&A%=uQID*Ndp_&+cAW$wpPHYi62Qe@upG z6z92kX8FEx+UgzPYuVS=uilLz4SNa-{vo+tUc32MEmgI_y2obyj|s^Dtymg`Z+Y$S zO3Wg|OYSHdPsOe!(Ro~{20t;S;p_WPnOFQBdyMM=+35Jmjd&Q*+50qUAh_Oq#El y=sZ6X*wn(45w5i0Y9_(ZkEI&y!=;-4E!Y(3ZMhIif5wm#XAj~;a#;qZbjR;cizRRX literal 0 HcmV?d00001 diff --git a/apps/dedreckon/dedreckon.app.js b/apps/dedreckon/dedreckon.app.js new file mode 100644 index 000000000..449bf9c1b --- /dev/null +++ b/apps/dedreckon/dedreckon.app.js @@ -0,0 +1,442 @@ +/* Ded Reckon */ +/* eslint-disable no-unused-vars */ + +/* fmt library v0.1.3 */ +let fmt = { + icon_alt : "\0\x08\x1a\1\x00\x00\x00\x20\x30\x78\x7C\xFE\xFF\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00", + icon_m : "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00", + icon_km : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00", + icon_kph : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\xFF\x00\xC3\xC3\xFF\xC3\xC3", + icon_c : "\0\x08\x1a\1\x00\x00\x60\x90\x90\x60\x00\x7F\xFF\xC0\xC0\xC0\xC0\xC0\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + + /* 0 .. DD.ddddd + 1 .. DD MM.mmm' + 2 .. DD MM'ss" + */ + geo_mode : 1, + + init: function() {}, + fmtDist: function(km) { + if (km >= 1.0) return km.toFixed(1) + this.icon_km; + return (km*1000).toFixed(0) + this.icon_m; + }, + fmtSteps: function(n) { return this.fmtDist(0.001 * 0.719 * n); }, + fmtAlt: function(m) { return m.toFixed(0) + this.icon_alt; }, + draw_dot : 1, + add0: function(i) { + if (i > 9) { + return ""+i; + } else { + return "0"+i; + } + }, + fmtTOD: function(now) { + this.draw_dot = !this.draw_dot; + let dot = ":"; + if (!this.draw_dot) + dot = "."; + return now.getHours() + dot + this.add0(now.getMinutes()); + }, + fmtNow: function() { return this.fmtTOD(new Date()); }, + fmtTimeDiff: function(d) { + if (d < 180) + return ""+d.toFixed(0); + d = d/60; + return ""+d.toFixed(0)+"m"; + }, + fmtAngle: function(x) { + switch (this.geo_mode) { + case 0: + return "" + x; + case 1: { + let d = Math.floor(x); + let m = x - d; + m = m*60; + return "" + d + " " + m.toFixed(3) + "'"; + } + case 2: { + let d = Math.floor(x); + let m = x - d; + m = m*60; + let mf = Math.floor(m); + let s = m - mf; + s = s*60; + return "" + d + " " + mf + "'" + s.toFixed(0) + '"'; + } + } + return "bad mode?"; + }, + fmtPos: function(pos) { + let x = pos.lat; + let c = "N"; + if (x<0) { + c = "S"; + x = -x; + } + let s = c+this.fmtAngle(x) + "\n"; + c = "E"; + if (x<0) { + c = "W"; + x = -x; + } + return s + c + this.fmtAngle(x); + }, + fmtFix: function(fix, t) { + if (fix && fix.fix && fix.lat) { + return this.fmtSpeed(fix.speed) + " " + + this.fmtAlt(fix.alt); + } else { + return "N/FIX " + this.fmtTimeDiff(t); + } + }, + fmtSpeed: function(kph) { + return kph.toFixed(1) + this.icon_kph; + }, +}; + +/* gps library v0.1.1 */ +let gps = { + emulator: -1, + init: function(x) { + this.emulator = (process.env.BOARD=="EMSCRIPTEN" + || process.env.BOARD=="EMSCRIPTEN2")?1:0; + }, + state: {}, + on_gps: function(f) { + let fix = this.getGPSFix(); + f(fix); + + /* + "lat": number, // Latitude in degrees + "lon": number, // Longitude in degrees + "alt": number, // altitude in M + "speed": number, // Speed in kph + "course": number, // Course in degrees + "time": Date, // Current Time (or undefined if not known) + "satellites": 7, // Number of satellites + "fix": 1 // NMEA Fix state - 0 is no fix + "hdop": number, // Horizontal Dilution of Precision + */ + this.state.timeout = setTimeout(this.on_gps, 1000, f); + }, + off_gps: function() { + clearTimeout(this.state.timeout); + }, + getGPSFix: function() { + if (!this.emulator) + return Bangle.getGPSFix(); + let fix = {}; + fix.fix = 1; + fix.lat = 50; + fix.lon = 14-(getTime()-this.gps_start) / 1000; /* Go West! */ + fix.alt = 200; + fix.speed = 5; + fix.course = 30; + fix.time = Date(); + fix.satellites = 5; + fix.hdop = 12; + return fix; + }, + gps_start : -1, + start_gps: function() { + Bangle.setGPSPower(1, "libgps"); + this.gps_start = getTime(); + }, + stop_gps: function() { + Bangle.setGPSPower(0, "libgps"); + }, +}; + +/* ui library 0.1 */ +let ui = { + display: 0, + numScreens: 2, + drawMsg: function(msg) { + g.reset().setFont("Vector", 35) + .setColor(1,1,1) + .fillRect(0, this.wi, 176, 176) + .setColor(0,0,0) + .drawString(msg, 5, 30); + }, + drawBusy: function() { + this.drawMsg("\n.oO busy"); + }, + nextScreen: function() { + print("nextS"); + this.display = this.display + 1; + if (this.display == this.numScreens) + this.display = 0; + this.drawBusy(); + }, + prevScreen: function() { + print("prevS"); + this.display = this.display - 1; + if (this.display < 0) + this.display = this.numScreens - 1; + this.drawBusy(); +}, + onSwipe: function(dir) { + this.nextScreen(); +}, + h: 176, + w: 176, + wi: 32, + last_b: 0, + touchHandler: function(d) { + let x = Math.floor(d.x); + let y = Math.floor(d.y); + + if (d.b != 1 || this.last_b != 0) { + this.last_b = d.b; + return; + } + + print("touch", x, y, this.h, this.w); + + /* + if ((xthis.h/2) && (ythis.w/2)) { + print("prev"); + this.prevScreen(); + } + if ((x>this.h/2) && (y>this.w/2)) { + print("next"); + this.nextScreen(); + } + }, + init: function() { + } +}; + +var last_steps = Bangle.getStepCount(), last_time = getTime(), speed = 0, step_phase = 0; + +var mpstep = 0.719 * 1.15; + +function updateSteps() { + if (step_phase ++ > 9) { + step_phase =0; + let steps = Bangle.getStepCount(); + let time = getTime(); + + speed = 3.6 * mpstep * ((steps-last_steps) / (time-last_time)); + last_steps = steps; + last_time = time; + } + return "" + fmt.fmtSpeed(speed) + " " + step_phase + "\n" + fmt.fmtDist(log_dist/1000) + " " + fmt.fmtDist(log_last/1000); +} + +/* compensated compass */ +var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null; +const tiltfixread = require("magnav").tiltfixread; +var heading; + + +var cancel_gps = false; + +function drawStats() { + let fix = gps.getGPSFix(); + + let msg = fmt.fmtFix(fix, getTime() - gps.gps_start); + + msg += "\n" + fmt.fmtDist(gps_dist/1000) + " " + fmt.fmtDist(gps_last/1000) + "\n" + updateSteps(); + let c = Bangle.getCompass(); + if (c) msg += "\n" + c.heading.toFixed(0) + "/" + heading.toFixed(0) + "deg " + log.length + "\n"; + + g.reset().clear().setFont("Vector", 31) + .setColor(1,1,1) + .fillRect(0, 24, 176, 100) + .setColor(0,0,0) + .drawString(msg, 3, 25); +} + +function updateGps() { + if (cancel_gps) + return; + heading = tiltfixread(CALIBDATA.offset,CALIBDATA.scale); + if (ui.display == 0) { + setTimeout(updateGps, 1000); + drawLog(); + drawStats(); + } + if (ui.display == 1) { + setTimeout(updateGps, 1000); + drawLog(); + } +} + +function stopGps() { + cancel_gps=true; + gps.stop_gps(); +} + +var log = [], log_dist = 0, gps_dist = 0; +var log_last = 0, gps_last = 0; + +function logEntry() { + let e = {}; + e.time = getTime(); + e.fix = gps.getGPSFix(); + e.steps = Bangle.getStepCount(); + if (0) { + let c = Bangle.getCompass(); + if (c) + e.dir = c.heading; + else + e.dir = -1; + } else { + e.dir = heading; + } + return e; +} + +function onTurn() { + let e = logEntry(); + log.push(e); +} + +function radians(a) { return a*Math.PI/180; } +function degrees(a) { return a*180/Math.PI; } +// distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km +// https://www.movable-type.co.uk/scripts/latlong.html +// (Equirectangular approximation) +function calcDistance(a,b) { + var x = radians(b.lon-a.lon) * Math.cos(radians((a.lat+b.lat)/2)); + var y = radians(b.lat-a.lat); + return Math.sqrt(x*x + y*y) * 6371000; +} + +var dn, de; +function initConv(fix) { + let n = { lat: fix.lat+1, lon: fix.lon }; + let e = { lat: fix.lat, lon: fix.lon+1 }; + + dn = calcDistance(fix, n); + de = calcDistance(fix, e); + print("conversion is ", dn, 108000, de, 50000); +} +function toM(start, fix) { + return { x: (fix.lon - start.lon) * de, y: (fix.lat - start.lat) * dn }; +} +var mpp = 4; +function toPix(q) { + let p = { x: q.x, y: q.y }; + p.x /= mpp; /* 10 m / pix */ + p.y /= -mpp; + p.x += 85; + p.y += 85; + return p; +} + +function drawLog() { + let here = logEntry(); + if (!here.fix.lat) { + here.fix.lat = 50; + here.fix.lon = 14; + } + initConv(here.fix); + log.push(here); + let l = log; + log_dist = 0; + log_last = -1; + gps_last = -1; + + g.reset().clear(); + g.setColor(0, 0, 1); + let last = { x: 0, y: 0 }; + for (let i = l.length - 2; i >= 0; i--) { + let next = {}; + let m = (l[i+1].steps - l[i].steps) * mpstep; + let dir = radians(180 + l[i].dir); + next.x = last.x + m * Math.sin(dir); + next.y = last.y + m * Math.cos(dir); + print(dir, m, last, next); + let lp = toPix(last); + let np = toPix(next); + g.drawLine(lp.x, lp.y, np.x, np.y); + g.drawCircle(np.x, np.y, 3); + last = next; + if (log_last == -1) + log_last = m; + log_dist += m; + } + g.setColor(0, 1, 0); + last = { x: 0, y: 0 }; + gps_dist = 0; + for (let i = l.length - 2; i >= 0; i--) { + let fix = l[i].fix; + if (fix.fix && fix.lat) { + let next = toM(here.fix, fix); + let lp = toPix(last); + let np = toPix(next); + let d = Math.sqrt((next.x-last.x)*(next.x-last.x)+(next.y-last.y)*(next.y-last.y)); + if (gps_last == -1) + gps_last = d; + gps_dist += d; + g.drawLine(lp.x, lp.y, np.x, np.y); + g.drawCircle(np.x, np.y, 3); + last = next; + } + } + log.pop(); +} + +function testPaint() { + let pos = gps.getGPSFix(); + log = []; + let e = { fix: pos, steps: 100, dir: 0 }; + log.push(e); + e = { fix: pos, steps: 200, dir: 90 }; + log.push(e); + e = { fix: pos, steps: 300, dir: 0 }; + log.push(e); + print(log, log.length, log[0], log[1]); + drawLog(); +} + +function touchHandler(d) { + let x = Math.floor(d.x); + let y = Math.floor(d.y); + + if (d.b != 1 || ui.last_b != 0) { + ui.last_b = d.b; + return; + } + + + if ((xui.h/2) && (y ui.onSwipe(s), + clock : 0 +}); + +if (0) + testPaint(); +if (1) { + g.reset(); + updateGps(); +} diff --git a/apps/dedreckon/metadata.json b/apps/dedreckon/metadata.json new file mode 100644 index 000000000..79bf8868e --- /dev/null +++ b/apps/dedreckon/metadata.json @@ -0,0 +1,13 @@ +{ "id": "dedreckon", + "name": "Ded Reckon", + "version": "0.01", + "description": "Dead Reckoning using compass and step counter", + "icon": "app.png", + "readme": "README.md", + "supports" : ["BANGLEJS2"], + "tags": "outdoors", + "storage": [ + {"name":"dedreckon.app.js","url":"dedreckon.app.js"}, + {"name":"dedreckon.img","url":"app-icon.js","evaluate":true} + ] +} From d6e189945e6467e87cb4e0d53bf1227bc793a5d2 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Fri, 2 Aug 2024 13:11:12 +0200 Subject: [PATCH 125/133] fix(measuretime): make sure to draw correct time number on overlapping-day hours --- apps/measuretime/measuretime.app.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/measuretime/measuretime.app.js b/apps/measuretime/measuretime.app.js index 97b56c710..49f5f2835 100644 --- a/apps/measuretime/measuretime.app.js +++ b/apps/measuretime/measuretime.app.js @@ -78,6 +78,19 @@ return lineEndFull - 5; }; + let drawHourString = function(hour, yLines) { + var hourForDrawing = 0; + if (hour < 0) { + // a negative hour => (+ and - = -) + hourForDrawing = 24 + hour; + } else if (hour >= 24) { + hourForDrawing = hour - 24; + } else { + hourForDrawing = hour; + } + g.drawString(hourForDrawing, hourStringXOffset(hourForDrawing), yLines, true); + }; + let drawTime = function () { g.clear(); var d = new Date(); @@ -101,12 +114,12 @@ switch (yTopLines - 88 + mins) { case -60: lineEnd = lineEndFull; - g.drawString(d.getHours()-1, hourStringXOffset(d.getHours()-1), yTopLines, true); + drawHourString(d.getHours() - 1, yTopLines); break; case 0: case 60: lineEnd = lineEndFull; - g.drawString(d.getHours(), hourStringXOffset(d.getHours()), yTopLines, true); + drawHourString(d.getHours(), yTopLines); break; case 45: case -45: @@ -136,11 +149,11 @@ case 0: case 60: lineEnd = lineEndFull; - g.drawString(d.getHours() + 1, hourStringXOffset(d.getHours()+1), yBottomLines, true); + drawHourString(d.getHours() + 1, yBottomLines);; break; case 120: lineEnd = lineEndFull; - g.drawString(d.getHours() + 2, hourStringXOffset(d.getHours()+2), yBottomLines, true); + drawHourString(d.getHours() + 2, yBottomLines); break; case 15: case 75: From 2b751ede2ffad170d71c6af2666d1b73ab22c4b1 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Fri, 2 Aug 2024 13:15:55 +0200 Subject: [PATCH 126/133] fix(measuretime): remove double semicolon --- apps/measuretime/measuretime.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/measuretime/measuretime.app.js b/apps/measuretime/measuretime.app.js index 49f5f2835..419916416 100644 --- a/apps/measuretime/measuretime.app.js +++ b/apps/measuretime/measuretime.app.js @@ -149,7 +149,7 @@ case 0: case 60: lineEnd = lineEndFull; - drawHourString(d.getHours() + 1, yBottomLines);; + drawHourString(d.getHours() + 1, yBottomLines); break; case 120: lineEnd = lineEndFull; From 5bb2dc87a8e2d728e994bb3cb42a86f81bf6c47d Mon Sep 17 00:00:00 2001 From: Bernhard Date: Fri, 2 Aug 2024 16:02:08 +0200 Subject: [PATCH 127/133] fix(measuretime): update changelog and metadata file --- apps/measuretime/ChangeLog | 1 + apps/measuretime/metadata.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/measuretime/ChangeLog b/apps/measuretime/ChangeLog index d0ac21aac..886e92f22 100644 --- a/apps/measuretime/ChangeLog +++ b/apps/measuretime/ChangeLog @@ -1,2 +1,3 @@ 0.1: Initial release 0.2: Draw line for 3d effect, fix number alignment +0.3: Fix day-end overflowing hour calculation diff --git a/apps/measuretime/metadata.json b/apps/measuretime/metadata.json index 6ba022dc0..e25fe1ef6 100644 --- a/apps/measuretime/metadata.json +++ b/apps/measuretime/metadata.json @@ -1,7 +1,7 @@ { "id": "measuretime", "name": "Measure Time", - "version": "0.2", + "version": "0.3", "description": "Measure Time in a fancy way.", "icon": "measuretime_icon.png", "screenshots": [ From 0918ece048d9510d14489ba2d160c4baf990e002 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 2 Aug 2024 15:47:42 +0100 Subject: [PATCH 128/133] clkinfostopw 0.10: Timer ClockInfo now uses +- icons, and changes timer from 'T-5 min' to just '5 min' to aid readability --- apps/clkinfostopw/ChangeLog | 1 + apps/clkinfostopw/clkinfo.js | 2 +- apps/clkinfostopw/clkinfo.ts | 2 +- apps/clkinfostopw/metadata.json | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/clkinfostopw/ChangeLog b/apps/clkinfostopw/ChangeLog index 20742562f..bca387666 100644 --- a/apps/clkinfostopw/ChangeLog +++ b/apps/clkinfostopw/ChangeLog @@ -1,3 +1,4 @@ 0.01: New clkinfo! 0.02: Added format option, reduced battery usage 0.03: Hardcode colon-format, show milliseconds for the first minute +0.04: Change clockinfo name from timer->Timer, now uses same menu with smpltmr \ No newline at end of file diff --git a/apps/clkinfostopw/clkinfo.js b/apps/clkinfostopw/clkinfo.js index fbbe80a55..9ba4c4dc4 100644 --- a/apps/clkinfostopw/clkinfo.js +++ b/apps/clkinfostopw/clkinfo.js @@ -40,7 +40,7 @@ }; var img = function () { return atob("GBiBAAAAAAB+AAB+AAAAAAB+AAH/sAOB8AcA4A4YcAwYMBgYGBgYGBg8GBg8GBgYGBgAGAwAMA4AcAcA4AOBwAH/gAB+AAAAAAAAAA=="); }; return { - name: "timer", + name: "Timer", img: img(), items: [ { diff --git a/apps/clkinfostopw/clkinfo.ts b/apps/clkinfostopw/clkinfo.ts index f0c2a6ccb..2d14054c6 100644 --- a/apps/clkinfostopw/clkinfo.ts +++ b/apps/clkinfostopw/clkinfo.ts @@ -48,7 +48,7 @@ const img = () => atob("GBiBAAAAAAB+AAB+AAAAAAB+AAH/sAOB8AcA4A4YcAwYMBgYGBgYGBg8GBg8GBgYGBgAGAwAMA4AcAcA4AOBwAH/gAB+AAAAAAAAAA=="); return { - name: "timer", + name: "Timer", img: img(), items: [ { diff --git a/apps/clkinfostopw/metadata.json b/apps/clkinfostopw/metadata.json index f33f61dbb..b785f5c96 100644 --- a/apps/clkinfostopw/metadata.json +++ b/apps/clkinfostopw/metadata.json @@ -1,7 +1,7 @@ { "id": "clkinfostopw", "name": "Stop Watch Clockinfo", - "version":"0.03", + "version":"0.04", "description": "A simple stopwatch, shown via clockinfo", "icon": "app.png", "type": "clkinfo", From 3d1dfd3fb3d157fb861a7e0a6fc7bf964cc57afb Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 2 Aug 2024 15:47:49 +0100 Subject: [PATCH 129/133] update lint --- apps/lint_exemptions.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/apps/lint_exemptions.js b/apps/lint_exemptions.js index e5a2170bd..3b7da1a20 100644 --- a/apps/lint_exemptions.js +++ b/apps/lint_exemptions.js @@ -196,12 +196,6 @@ module.exports = { "no-undef" ] }, - "apps/sixths/sixths.app.js": { - "hash": "2a4676828bdf78df052df402de34e6f1abd1c847ebe0d193fc789cd6e9dd0e5c", - "rules": [ - "no-undef" - ] - }, "apps/scribble/app.js": { "hash": "6d13abd27bab8009a6bdabe1df2df394bc14aac20c68f67e8f8b085fa6b427cd", "rules": [ @@ -1249,12 +1243,6 @@ module.exports = { "no-undef" ] }, - "apps/accelrec/app.js": { - "hash": "b5369a60afc8f360f0b33f71080eb3f5d09a1bf3703acfcf07cd80dd19f1997d", - "rules": [ - "no-undef" - ] - }, "apps/BLEcontroller/app-joy.js": { "hash": "e4f34bb1bc11b52c3d7a1c537a140b0e23ccef82694dcd602cb517a8ba342898", "rules": [ From 8d602d26692bea035495c23061c16fc6319d2adf Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 2 Aug 2024 15:48:02 +0100 Subject: [PATCH 130/133] smpltmr 0.10: Timer ClockInfo now uses +- icons, and changes timer from 'T-5 min' to just '5 min' to aid readability --- apps/smpltmr/ChangeLog | 1 + apps/smpltmr/clkinfo.js | 4 ++-- apps/smpltmr/metadata.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/smpltmr/ChangeLog b/apps/smpltmr/ChangeLog index 2c073ff43..c3f069428 100644 --- a/apps/smpltmr/ChangeLog +++ b/apps/smpltmr/ChangeLog @@ -7,3 +7,4 @@ 0.07: Update clock_info to avoid a redraw 0.08: Timer ClockInfo now updates once a minute 0.09: Timer ClockInfo resets to timer menu when blurred +0.10: Timer ClockInfo now uses +- icons, and changes timer from 'T-5 min' to just '5 min' to aid readability \ No newline at end of file diff --git a/apps/smpltmr/clkinfo.js b/apps/smpltmr/clkinfo.js index a7a6bf71b..6fc2cd265 100644 --- a/apps/smpltmr/clkinfo.js +++ b/apps/smpltmr/clkinfo.js @@ -28,7 +28,7 @@ var min = getAlarmMinutes(); if(min < 0) return "OFF"; - return "T-" + String(min)+ " min"; + return min + " min"; } function increaseAlarm(t){ @@ -80,7 +80,7 @@ offsets.forEach((o, i) => { smpltmrItems.items = smpltmrItems.items.concat({ name: null, - get: () => ({ text: (o > 0 ? "+" : "") + o + " min.", img: smpltmrItems.img }), + get: () => ({ text: (o > 0 ? "+" : "") + o + " min", img: (o>0)?atob("GBiBAAB+AAB+AAAYAAAYAAB+AA3/sA+B8A4AcAwAMBgYGBgYGDAYDDAYDDH/jDH/jDAYDDAYDBgYGBgYGAwAMA4AcAeB4AH/gAB+AA=="):atob("GBiBAAB+AAB+AAAYAAAYAAB+AA3/sA+B8A4AcAwAMBgAGBgAGDAADDAADDH/jDH/jDAADDAADBgAGBgAGAwAMA4AcAeB4AH/gAB+AA==") }), show: function() { }, hide: function() { }, blur: restoreMainItem, diff --git a/apps/smpltmr/metadata.json b/apps/smpltmr/metadata.json index 98affcfe6..2f33f07b9 100644 --- a/apps/smpltmr/metadata.json +++ b/apps/smpltmr/metadata.json @@ -2,7 +2,7 @@ "id": "smpltmr", "name": "Simple Timer", "shortName": "Simple Timer", - "version": "0.09", + "version": "0.10", "description": "A very simple app to start a timer.", "icon": "app.png", "tags": "tool,alarm,timer,clkinfo", From e069a3870cdd56e11d4faf9d824a0b29d737b625 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 2 Aug 2024 15:48:15 +0100 Subject: [PATCH 131/133] twotwoclock 0.02: Clockinfos now save under correct name, and wrap correctly to >1 line --- apps/twotwoclock/ChangeLog | 1 + apps/twotwoclock/app.js | 8 ++++---- apps/twotwoclock/metadata.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/twotwoclock/ChangeLog b/apps/twotwoclock/ChangeLog index 09953593e..d3275c0e8 100644 --- a/apps/twotwoclock/ChangeLog +++ b/apps/twotwoclock/ChangeLog @@ -1 +1,2 @@ 0.01: New Clock! +0.02: Clockinfos now save under correct name, and wrap correctly to >1 line \ No newline at end of file diff --git a/apps/twotwoclock/app.js b/apps/twotwoclock/app.js index 57be691e1..b2d5ea9fb 100644 --- a/apps/twotwoclock/app.js +++ b/apps/twotwoclock/app.js @@ -134,8 +134,8 @@ for (var i=0;i<10;i++) if (g.stringWidth(txt) > options.w) // if too big, smaller font g.setFont("LECO1976Regular14"); if (g.stringWidth(txt) > options.w) {// if still too big, split to 2 lines - var l = g.wrapString(txt, options.w); - txt = l.slice(0,2).join("\n") + (l.length>2)?"...":""; + var l = g.wrapString(txt, options.w-4); + txt = l.slice(0,2).join("\n") + ((l.length>2)?"...":""); } var x = options.x+options.w/2, y = options.y+54; g.setColor(g.theme.bg).drawString(txt, x-2, y). // draw the text background @@ -147,12 +147,12 @@ for (var i=0;i<10;i++) }; clockInfoMenuA = require("clock_info").addInteractive(clockInfoItems, { - app:"pebblepp", + app:"twotwoclock", x : g.getWidth()-clockInfoW, y: 0, w: clockInfoW, h:clockInfoH, draw : clockInfoDraw }); clockInfoMenuB = require("clock_info").addInteractive(clockInfoItems, { - app:"pebblepp", + app:"twotwoclock", x : g.getWidth()-clockInfoW, y: clockInfoH, w: clockInfoW, h:clockInfoH, draw : clockInfoDraw }); diff --git a/apps/twotwoclock/metadata.json b/apps/twotwoclock/metadata.json index ebcba539c..ae3b958ef 100644 --- a/apps/twotwoclock/metadata.json +++ b/apps/twotwoclock/metadata.json @@ -1,7 +1,7 @@ { "id": "twotwoclock", "name": "TwoTwo Clock", "shortName":"22 Clock", - "version":"0.01", + "version":"0.02", "description": "A clock with the time split over two lines, with custom backgrounds and two ClockInfos", "icon": "icon.png", "type": "clock", From 8c5eceb7c31e98e41f750bc54bfdc3dbd677734b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 2 Aug 2024 16:07:48 +0100 Subject: [PATCH 132/133] Revert "clkinfostopw renaming timer->Timer" This just gets in the way of simpltmr, but maybe we should consider renaming to "stopw" or similar - or maybe it doesn't need its own category at all This reverts commit 0918ece048d9510d14489ba2d160c4baf990e002. --- apps/clkinfostopw/ChangeLog | 1 - apps/clkinfostopw/clkinfo.js | 2 +- apps/clkinfostopw/clkinfo.ts | 2 +- apps/clkinfostopw/metadata.json | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/clkinfostopw/ChangeLog b/apps/clkinfostopw/ChangeLog index bca387666..20742562f 100644 --- a/apps/clkinfostopw/ChangeLog +++ b/apps/clkinfostopw/ChangeLog @@ -1,4 +1,3 @@ 0.01: New clkinfo! 0.02: Added format option, reduced battery usage 0.03: Hardcode colon-format, show milliseconds for the first minute -0.04: Change clockinfo name from timer->Timer, now uses same menu with smpltmr \ No newline at end of file diff --git a/apps/clkinfostopw/clkinfo.js b/apps/clkinfostopw/clkinfo.js index 9ba4c4dc4..fbbe80a55 100644 --- a/apps/clkinfostopw/clkinfo.js +++ b/apps/clkinfostopw/clkinfo.js @@ -40,7 +40,7 @@ }; var img = function () { return atob("GBiBAAAAAAB+AAB+AAAAAAB+AAH/sAOB8AcA4A4YcAwYMBgYGBgYGBg8GBg8GBgYGBgAGAwAMA4AcAcA4AOBwAH/gAB+AAAAAAAAAA=="); }; return { - name: "Timer", + name: "timer", img: img(), items: [ { diff --git a/apps/clkinfostopw/clkinfo.ts b/apps/clkinfostopw/clkinfo.ts index 2d14054c6..f0c2a6ccb 100644 --- a/apps/clkinfostopw/clkinfo.ts +++ b/apps/clkinfostopw/clkinfo.ts @@ -48,7 +48,7 @@ const img = () => atob("GBiBAAAAAAB+AAB+AAAAAAB+AAH/sAOB8AcA4A4YcAwYMBgYGBgYGBg8GBg8GBgYGBgAGAwAMA4AcAcA4AOBwAH/gAB+AAAAAAAAAA=="); return { - name: "Timer", + name: "timer", img: img(), items: [ { diff --git a/apps/clkinfostopw/metadata.json b/apps/clkinfostopw/metadata.json index b785f5c96..f33f61dbb 100644 --- a/apps/clkinfostopw/metadata.json +++ b/apps/clkinfostopw/metadata.json @@ -1,7 +1,7 @@ { "id": "clkinfostopw", "name": "Stop Watch Clockinfo", - "version":"0.04", + "version":"0.03", "description": "A simple stopwatch, shown via clockinfo", "icon": "app.png", "type": "clkinfo", From a5e37ec55617a13a0beb9b889a0fa8389da4de5f Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Fri, 2 Aug 2024 20:12:44 +0200 Subject: [PATCH 133/133] dedreckon: add a note about magnav. --- apps/dedreckon/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/dedreckon/README.md b/apps/dedreckon/README.md index e46c6fd69..706c7f191 100644 --- a/apps/dedreckon/README.md +++ b/apps/dedreckon/README.md @@ -14,4 +14,7 @@ now). Point watch to new direction, then tap top left part of screen to indicate a turn. Map shows blue line for track from dead reckonging, and green line for -track from GPS. \ No newline at end of file +track from GPS. + +You probably want magnav installed (and calibrated) for useful +results, as it provides library with better compass. \ No newline at end of file