diff --git a/apps/a_clock_timer/ChangeLog b/apps/a_clock_timer/ChangeLog index 26b8d73d7..262f30295 100644 --- a/apps/a_clock_timer/ChangeLog +++ b/apps/a_clock_timer/ChangeLog @@ -1,2 +1,3 @@ 0.01: Beta version for Bangle 2 (2021/11/28) 0.02: Shows night time on the map (2022/12/28) +0.03: Add 1 minute timer with upper taps (2023/01/05) diff --git a/apps/a_clock_timer/README.md b/apps/a_clock_timer/README.md index e435bdb3f..3fcc00b28 100644 --- a/apps/a_clock_timer/README.md +++ b/apps/a_clock_timer/README.md @@ -2,7 +2,10 @@ * Works with Bangle 2 * Timer - * Right tap: start/increase by 10 minutes; Left tap: decrease by 5 minutes + * Top Right tap: increase by 1 minute + * Top Left tap: decrease by 1 minute + * Bottom Right tap: increase by 10 minutes + * Bottom Left tap: decrease by 5 minutes * Short buzz at T-30, T-20, T-10 ; Double buzz at T * Other time zones * Currently hardcoded to Paris and Tokyo (this will be customizable in a future version) diff --git a/apps/a_clock_timer/app.js b/apps/a_clock_timer/app.js index d13098b26..441229842 100644 --- a/apps/a_clock_timer/app.js +++ b/apps/a_clock_timer/app.js @@ -18,19 +18,29 @@ var timervalue = 0; var istimeron = false; var timertick; -Bangle.on('touch',t=>{ - if (t == 1) { +Bangle.on('touch',(touchside, touchdata)=>{ + if (touchside == 1) { Bangle.buzz(30); - if (timervalue < 5*60) { timervalue = 1 ; } - else { timervalue -= 5*60; } + var changevalue = 0; + if(touchdata.y > 88) { + changevalue += 60*5; + } else { + changevalue += 60*1; + } + if (timervalue < changevalue) { timervalue = 1 ; } + else { timervalue -= changevalue; } } - else if (t == 2) { + else if (touchside == 2) { Bangle.buzz(30); if (!istimeron) { istimeron = true; timertick = setInterval(countDown, 1000); } - timervalue += 60*10; + if(touchdata.y > 88) { + timervalue += 60*10; + } else { + timervalue += 60*1; + } } }); @@ -73,7 +83,7 @@ function countDown() { function showWelcomeMessage() { g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); g.setFontAlign(0, 0).setFont("6x8"); - g.drawString("Touch right to", 44, 80); + g.drawString("Tap right to", 44, 80); g.drawString("start timer", 44, 88); setTimeout(function(){ g.reset().clearRect(0, 76, 44+44, g.getHeight()/2+6); }, 8000); } @@ -103,18 +113,21 @@ function draw() { g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT); - var x_sun = 176 - (getGmt().getHours() / 24 * 176 + 4); + var gmtHours = getGmt().getHours(); + + var x_sun = 176 - (gmtHours / 24 * 176 + 4); g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight()); g.reset(); - var x_night_start = 176 - (((getGmt().getHours()-6)%24) / 24 * 176 + 4); - var x_night_end = 176 - (((getGmt().getHours()+6)%24) / 24 * 176 + 4); - for (let x = x_night_start; x < 176; x+=2) { - g.setColor('#000').drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight()); + var x_night_start = 176 - (((gmtHours-6)%24) / 24 * 176 + 4); + var x_night_end = 176 - (((gmtHours+6)%24) / 24 * 176 + 4); + g.setColor('#000'); + for (let x = x_night_start; x < (x_night_end < x_night_start ? 176 : x_night_end); x+=2) { + g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight()); } if (x_night_end < x_night_start) { for (let x = 0; x < x_night_end; x+=2) { - g.setColor('#000').drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight()); + g.drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight()); } } diff --git a/apps/a_clock_timer/metadata.json b/apps/a_clock_timer/metadata.json index b488f33bc..4e7a36b8a 100644 --- a/apps/a_clock_timer/metadata.json +++ b/apps/a_clock_timer/metadata.json @@ -1,7 +1,7 @@ { "id": "a_clock_timer", "name": "A Clock with Timer", - "version": "0.02", + "version": "0.03", "description": "A Clock with Timer, Map and Time Zones", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/aiclock/ChangeLog b/apps/aiclock/ChangeLog index 15a7d0a14..6d6eeb55e 100644 --- a/apps/aiclock/ChangeLog +++ b/apps/aiclock/ChangeLog @@ -3,4 +3,5 @@ 0.03: Indicate battery level through line occurrence. 0.04: Use widget_utils module. 0.05: Support for clkinfo. -0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc. \ No newline at end of file +0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc. +0.07: Use clock_info.addInteractive instead of a custom implementation \ No newline at end of file diff --git a/apps/aiclock/README.md b/apps/aiclock/README.md index 31dd5aa29..521bd2c5e 100644 --- a/apps/aiclock/README.md +++ b/apps/aiclock/README.md @@ -11,8 +11,7 @@ The original output of stable diffusion is shown here: My implementation is shown below. Note that horizontal lines occur randomly, but the probability is correlated with the battery level. So if your screen contains only a few lines its time to charge your bangle again ;) Also note that the upper text -implementes the clkinfo module and can be configured via touch left/right/up/down. -Touch at the center to trigger the selected action. +implements the clkinfo module and can be configured via touch and swipe left/right and up/down.  diff --git a/apps/aiclock/aiclock.app.js b/apps/aiclock/aiclock.app.js index 66fa2ca6a..350832367 100644 --- a/apps/aiclock/aiclock.app.js +++ b/apps/aiclock/aiclock.app.js @@ -1,7 +1,6 @@ /************************************************ * AI Clock */ - const storage = require('Storage'); const clock_info = require("clock_info"); @@ -21,124 +20,14 @@ Graphics.prototype.setFontGochiHand = function(scale) { return this; } -/************************************************ - * Set some important constants such as width, height and center - */ -var W = g.getWidth(),R=W/2; -var H = g.getHeight(); -var cx = W/2; -var cy = H/2; -var drawTimeout; - -/************************************************ - * SETTINGS - */ -const SETTINGS_FILE = "aiclock.setting.json"; -let settings = { - menuPosX: 0, - menuPosY: 0, -}; -let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; -for (const key in saved_settings) { - settings[key] = saved_settings[key] -} - - -/************************************************ - * Menu - */ -function getDate(){ - var date = new Date(); - return ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2) -} - - -// Custom clockItems menu - therefore, its added here and not in a clkinfo.js file. -var clockItems = { - name: getDate(), - img: null, - items: [ - { name: "Week", - get: () => ({ text: "Week " + weekOfYear(), img: null}), - show: function() { clockItems.items[0].emit("redraw"); }, - hide: function () {} - }, - ] - }; - -function weekOfYear() { - var date = new Date(); - date.setHours(0, 0, 0, 0); - // Thursday in current week decides the year. - date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); - // January 4 is always in week 1. - var week1 = new Date(date.getFullYear(), 0, 4); - // Adjust to Thursday in week 1 and count number of weeks from date to week1. - return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - - 3 + (week1.getDay() + 6) % 7) / 7); -} - - - -// Load menu -var menu = clock_info.load(); -menu = menu.concat(clockItems); - - - // Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it. - if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){ - settings.menuPosX = 0; - settings.menuPosY = 0; - } - - function canRunMenuItem(){ - if(settings.menuPosY == 0){ - return false; - } - - var menuEntry = menu[settings.menuPosX]; - var item = menuEntry.items[settings.menuPosY-1]; - return item.run !== undefined; - } - - - function runMenuItem(){ - if(settings.menuPosY == 0){ - return; - } - - var menuEntry = menu[settings.menuPosX]; - var item = menuEntry.items[settings.menuPosY-1]; - try{ - var ret = item.run(); - if(ret){ - Bangle.buzz(300, 0.6); - } - } catch (ex) { - // Simply ignore it... - } - } - - -/* - * Based on the great multi clock from https://github.com/jeffmer/BangleApps/ - */ -Graphics.prototype.drawRotRect = function(w, r1, r2, angle) { - angle = angle % 360; - var w2=w/2, h=r2-r1, theta=angle*Math.PI/180; - return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0], - {x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta})); -}; - - -function drawBackground() { +function drawBackground(start, end) { g.setFontAlign(0,0); - g.setColor(g.theme.fg); + g.setColor("#000"); var bat = E.getBattery() / 100.0; - var y = 0; - while(y < H){ + var y = start; + while(y < end){ // Show less lines in case of small battery level. if(Math.random() > bat){ y += 5; @@ -154,6 +43,30 @@ function drawBackground() { } +/************************************************ + * Set some important constants such as width, height and center + */ +var W = g.getWidth(),R=W/2; +var H = g.getHeight(); +var cx = W/2; +var cy = H/2; +var drawTimeout; + +var clkInfoY = 60; + + +/* + * Based on the great multi clock from https://github.com/jeffmer/BangleApps/ + */ +Graphics.prototype.drawRotRect = function(w, r1, r2, angle) { + angle = angle % 360; + var w2=w/2, h=r2-r1, theta=angle*Math.PI/180; + return this.fillPoly(this.transformVertices([-w2,0,-w2,-h,w2,-h,w2,0], + {x:cx+r1*Math.sin(theta),y:cy-r1*Math.cos(theta),rotate:theta})); +}; + + + function drawCircle(isLocked){ g.setColor(g.theme.fg); g.fillCircle(cx, cy, 12); @@ -163,54 +76,6 @@ function drawCircle(isLocked){ g.fillCircle(cx, cy, 6); } -function toAngle(a){ - if (a < 0){ - return 360 + a; - } - - if(a > 360) { - return 360 - a; - } - - return a -} - - -function drawMenuItem(text, image){ - if(text == null){ - drawTime(); - return - } - text = String(text); - - g.reset().setBgColor("#fff").setColor("#000"); - g.setFontAlign(0,0); - g.setFont("Vector", 20); - - var imgWidth = image == null ? 0 : 24; - var strWidth = g.stringWidth(text); - var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2); - var w = imgWidth + strWidth; - - g.clearRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2) - - // Draw right line as designed by stable diffusion - g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2); - g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2); - g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2); - - // And finally the text - g.drawString(text, cx+imgWidth/2, 42); - g.drawString(text, cx+1+imgWidth/2, 41); - - if(image != null) { - var scale = image.width ? imgWidth / image.width : 1; - g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale}); - } - - drawTime(); -} - function drawTime(){ // Draw digital time first @@ -267,34 +132,23 @@ function drawDigits(){ } -function drawMenu(){ - var menuEntry = menu[settings.menuPosX]; - - // The first entry is the overview... - if(settings.menuPosY == 0){ - drawMenuItem(menuEntry.name, menuEntry.img); - return; - } - - // Draw item if needed - var item = menuEntry.items[settings.menuPosY-1].get(); - drawMenuItem(item.text, item.img); +function draw(){ + // Note that we force a redraw also of the clock info as + // we want to ensure (for design purpose) that the hands + // are above the clkinfo section. + clockInfoMenu.redraw(); } - - - -function draw(){ +function drawMainClock(){ // Queue draw in one minute queueDraw(); - g.reset(); - g.clearRect(0, 0, g.getWidth(), g.getHeight()); - g.setColor(1,1,1); + g.setColor("#fff"); + g.reset().clearRect(0, clkInfoY, g.getWidth(), g.getHeight()); - drawBackground(); - drawMenu(); + drawBackground(clkInfoY, H); + drawTime(); drawCircle(Bangle.isLocked()); } @@ -304,7 +158,7 @@ function draw(){ */ Bangle.on('lcdPower',on=>{ if (on) { - draw(true); + draw(); } else { // stop draw timer if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; @@ -315,62 +169,10 @@ Bangle.on('lock', function(isLocked) { drawCircle(isLocked); }); -Bangle.on('touch', function(btn, e){ - var left = parseInt(g.getWidth() * 0.22); - var right = g.getWidth() - left; - var upper = parseInt(g.getHeight() * 0.22); - var lower = g.getHeight() - upper; - - var is_upper = e.y < upper; - var is_lower = e.y > lower; - var is_left = e.x < left && !is_upper && !is_lower; - var is_right = e.x > right && !is_upper && !is_lower; - var is_center = !is_upper && !is_lower && !is_left && !is_right; - - if(is_lower){ - Bangle.buzz(40, 0.6); - settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1); - - draw(); - } - - if(is_upper){ - Bangle.buzz(40, 0.6); - settings.menuPosY = settings.menuPosY-1; - settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY; - - draw(); - } - - if(is_right){ - Bangle.buzz(40, 0.6); - settings.menuPosX = (settings.menuPosX+1) % menu.length; - settings.menuPosY = 0; - draw(); - } - - if(is_left){ - Bangle.buzz(40, 0.6); - settings.menuPosY = 0; - settings.menuPosX = settings.menuPosX-1; - settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX; - draw(); - } - - if(is_center){ - if(canRunMenuItem()){ - runMenuItem(); - } - } -}); - E.on("kill", function(){ - try{ - storage.write(SETTINGS_FILE, settings); - } catch(ex){ - // If this fails, we still kill the app... - } + clockInfoMenu.remove(); + delete clockInfoMenu; }); @@ -386,6 +188,55 @@ function queueDraw() { } +/************************************************ + * Clock Info + */ +let clockInfoItems = clock_info.load(); +let clockInfoMenu = clock_info.addInteractive(clockInfoItems, { + x : 0, + y: 0, + w: W, + h: clkInfoY, + draw : (itm, info, options) => { + g.setFontAlign(0,0); + g.setFont("Vector", 20); + + g.setColor("#fff"); + g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h); + drawBackground(0, clkInfoY+2); + + // Set text and font + var image = info.img; + var text = String(info.text); + + var imgWidth = image == null ? 0 : 24; + var strWidth = g.stringWidth(text); + var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2); + var w = imgWidth + strWidth; + + // Draw right line as designed by stable diffusion + g.setColor(options.focus ? "#0f0" : "#fff"); + g.fillRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2) + + g.setColor("#000"); + g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2); + g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2); + g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2); + + // Draw text and image + g.drawString(text, cx+imgWidth/2, 42); + g.drawString(text, cx+1+imgWidth/2, 41); + + if(image != null) { + var scale = image.width ? imgWidth / image.width : 1; + g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale}); + } + + drawMainClock(); + } +}); + + /* * Lets start widgets, listen for btn etc. */ @@ -400,7 +251,7 @@ Bangle.loadWidgets(); require('widget_utils').hide(); // Clear the screen once, at startup and draw clock -g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear(); +g.setTheme({bg:"#fff",fg:"#000",dark:false}); draw(); // After drawing the watch face, we can draw the widgets diff --git a/apps/aiclock/metadata.json b/apps/aiclock/metadata.json index 4ff40ca65..4c01ecaa9 100644 --- a/apps/aiclock/metadata.json +++ b/apps/aiclock/metadata.json @@ -3,7 +3,7 @@ "name": "AI Clock", "shortName":"AI Clock", "icon": "aiclock.png", - "version":"0.06", + "version":"0.07", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.", diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog index cd52c7665..4bd8664c1 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -24,4 +24,4 @@ 0.24: Update clock_info to avoid a redraw 0.25: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on fw2v16. ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc. - +0.26: Use clkinfo.addInteractive instead of a custom implementation diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md index d869fa2cf..b19e52787 100644 --- a/apps/bwclk/README.md +++ b/apps/bwclk/README.md @@ -3,18 +3,16 @@ A very minimalistic clock.  +## ToDos and known issues +- [ ] The clkinfo is always shown and its, therefore, not possible to only show the time as shown in the screenshot. +- [ ] The weeknumber is currently not an option in clkinfo. +- [ ] Its not possible to run clkinfo items (e.g. trigger home assistant). + ## Features The BW clock implements features that are exposed by other apps through the `clkinfo` module. -For example, if you install the HomeAssistant app, this menu item will be shown if you click right -and additionally allows you to send triggers directly from the clock (select triggers via up/down and -send via click center). Here are examples of other apps that are integrated: - -- Bangle data such as steps, heart rate, battery or charging state. -- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled* -- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app* -- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app* - -Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden. +For example, if you install the HomeAssistant app, this menu item will be shown if you first +touch the bottom of the screen and then swipe left/right to the home assistant menu. To select +sub-items simply swipe up/down. ## Settings - Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden). @@ -22,25 +20,6 @@ Note: If some apps are not installed (e.gt. weather app), then this menu item is - The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further. - Your bangle uses the sys color settings so you can change the color too. -## Menu structure -2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to trigger HomeAssistant. - -Simply click left / right to go through the menu entries such as Bangle, Weather etc. -and click up/down to move into this sub-menu. You can then click in the middle of the screen -to e.g. send a trigger via HomeAssistant once you selected it. The actions really depend -on the app that provide this sub-menu through the `clkinfo` module. - -``` - Bangle -- Agenda -- Weather -- HomeAssistant - | | | | - Battery Entry 1 Temperature Trigger1 - | | | | - Steps ... ... ... - | - ... -``` - - ## Thanks to - Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located. - Icons created by Flaticon diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js index db531a22f..05db62779 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -31,6 +31,19 @@ for (const key in saved_settings) { settings[key] = saved_settings[key]; } +let isFullscreen = function() { + var s = settings.screen.toLowerCase(); + if(s == "dynamic"){ + return Bangle.isLocked(); + } else { + return s == "full"; + } +}; + +let getLineY = function(){ + return H/5*2 + (isFullscreen() ? 0 : 8); +} + /************************************************ * Assets */ @@ -84,72 +97,48 @@ let imgLock = function() { /************************************************ - * Menu + * Clock Info */ -// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file. -let bwItems = { - name: null, - img: null, - items: [ - { name: "WeekOfYear", - get: () => ({ text: "Week " + weekOfYear(), img: null}), - show: function() {}, - hide: function () {} - }, - ] -}; +let clockInfoItems = clock_info.load(); +let clockInfoMenu = clock_info.addInteractive(clockInfoItems, { + x : 0, + y: 135, + w: W, + h: H-135, + draw : (itm, info, options) => { + g.setColor(g.theme.fg); + g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h); -let weekOfYear = function() { - var date = new Date(); - date.setHours(0, 0, 0, 0); - // Thursday in current week decides the year. - date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7); - // January 4 is always in week 1. - var week1 = new Date(date.getFullYear(), 0, 4); - // Adjust to Thursday in week 1 and count number of weeks from date to week1. - return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - - 3 + (week1.getDay() + 6) % 7) / 7); -}; + g.setFontAlign(0,0); + g.setColor(g.theme.bg); - -// Load menu -let menu = clock_info.load(); -menu = menu.concat(bwItems); - - -// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it. -if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){ - settings.menuPosX = 0; - settings.menuPosY = 0; -} - -let canRunMenuItem = function() { - if(settings.menuPosY == 0){ - return false; - } - - var menuEntry = menu[settings.menuPosX]; - var item = menuEntry.items[settings.menuPosY-1]; - return item.run !== undefined; -}; - - -let runMenuItem = function() { - if(settings.menuPosY == 0){ - return; - } - - var menuEntry = menu[settings.menuPosX]; - var item = menuEntry.items[settings.menuPosY-1]; - try{ - var ret = item.run(); - if(ret){ - Bangle.buzz(300, 0.6); + if (options.focus){ + g.drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1); // show if focused + g.drawRect(options.x+1, options.y+1, options.x+options.w-3, options.y+options.h-2); // show if focused } - } catch (ex) { - // Simply ignore it... + + // Set text and font + var image = info.img; + var text = String(info.text); + if(text.split('\n').length > 1){ + g.setMiniFont(); + } else { + g.setSmallFont(); + } + + // Compute sizes + var strWidth = g.stringWidth(text); + var imgWidth = image == null ? 0 : 24; + var midx = options.x+options.w/2; + + // Draw + if (image) { + var scale = imgWidth / image.width; + g.drawImage(image, midx-parseInt(imgWidth*1.3/2)-parseInt(strWidth/2), options.y+6, {scale: scale}); + } + g.drawString(text, midx+parseInt(imgWidth*1.3/2), options.y+20); } -}; +}); /************************************************ @@ -161,7 +150,7 @@ let draw = function() { // Draw clock drawDate(); - drawMenuAndTime(); + drawTime(); drawLock(); drawWidgets(); }; @@ -169,7 +158,7 @@ let draw = function() { let drawDate = function() { // Draw background - var y = H/5*2 + (isFullscreen() ? 0 : 8); + var y = getLineY() g.reset().clearRect(0,0,W,y); // Draw date @@ -197,14 +186,12 @@ let drawDate = function() { }; -let drawTime = function(y, smallText) { +let drawTime = function() { // Draw background + var y1 = getLineY(); + var y = y1; var date = new Date(); - // Draw time - g.setColor(g.theme.bg); - g.setFontAlign(0,0); - var hours = String(date.getHours()); var minutes = date.getMinutes(); minutes = minutes < 10 ? String("0") + minutes : minutes; @@ -212,67 +199,18 @@ let drawTime = function(y, smallText) { var timeStr = hours + colon + minutes; // Set y coordinates correctly - y += parseInt((H - y)/2) + 5; + y += parseInt((H - y)/2)-10; - // Show large or small time depending on info entry - if(smallText){ - y -= 15; - g.setMediumFont(); - } else { - g.setLargeFont(); - } + // Clear region + g.setColor(g.theme.fg); + g.fillRect(0,y1,W,y+20); + + g.setMediumFont(); + g.setColor(g.theme.bg); + g.setFontAlign(0,0); g.drawString(timeStr, W/2, y); }; -let drawMenuItem = function(text, image) { - // First clear the time region - var y = H/5*2 + (isFullscreen() ? 0 : 8); - - g.setColor(g.theme.fg); - g.fillRect(0,y,W,H); - - // Draw menu text - var hasText = (text != null && text != ""); - if(hasText){ - g.setFontAlign(0,0); - - // For multiline text we show an even smaller font... - text = String(text); - if(text.split('\n').length > 1){ - g.setMiniFont(); - } else { - g.setSmallFont(); - } - - var imgWidth = image == null ? 0 : 24; - var strWidth = g.stringWidth(text); - g.setColor(g.theme.fg).fillRect(0, 149-14, W, H); - g.setColor(g.theme.bg).drawString(text, W/2 + imgWidth/2 + 2, 149+3); - - if(image != null){ - var scale = imgWidth / image.width; - g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 149 - parseInt(imgWidth/2), {scale: scale}); - } - } - - // Draw time - drawTime(y, hasText); -}; - - -let drawMenuAndTime = function() { - var menuEntry = menu[settings.menuPosX]; - - // The first entry is the overview... - if(settings.menuPosY == 0){ - drawMenuItem(menuEntry.name, menuEntry.img); - return; - } - - // Draw item if needed - var item = menuEntry.items[settings.menuPosY-1].get(); - drawMenuItem(item.text, item.img); -}; let drawLock = function() { if(settings.showLock && Bangle.isLocked()){ @@ -291,17 +229,6 @@ let drawWidgets = function() { }; -let isFullscreen = function() { - var s = settings.screen.toLowerCase(); - if(s == "dynamic"){ - return Bangle.isLocked(); - } else { - return s == "full"; - } -}; - - - /************************************************ * Listener */ @@ -343,74 +270,12 @@ let lockListenerBw = function(isLocked) { }; Bangle.on('lock', lockListenerBw); -let chargingListenerBw = function(charging) { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; - // Jump to battery - settings.menuPosX = 0; - settings.menuPosY = 1; - draw(); +let kill = function(){ + clockInfoMenu.remove(); + delete clockInfoMenu; }; -Bangle.on('charging', chargingListenerBw); - -let touchListenerBw = function(btn, e) { - var widget_size = isFullscreen() ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better... - var left = parseInt(g.getWidth() * 0.22); - var right = g.getWidth() - left; - var upper = parseInt(g.getHeight() * 0.22) + widget_size; - var lower = g.getHeight() - upper; - - var is_upper = e.y < upper; - var is_lower = e.y > lower; - var is_left = e.x < left && !is_upper && !is_lower; - var is_right = e.x > right && !is_upper && !is_lower; - var is_center = !is_upper && !is_lower && !is_left && !is_right; - - if(is_lower){ - Bangle.buzz(40, 0.6); - settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1); - - drawMenuAndTime(); - } - - if(is_upper){ - if(e.y < widget_size){ - return; - } - - Bangle.buzz(40, 0.6); - settings.menuPosY = settings.menuPosY-1; - settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY; - - drawMenuAndTime(); - } - - if(is_right){ - Bangle.buzz(40, 0.6); - settings.menuPosX = (settings.menuPosX+1) % menu.length; - settings.menuPosY = 0; - drawMenuAndTime(); - } - - if(is_left){ - Bangle.buzz(40, 0.6); - settings.menuPosY = 0; - settings.menuPosX = settings.menuPosX-1; - settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX; - drawMenuAndTime(); - } - - if(is_center){ - if(canRunMenuItem()){ - runMenuItem(); - } - } -}; -Bangle.on('touch', touchListenerBw); - -let save = () => storage.write(SETTINGS_FILE, settings); -E.on("kill", save); +E.on("kill", kill); /************************************************ * Startup Clock diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index 376124a96..e26307410 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -1,11 +1,11 @@ { "id": "bwclk", "name": "BW Clock", - "version": "0.25", - "description": "A very minimalistic clock to mainly show date and time.", + "version": "0.26", + "description": "A very minimalistic clock.", "readme": "README.md", "icon": "app.png", - "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}], + "screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}], "type": "clock", "tags": "clock,clkinfo", "supports": ["BANGLEJS2"], diff --git a/apps/bwclk/screenshot_4.png b/apps/bwclk/screenshot_4.png deleted file mode 100644 index 83de5c2ce..000000000 Binary files a/apps/bwclk/screenshot_4.png and /dev/null differ diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog index 044b8c35f..b353459b9 100644 --- a/apps/dtlaunch/ChangeLog +++ b/apps/dtlaunch/ChangeLog @@ -23,4 +23,6 @@ button to exit is no longer an option. facilitate 'fast switching' of apps where available. 0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since widgets would still be loaded when they weren't supposed to. +0.21: Bangle 2: Call Bangle.drawWidgets() early on so that the widget field +immediately follows the correct theme. diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js index a7a318c18..e1ce6d8d1 100644 --- a/apps/dtlaunch/app-b2.js +++ b/apps/dtlaunch/app-b2.js @@ -84,6 +84,7 @@ g.flip(); }; + Bangle.drawWidgets(); // To immediately update widget field to follow current theme - remove leftovers if previous app set custom theme. Bangle.loadWidgets(); drawPage(0); diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json index b69a1a5e6..d1aa6f679 100644 --- a/apps/dtlaunch/metadata.json +++ b/apps/dtlaunch/metadata.json @@ -1,7 +1,7 @@ { "id": "dtlaunch", "name": "Desktop Launcher", - "version": "0.20", + "version": "0.21", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "icon": "icon.png", diff --git a/apps/entonclk/ChangeLog b/apps/entonclk/ChangeLog index 62e2d0c20..e16defa54 100644 --- a/apps/entonclk/ChangeLog +++ b/apps/entonclk/ChangeLog @@ -1 +1,2 @@ -0.1: New App! \ No newline at end of file +0.1: New App! +0.2: Now with timer function diff --git a/apps/entonclk/README.md b/apps/entonclk/README.md index 8c788c7a5..c67cc19c8 100644 --- a/apps/entonclk/README.md +++ b/apps/entonclk/README.md @@ -6,4 +6,16 @@ Things I changed: - The main font for the time is now Audiowide - Removed the written out day name and replaced it with steps and bpm -- Changed the date string to a (for me) more readable string \ No newline at end of file +- Changed the date string to a (for me) more readable string + +Timer function: +- Touch the right side, to start the timer +- Initial timer timeout is 300s/5min +- Right touch again, add 300s/5min to timeout +- Left touch, decrease timeout by 60s/1min +- So it is easy, to add timeouts like 7min/3min or 12min +- Special thanks to the maintainer of the a_clock_timer app from which I borrowed the code. + +Todo: +- Make displayed information configurable, after https://github.com/espruino/BangleApps/issues/2226 +- Clean up code diff --git a/apps/entonclk/app.js b/apps/entonclk/app.js index 69fdea479..292030b86 100644 --- a/apps/entonclk/app.js +++ b/apps/entonclk/app.js @@ -1,67 +1,127 @@ +// Fonts Graphics.prototype.setFontAudiowide = function() { - // Actual height 33 (36 - 4) var widths = atob("CiAsESQjJSQkHyQkDA=="); var font = atob("AAAAAAAAAAAAAAAAAAAAAPAAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAfgAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAADgAAAAAAHgAAAAAAfgAAAAAA/gAAAAAD/gAAAAAH/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf/AAAAAB/8AAAAAD/4AAAAAP/gAAAAAf+AAAAAB/8AAAAAH/wAAAAAP/gAAAAA/+AAAAAB/8AAAAAD/wAAAAAD/gAAAAAD+AAAAAAD4AAAAAADwAAAAAADAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAA//+AAAAB///AAAAH///wAAAP///4AAAf///8AAA////+AAA/4AP+AAB/gAD/AAB/AA9/AAD+AB+/gAD+AD+/gAD+AD+/gAD8AH+fgAD8AP8fgAD8AP4fgAD8Af4fgAD8A/wfgAD8A/gfgAD8B/gfgAD8D/AfgAD8D+AfgAD8H+AfgAD8P8AfgAD8P4AfgAD8f4AfgAD8/wAfgAD8/gAfgAD+/gA/gAD+/AA/gAB/eAB/AAB/sAD/AAB/wAH/AAA////+AAAf///8AAAP///4AAAH///wAAAD///gAAAA//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//gAAAAH//gAAAAP//gAD8Af//gAD8A///gAD8B///gAD8B///gAD8B/AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB//8AfgAA//4AfgAAf/wAfgAAP/gAfgAAB8AAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD/////gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//4AAAAD//8AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAD//+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAAAB+AAAAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAD/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//AAfgAD//wAfgAD//4AfgAD//8AfgAD//8AfgAD//+AfgAD8D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAD8A///AAAAAf/+AAAAAP/4AAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///AAAAH///wAAAf///8AAAf///8AAA////+AAB/////AAB/h+H/AAD/B+B/gAD+B+A/gAD+B+A/gAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B/A/gAD8B///gAD8B///gAD8A///AAAAAf//AAAAAf/+AAAAAH/4AAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAAAD8AAAAgAD8AAABgAD8AAAHgAD8AAAfgAD8AAA/gAD8AAD/gAD8AAP/gAD8AA//gAD8AB//AAD8AH/8AAD8Af/wAAD8A//AAAD8D/+AAAD8P/4AAAD8f/gAAAD9//AAAAD//8AAAAD//wAAAAD//gAAAAD/+AAAAAD/4AAAAAD/wAAAAAD/AAAAAAD8AAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH/4AAAAAP/8AAAH+f/+AAAf////AAA/////gAB/////gAB///A/gAD//+AfgAD//+AfgAD+D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+D+AfgAD//+AfgAD//+AfgAB///A/gAB/////gAA/////AAAP////AAAD+f/+AAAAAP/8AAAAAH/4AAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/wAAAAA//4AAAAB//8AAAAB//8AfgAD//+AfgAD/D+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD8B+AfgAD+B+A/gAD+B+A/gAD/B+B/gAB/////AAB/////AAA////+AAAf///8AAAP///4AAAH///wAAAB///AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAPAAAA/AAfgAAA/AAfgAAA/AAfgAAA/AAfgAAAeAAPAAAAAAAAAAAAAAAAAAAAAAAAAA"); var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 48+(scale<<8)+(1<<16)); }; +// Globals variables +var timervalue = 0; +var istimeron = false; +var timertick; + +// Functions function getSteps() { - var steps = 0; - try{ - if (WIDGETS.wpedom !== undefined) { - steps = WIDGETS.wpedom.getSteps(); - } else if (WIDGETS.activepedom !== undefined) { - steps = WIDGETS.activepedom.getSteps(); - } else { - steps = Bangle.getHealthStatus("day").steps; - } + var steps = 0; + try{ + if (WIDGETS.wpedom !== undefined) { + steps = WIDGETS.wpedom.getSteps(); + } else if (WIDGETS.activepedom !== undefined) { + steps = WIDGETS.activepedom.getSteps(); + } else { + steps = Bangle.getHealthStatus("day").steps; + } } catch(ex) { - // In case we failed, we can only show 0 steps. - return "?"; + // In case we failed, we can only show 0 steps. + return "?"; } - return Math.round(steps); + return Math.round(steps); } +function timeToString(duration) { + var hrs = ~~(duration / 3600); + var mins = ~~((duration % 3600) / 60); + var secs = ~~duration % 60; + var ret = ""; + if (hrs > 0) { + ret += "" + hrs + ":" + (mins < 10 ? "0" : ""); + } + ret += "" + mins + ":" + (secs < 10 ? "0" : ""); + ret += "" + secs; + return ret; +} + +function countDown() { + timervalue--; + + g.reset().clearRect(0, 40, 44+99, g.getHeight()/2-25); + + g.setFontAlign(0, -1, 0); + g.setFont("6x8", 2).drawString(timeToString(timervalue), 95, g.getHeight()/2-50); + + if (timervalue <= 0) { + istimeron = false; + clearInterval(timertick); + + Bangle.buzz().then(()=>{ + return new Promise(resolve=>setTimeout(resolve, 500)); + }).then(()=>{ + return Bangle.buzz(1000); + }); + } + else + if ((timervalue <= 30) && (timervalue % 10 == 0)) { Bangle.buzz(); } +} + +// Touch +Bangle.on('touch',t => { + if (t == 1) { + // Touch on the left, reduce timervalue about 60s + Bangle.buzz(30); + if (timervalue < 60) { timervalue = 1 ; } + else { timervalue -= 60; } + } + // Touch on the right, raise timervaule about 300s + else if (t == 2) { + Bangle.buzz(30); + if (!istimeron) { + istimeron = true; + timertick = setInterval(countDown, 1000); + } + timervalue += 60*5; + } +}); + { // must be inside our own scope here so that when we are unloaded everything disappears // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global -let drawTimeout; + let drawTimeout; -// Actually draw the watch face -let draw = function() { - var x = g.getWidth() / 2; - var y = g.getHeight() / 2; - g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets) - var date = new Date(); - var timeStr = require("locale").time(date, 1); // Hour and minute - g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y); - var dateStr = require("locale").date(date, 1).toUpperCase(); - g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28); - g.setFontAlign(0, 0).setFont("6x8", 2); - g.drawString(getSteps(), 50, y+70); - g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70); + // Actually draw the watch face + let draw = function() { + var x = g.getWidth() / 2; + var y = g.getHeight() / 2; + g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets) + var date = new Date(); + var timeStr = require("locale").time(date, 1); // Hour and minute + g.setFontAlign(0, 0).setFont("Audiowide").drawString(timeStr, x, y); + var dateStr = require("locale").date(date, 1).toUpperCase(); + g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y+28); + g.setFontAlign(0, 0).setFont("6x8", 2); + g.drawString(getSteps(), 50, y+70); + g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), g.getWidth() -37, y + 70); - // queue next draw - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); -}; - -// Show launcher when middle button pressed -Bangle.setUI({ - mode : "clock", - remove : function() { - // Called to unload all of the clock app + // queue next draw if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; - delete Graphics.prototype.setFontAnton; - }}); -// Load widgets -Bangle.loadWidgets(); -draw(); -setTimeout(Bangle.drawWidgets,0); -} + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); + }; + + // Show launcher when middle button pressed + Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFontAnton; + }}); + // Load widgets + Bangle.loadWidgets(); + draw(); + setTimeout(Bangle.drawWidgets,0); +} \ No newline at end of file diff --git a/apps/entonclk/metadata.json b/apps/entonclk/metadata.json index 7e4947406..4b7174263 100644 --- a/apps/entonclk/metadata.json +++ b/apps/entonclk/metadata.json @@ -1,8 +1,8 @@ { "id": "entonclk", "name": "Enton Clock", - "version": "0.1", - "description": "A simple clock using the Audiowide font. ", + "version": "0.2", + "description": "A simple clock using the Audiowide font with timer. ", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], "type": "clock", diff --git a/apps/gipy/ChangeLog b/apps/gipy/ChangeLog index 3b0d62009..f913c9e58 100644 --- a/apps/gipy/ChangeLog +++ b/apps/gipy/ChangeLog @@ -63,3 +63,13 @@ * Record traveled distance to get a good average speed. * Breaks (low speed) will not count in average speed. * Bugfix in average speed. + +0.16: + * When lost indicates nearest point on path. + * Rescale display if lost and too far. + * New setting to hide points and increase display speed. + * Speed optimisations. + * Estimated time of Arrival/Going back. + * Display current and next segment in red so that you know where to go. + * Avoid angles flickering at low speed at the cost of less refresh. + * Splash screen while waiting for gps signal. diff --git a/apps/gipy/README.md b/apps/gipy/README.md index 6c9b87c23..4ca98dea8 100644 --- a/apps/gipy/README.md +++ b/apps/gipy/README.md @@ -2,12 +2,10 @@ Gipy allows you to follow gpx traces on your watch. - + -It is for now meant for bicycling and not hiking -(it uses your movement to figure out your orientation -and walking is too slow). +It is mainly meant for bicycling but hiking might be fine. It is untested on Banglejs1. If you can try it, you would be welcome. @@ -20,10 +18,10 @@ It provides the following features : - display the path with current position from gps - detects and buzzes if you leave the path - buzzes before sharp turns -- buzzes before nodes with comments +- buzzes before waypoints (for example when you need to turn in https://mapstogpx.com/) - display instant / average speed -- display distance to next node +- display distance to next point - display additional data from openstreetmap : - water points - toilets @@ -54,32 +52,47 @@ Your path will be displayed in svg. ### Starting Gipy -Once you start gipy you will have a menu for selecting your trace (if more than one). -Choose the one you want and here you go : +At start you will have a menu for selecting your trace (if more than one). +Choose the one you want and you will reach the splash screen where you'll wait for the gps signal. +Once you have a signal you will reach the main screen: - + -On your screen you can see : +On your screen you can see: - yourself (the big black dot) - the path (the top of the screen is in front of you) +- on the path, current and next segments are red and other ones are black - if needed a projection of yourself on the path (small black dot) -- extremities of segments as white dots -- turning points as doubled white dots -- some text on the left (from top to bottom) : +- points as white dots +- waypoints as doubled white dots +- some text on the left (from top to bottom): + * time to reach start point at current average speed * current time + * time to reach end point at current average speed * left distance till end of current segment - * distance from start of path / path length + * remaining distance / path length * average speed / instant speed - interest points from openstreetmap as color dots : - * red : bakery - * deep blue : water point - * cyan : toilets (often doubles as water point) - * green : artwork + * red: bakery + * deep blue: water point + * cyan: toilets (often doubles as water point) + * green: artwork - a *turn* indicator on the top right when you reach a turning point - a *gps* indicator (blinking) on the top right if you lose gps signal - a *lost* indicator on the top right if you stray too far away from path -- a black segment extending from you when you are lost, indicating the rough direction of where to go + +### Lost + +If you stray away from path we will rescale the display to continue displaying nearby segments and +display the direction to follow as a black segment. + +Note that while lost, the app will slow down a lot since it will start scanning all possible points to figure out where you +are. On path it just needed to scan a few points ahead and behind. + + + +The distance to next point displayed corresponds to the length of the black segment. ### Settings @@ -87,6 +100,7 @@ Few settings for now (feel free to suggest me more) : - keep gps alive : if turned off, will try to save battery by turning the gps off on long segments - max speed : used to compute how long to turn the gps off +- display points : display/hide points (not waypoints) ### Caveats diff --git a/apps/gipy/TODO b/apps/gipy/TODO index 53c3530e2..266a1c5c9 100644 --- a/apps/gipy/TODO +++ b/apps/gipy/TODO @@ -1,10 +1,5 @@ -* bugs - -- when exactly on turn, distance to next point is still often 50m - -----> it does not buzz very often on turns - -- when going backwards we have a tendencing to get a wrong current_segment ++ use Bangle.project(latlong) * additional features @@ -15,7 +10,6 @@ (and look at more than next point) - display distance to next water/toilet ? -- dynamic map rescale - display scale (100m) - compress path ? diff --git a/apps/gipy/app.js b/apps/gipy/app.js index ae82e5dfb..c9d018cac 100644 --- a/apps/gipy/app.js +++ b/apps/gipy/app.js @@ -6,10 +6,29 @@ var settings = Object.assign( { keep_gps_alive: true, max_speed: 35, + display_points: true, }, require("Storage").readJSON("gipy.json", true) || {} ); +let profile_start_times = []; + +let splashscreen = require("heatshrink").decompress( + atob( + "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" + ) +); + +function start_profiling() { + profile_start_times.push(getTime()); +} + +function end_profiling(label) { + let end_time = getTime(); + let elapsed = end_time - profile_start_times.pop(); + console.log("profile:", label, "took", elapsed); +} + let interests_colors = [ 0xf800, // Bakery, red 0x001f, // DrinkingWater, blue @@ -29,9 +48,29 @@ function binary_search(array, x) { return start; } +// return a string containing estimated time of arrival. +// speed is in km/h +// remaining distance in km +// hour, minutes is current time +function compute_eta(hour, minutes, approximate_speed, remaining_distance) { + if (isNaN(approximate_speed) || approximate_speed < 0.1) { + return ""; + } + let time_needed = (remaining_distance * 60) / approximate_speed; // in minutes + let eta_in_minutes = hour * 60 + minutes + time_needed; + let eta_minutes = Math.round(eta_in_minutes % 60); + let eta_hour = Math.round((eta_in_minutes - eta_minutes) / 60) % 24; + if (eta_minutes < 10) { + return eta_hour.toString() + ":0" + eta_minutes; + } else { + return eta_hour.toString() + ":" + eta_minutes; + } +} + class Status { constructor(path) { this.path = path; + this.scale_factor = 40000.0; // multiply geo coordinates by this to get pixels coordinates this.on_path = false; // are we on the path or lost ? this.position = null; // where we are this.adjusted_cos_direction = null; // cos of where we look at @@ -39,8 +78,7 @@ class Status { this.current_segment = null; // which segment is closest this.reaching = null; // which waypoint are we reaching ? this.distance_to_next_point = null; // how far are we from next point ? - this.paused_time = 0.0; // how long did we stop (stops don't count in avg speed) - this.paused_since = getTime(); + this.projected_point = null; let r = [0]; // let's do a reversed prefix computations on all distances: @@ -54,67 +92,51 @@ class Status { previous_point = point; } this.remaining_distances = r; // how much distance remains at start of each segment - this.starting_time = this.paused_since; // time we start + this.starting_time = null; // time we start this.advanced_distance = 0.0; this.gps_coordinates_counter = 0; // how many coordinates did we receive - this.old_points = []; - this.old_times = []; + this.old_points = []; // record previous points but only when enough distance between them + this.old_times = []; // the corresponding times } new_position_reached(position) { // we try to figure out direction by looking at previous points // instead of the gps course which is not very nice. - this.gps_coordinates_counter += 1; + let now = getTime(); + + if (this.old_points.length == 0) { + this.gps_coordinates_counter += 1; + this.old_points.push(position); + this.old_times.push(now); + return null; + } else { + let previous_point = this.old_points[this.old_points.length - 1]; + let distance_to_previous = previous_point.distance(position); + // gps signal is noisy but rarely above 4 meters + if (distance_to_previous < 4) { + return null; + } + } + this.gps_coordinates_counter += 1; this.old_points.push(position); this.old_times.push(now); - if (this.old_points.length == 1) { - return null; - } - - let last_point = this.old_points[this.old_points.length - 1]; let oldest_point = this.old_points[0]; + let distance_to_oldest = oldest_point.distance(position); - // every 7 points we count the distance - if (this.gps_coordinates_counter % 7 == 0) { - let distance = last_point.distance(oldest_point); - if (distance < 150.0) { + // every 3 points we count the distance + if (this.gps_coordinates_counter % 3 == 0) { + if (distance_to_oldest < 150.0) { // to avoid gps glitches - this.advanced_distance += distance; + this.advanced_distance += distance_to_oldest; } } - if (this.old_points.length == 8) { - let p1 = this.old_points[0] - .plus(this.old_points[1]) - .plus(this.old_points[2]) - .plus(this.old_points[3]) - .times(1 / 4); - let p2 = this.old_points[4] - .plus(this.old_points[5]) - .plus(this.old_points[6]) - .plus(this.old_points[7]) - .times(1 / 4); - let t1 = (this.old_times[1] + this.old_times[2]) / 2; - let t2 = (this.old_times[5] + this.old_times[6]) / 2; - this.instant_speed = p1.distance(p2) / (t2 - t1); + this.instant_speed = distance_to_oldest / (now - this.old_times[0]); + + if (this.old_points.length == 4) { this.old_points.shift(); this.old_times.shift(); - } else { - this.instant_speed = - oldest_point.distance(last_point) / (now - this.old_times[0]); - - // update paused time if we are too slow - if (this.instant_speed < 2) { - if (this.paused_since === null) { - this.paused_since = now; - } - } else { - if (this.paused_since !== null) { - this.paused_time += now - this.paused_since; - this.paused_since = null; - } - } } // let's just take angle of segment between newest point and a point a bit before let previous_index = this.old_points.length - 3; @@ -153,6 +175,7 @@ class Status { let next_segment = res[1]; if (this.is_lost(next_segment)) { + // start_profiling(); // it did not work, try anywhere res = this.path.nearest_segment( this.position, @@ -163,6 +186,7 @@ class Status { ); orientation = res[0]; next_segment = res[1]; + // end_profiling("repositioning"); } // now check if we strayed away from path or back to it let lost = this.is_lost(next_segment); @@ -223,16 +247,30 @@ class Status { return this.remaining_distances[0] - remaining_in_correct_orientation; } } + // check if we are lost (too far from segment we think we are on) + // if we are adjust scale so that path will still be displayed. + // we do the scale adjustment here to avoid recomputations later on. is_lost(segment) { - let distance_to_nearest = this.position.distance_to_segment( + let projection = this.position.closest_segment_point( this.path.point(segment), this.path.point(segment + 1) ); - return distance_to_nearest > 50; + this.projected_point = projection; // save this info for display + let distance_to_projection = this.position.distance(projection); + if (distance_to_projection > 50) { + this.scale_factor = + Math.min(88.0 / distance_to_projection, 1.0) * 40000.0; + return true; + } else { + this.scale_factor = 40000.0; + return false; + } } display(orientation) { g.clear(); + // start_profiling(); this.display_map(); + // end_profiling("display_map"); this.display_interest_points(); this.display_stats(orientation); @@ -265,14 +303,17 @@ class Status { let c = interest_point.coordinates( this.position, this.adjusted_cos_direction, - this.adjusted_sin_direction + this.adjusted_sin_direction, + this.scale_factor ); g.setColor(color).fillCircle(c[0], c[1], 5); } } display_stats(orientation) { - let remaining_distance = this.remaining_distance(orientation); - let rounded_distance = Math.round(remaining_distance / 100) / 10; + let remaining_forward_distance = this.remaining_distance(0); + let remaining_backward_distance = this.remaining_distance(1); + let rounded_forward_distance = + Math.round(remaining_forward_distance / 100) / 10; let total = Math.round(this.remaining_distances[0] / 100) / 10; let now = new Date(); let minutes = now.getMinutes().toString(); @@ -280,24 +321,53 @@ class Status { minutes = "0" + minutes; } let hours = now.getHours().toString(); + // now, distance to next point in meters g.setFont("6x8:2") - .setFontAlign(-1, -1, 0) .setColor(g.theme.fg) - .drawString(hours + ":" + minutes, 0, 30); - - g.setFont("6x8:2").drawString( - "" + this.distance_to_next_point + "m", - 0, - g.getHeight() - 49 - ); + .drawString( + "" + this.distance_to_next_point + "m", + 0, + g.getHeight() - 49 + ); let point_time = this.old_times[this.old_times.length - 1]; - let done_in = point_time - this.starting_time - this.paused_time; + let done_in = point_time - this.starting_time; let approximate_speed = Math.round( (this.advanced_distance * 3.6) / done_in ); - let approximate_instant_speed = Math.round(this.instant_speed * 3.6); + let forward_eta = compute_eta( + now.getHours(), + now.getMinutes(), + approximate_speed, + remaining_forward_distance / 1000 + ); + + let backward_eta = compute_eta( + now.getHours(), + now.getMinutes(), + approximate_speed, + remaining_backward_distance / 1000 + ); + + // display backward ETA + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString(backward_eta, 0, 30); + // display the clock + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString(hours + ":" + minutes, 0, 48); + // now display ETA + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(g.theme.fg) + .drawString(forward_eta, 0, 66); + + // display speed (avg and instant) + let approximate_instant_speed = Math.round(this.instant_speed * 3.6); g.setFont("6x8:2") .setFontAlign(-1, -1, 0) .drawString( @@ -306,12 +376,14 @@ class Status { g.getHeight() - 15 ); + // display distance on path g.setFont("6x8:2").drawString( - "" + rounded_distance + "/" + total, + "" + rounded_forward_distance + "/" + total, 0, g.getHeight() - 32 ); + // display various indicators if (this.distance_to_next_point <= 100) { if (this.path.is_waypoint(this.reaching)) { g.setColor(0.0, 1.0, 0.0) @@ -343,19 +415,44 @@ class Status { let half_height = g.getHeight() / 2; let previous_x = null; let previous_y = null; + let scale_factor = this.scale_factor; + + // display direction to next point if lost + if (!this.on_path) { + let next_point = this.path.point(this.current_segment + 1); + let previous_point = this.path.point(this.current_segment); + let nearest_point; + if ( + previous_point.fake_distance(this.position) < + next_point.fake_distance(this.position) + ) { + nearest_point = previous_point; + } else { + nearest_point = next_point; + } + let tx = (nearest_point.lon - cx) * scale_factor; + let ty = (nearest_point.lat - cy) * scale_factor; + let rotated_x = tx * cos - ty * sin; + let rotated_y = tx * sin + ty * cos; + let x = half_width - Math.round(rotated_x); // x is inverted + let y = half_height + Math.round(rotated_y); + g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y); + } + + // now display path for (let i = start; i < end; i++) { - let tx = (points[2 * i] - cx) * 40000.0; - let ty = (points[2 * i + 1] - cy) * 40000.0; + let tx = (points[2 * i] - cx) * scale_factor; + let ty = (points[2 * i + 1] - cy) * scale_factor; let rotated_x = tx * cos - ty * sin; let rotated_y = tx * sin + ty * cos; let x = half_width - Math.round(rotated_x); // x is inverted let y = half_height + Math.round(rotated_y); if (previous_x !== null) { - if (i == this.current_segment + 1) { - g.setColor(0.0, 1.0, 0.0); - } else { - g.setColor(1.0, 0.0, 0.0); + let segment_color = g.theme.fg; + if (i == this.current_segment + 1 || i == this.current_segment + 2) { + segment_color = 0xf800; } + g.setColor(segment_color); g.drawLine(previous_x, previous_y, x, y); if (this.path.is_waypoint(i - 1)) { @@ -364,10 +461,12 @@ class Status { g.setColor(g.theme.bg); g.fillCircle(previous_x, previous_y, 5); } - g.setColor(g.theme.fg); - g.fillCircle(previous_x, previous_y, 4); - g.setColor(g.theme.bg); - g.fillCircle(previous_x, previous_y, 3); + if (settings.display_points) { + g.setColor(g.theme.fg); + g.fillCircle(previous_x, previous_y, 4); + g.setColor(g.theme.bg); + g.fillCircle(previous_x, previous_y, 3); + } } previous_x = x; @@ -389,51 +488,21 @@ class Status { g.setColor(g.theme.fgH); g.fillCircle(half_width, half_height, 5); - // display old points for direction debug - // for (let i = 0; i < this.old_points.length; i++) { - // let tx = (this.old_points[i].lon - cx) * 40000.0; - // let ty = (this.old_points[i].lat - cy) * 40000.0; - // let rotated_x = tx * cos - ty * sin; - // let rotated_y = tx * sin + ty * cos; - // let x = half_width - Math.round(rotated_x); // x is inverted - // let y = half_height + Math.round(rotated_y); - // g.setColor((i + 1) / 4.0, 0.0, 0.0); - // g.fillCircle(x, y, 3); - // } - - // display current-segment's projection for debug - let projection = pos.closest_segment_point( - this.path.point(this.current_segment), - this.path.point(this.current_segment + 1) - ); - - let tx = (projection.lon - cx) * 40000.0; - let ty = (projection.lat - cy) * 40000.0; + // display current-segment's projection + let tx = (this.projected_point.lon - cx) * scale_factor; + let ty = (this.projected_point.lat - cy) * scale_factor; let rotated_x = tx * cos - ty * sin; let rotated_y = tx * sin + ty * cos; let x = half_width - Math.round(rotated_x); // x is inverted let y = half_height + Math.round(rotated_y); - g.setColor(g.theme.fg); + g.setColor(g.theme.fgH); g.fillCircle(x, y, 4); - - // display direction to next point if lost - if (!this.on_path) { - let next_point = this.path.point(this.current_segment + 1); - let diff = next_point.minus(this.position); - let angle = Math.atan2(diff.lat, diff.lon); - let tx = Math.cos(angle) * 50.0; - let ty = Math.sin(angle) * 50.0; - let rotated_x = tx * cos - ty * sin; - let rotated_y = tx * sin + ty * cos; - let x = half_width - Math.round(rotated_x); // x is inverted - let y = half_height + Math.round(rotated_y); - g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y); - } } } function load_gpc(filename) { let buffer = require("Storage").readArrayBuffer(filename); + let file_size = buffer.length; let offset = 0; // header @@ -475,7 +544,7 @@ function load_gpc(filename) { let interests_starts = Uint16Array(buffer, offset, starts_length); offset += 2 * starts_length; - return [ + let path_data = [ points, waypoints, interests_coordinates, @@ -483,6 +552,18 @@ function load_gpc(filename) { interests_on_path, interests_starts, ]; + + // checksum file size + if (offset != file_size) { + console.log("invalid file size", file_size, "expected", offset); + let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset; + E.showAlert(msg).then(function () { + E.showAlert(); + start_gipy(filename, path_data); + }); + } else { + start_gipy(filename, path_data); + } } class Path { @@ -502,20 +583,6 @@ class Path { return r != 0; } - // execute op on all segments. - // start is index of first wanted segment - // end is 1 after index of last wanted segment - on_segments(op, start, end) { - let previous_point = null; - for (let i = start; i < end + 1; i++) { - let point = new Point(this.points[2 * i], this.points[2 * i + 1]); - if (previous_point !== null) { - op(previous_point, point, i); - } - previous_point = point; - } - } - // return point at given index point(index) { let lon = this.points[2 * index]; @@ -541,39 +608,28 @@ class Path { // we are going to compute two min distances, one for each direction. let indices = [0, 0]; let mins = [Number.MAX_VALUE, Number.MAX_VALUE]; - this.on_segments( - function (p1, p2, i) { - // we use the dot product to figure out if oriented correctly - // let distance = point.fake_distance_to_segment(p1, p2); - let projection = point.closest_segment_point(p1, p2); - let distance = point.fake_distance(projection); + let p1 = new Point(this.points[2 * start], this.points[2 * start + 1]); + for (let i = start + 1; i < end + 1; i++) { + let p2 = new Point(this.points[2 * i], this.points[2 * i + 1]); + + let closest_point = point.closest_segment_point(p1, p2); + let distance = point.length_squared(closest_point); + + let dot = + cos_direction * (p2.lon - p1.lon) + sin_direction * (p2.lat - p1.lat); + let orientation = +(dot < 0); // index 0 is good orientation + if (distance <= mins[orientation]) { + mins[orientation] = distance; + indices[orientation] = i - 1; + } + + p1 = p2; + } - // let d = projection.minus(point).times(40000.0); - // let rotated_x = d.lon * acos - d.lat * asin; - // let rotated_y = d.lon * asin + d.lat * acos; - // let x = g.getWidth() / 2 - Math.round(rotated_x); // x is inverted - // let y = g.getHeight() / 2 + Math.round(rotated_y); - // - let diff = p2.minus(p1); - let dot = cos_direction * diff.lon + sin_direction * diff.lat; - let orientation = +(dot < 0); // index 0 is good orientation - // g.setColor(0.0, 0.0 + orientation, 1.0 - orientation).fillCircle( - // x, - // y, - // 10 - // ); - if (distance <= mins[orientation]) { - mins[orientation] = distance; - indices[orientation] = i - 1; - } - }, - start, - end - ); // by default correct orientation (0) wins // but if other one is really closer, return other one - if (mins[1] < mins[0] / 10.0) { + if (mins[1] < mins[0] / 100.0) { return [1, indices[1]]; } else { return [0, indices[0]]; @@ -589,8 +645,8 @@ class Point { this.lon = lon; this.lat = lat; } - coordinates(current_position, cos_direction, sin_direction) { - let translated = this.minus(current_position).times(40000.0); + coordinates(current_position, cos_direction, sin_direction, scale_factor) { + let translated = this.minus(current_position).times(scale_factor); let rotated_x = translated.lon * cos_direction - translated.lat * sin_direction; let rotated_y = @@ -609,8 +665,9 @@ class Point { return new Point(this.lon + other_point.lon, this.lat + other_point.lat); } length_squared(other_point) { - let d = this.minus(other_point); - return d.lon * d.lon + d.lat * d.lat; + let londiff = this.lon - other_point.lon; + let latdiff = this.lat - other_point.lat; + return londiff * londiff + latdiff * latdiff; } times(scalar) { return new Point(this.lon * scalar, this.lat * scalar); @@ -639,10 +696,15 @@ class Point { fake_distance(other_point) { return Math.sqrt(this.length_squared(other_point)); } + // return closest point from 'this' on [v,w] segment. + // since this function is critical we inline all code here. closest_segment_point(v, w) { // from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment // Return minimum distance between line segment vw and point p - let l2 = v.length_squared(w); // i.e. |w-v|^2 - avoid a sqrt + let segment_londiff = w.lon - v.lon; + let segment_latdiff = w.lat - v.lat; + let l2 = + segment_londiff * segment_londiff + segment_latdiff * segment_latdiff; // i.e. |w-v|^2 - avoid a sqrt if (l2 == 0.0) { return v; // v == w case } @@ -650,41 +712,48 @@ class Point { // We find projection of point p onto the line. // It falls where t = [(p-v) . (w-v)] / |w-v|^2 // We clamp t from [0,1] to handle points outside the segment vw. - let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2)); - return v.plus(w.minus(v).times(t)); // Projection falls on the segment - } - distance_to_segment(v, w) { - let projection = this.closest_segment_point(v, w); - return this.distance(projection); - } - fake_distance_to_segment(v, w) { - let projection = this.closest_segment_point(v, w); - return this.fake_distance(projection); + + // let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2)); //inlined below + let start_londiff = this.lon - v.lon; + let start_latdiff = this.lat - v.lat; + let t = + (start_londiff * segment_londiff + start_latdiff * segment_latdiff) / l2; + if (t < 0) { + t = 0; + } else { + if (t > 1) { + t = 1; + } + } + let lon = v.lon + segment_londiff * t; + let lat = v.lat + segment_latdiff * t; + return new Point(lon, lat); } } -Bangle.loadWidgets(); - let fake_gps_point = 0.0; function simulate_gps(status) { + // let's keep the screen on in simulations + Bangle.setLCDTimeout(0); + Bangle.setLCDPower(1); if (fake_gps_point > status.path.len - 1) { return; } let point_index = Math.floor(fake_gps_point); - if (point_index >= status.path.len) { + if (point_index >= status.path.len / 2 - 1) { return; } - //let p1 = status.path.point(0); - //let n = status.path.len; - //let p2 = status.path.point(n - 1); - let p1 = status.path.point(point_index); - let p2 = status.path.point(point_index + 1); + let p1 = status.path.point(2 * point_index); // use these to approximately follow path + let p2 = status.path.point(2 * (point_index + 1)); + //let p1 = status.path.point(point_index); // use these to strictly follow path + //let p2 = status.path.point(point_index + 1); let alpha = fake_gps_point - point_index; let pos = p1.times(1 - alpha).plus(p2.times(alpha)); let old_pos = status.position; fake_gps_point += 0.05; // advance simulation + // status.update_position(new Point(1, 1), null); // uncomment to be always lost status.update_position(pos, null); } @@ -706,21 +775,28 @@ function start(fn) { E.showMenu(); console.log("loading", fn); - // let path = new Path(load_gpx("test.gpx")); - let path = new Path(load_gpc(fn)); + load_gpc(fn); +} + +function start_gipy(filename, path_data) { + console.log("starting"); + let path = new Path(path_data); let status = new Status(path); if (simulated) { + status.starting_time = getTime(); status.position = new Point(status.path.point(0)); setInterval(simulate_gps, 500, status); } else { - // let's display start while waiting for gps signal - let p1 = status.path.point(0); - let p2 = status.path.point(1); - let diff = p2.minus(p1); - let direction = Math.atan2(diff.lat, diff.lon); + // let's display splash screen while waiting for gps signal + g.clear(); + g.drawImage(splashscreen, 0, 0); + g.setFont("6x8:2") + .setFontAlign(-1, -1, 0) + .setColor(0xf800) + .drawString(filename, 0, g.getHeight() - 30); + Bangle.setLocked(false); - status.update_position(p1, direction); let frame = 0; let set_coordinates = function (data) { @@ -731,6 +807,10 @@ function start(fn) { !isNaN(data.lon) && (data.lat != 0.0 || data.lon != 0.0); if (valid_coordinates) { + if (status.starting_time === null) { + status.starting_time = getTime(); + Bangle.loadWidgets(); // i don't know why i cannot load them at start : they would display on splash screen + } status.update_position(new Point(data.lon, data.lat), null); } let gps_status_color; diff --git a/apps/gipy/interface.html b/apps/gipy/interface.html index a1c405ed7..552e7be17 100644 --- a/apps/gipy/interface.html +++ b/apps/gipy/interface.html @@ -182,12 +182,21 @@ document document .getElementById("upload") .addEventListener('click', function() { + document.getElementById('upload').disabled = true; status.innerHTML = "uploading file"; console.log("uploading"); let gpc_string = vec_to_string(gpc_content); Util.writeStorage(gpc_filename + ".gpc", gpc_string, () => { - status.innerHTML = `${gpc_filename}.gpc uploaded`; - console.log("DONE"); + status.innerHTML = "Checking upload"; + Util.readStorage(gpc_filename + ".gpc", uploaded_content => { + if (uploaded_content == gpc_string) { + status.innerHTML = `${gpc_filename}.gpc uploaded`; + console.log("DONE"); + } else { + status.innerHTML = "Upload FAILED"; + document.getElementById('upload').disabled = false; + } + }); }); }); diff --git a/apps/gipy/legend.png b/apps/gipy/legend.png new file mode 100644 index 000000000..9040f6df8 Binary files /dev/null and b/apps/gipy/legend.png differ diff --git a/apps/gipy/lost.png b/apps/gipy/lost.png new file mode 100644 index 000000000..348eaed8e Binary files /dev/null and b/apps/gipy/lost.png differ diff --git a/apps/gipy/metadata.json b/apps/gipy/metadata.json index 2d06a7c2d..97d18f5fe 100644 --- a/apps/gipy/metadata.json +++ b/apps/gipy/metadata.json @@ -2,13 +2,13 @@ "id": "gipy", "name": "Gipy", "shortName": "Gipy", - "version": "0.15", - "description": "Follow gpx files", + "version": "0.16", + "description": "Follow gpx files using the gps. Don't get lost in your bike trips and hikes.", "allow_emulator":false, "icon": "gipy.png", "type": "app", "tags": "tool,outdoors,gps", - "screenshots": [], + "screenshots": [{"url":"splash.png"}], "supports": ["BANGLEJS2"], "readme": "README.md", "interface": "interface.html", diff --git a/apps/gipy/pkg/gpconv.d.ts b/apps/gipy/pkg/gpconv.d.ts index ecffa7b69..2bb57f651 100644 --- a/apps/gipy/pkg/gpconv.d.ts +++ b/apps/gipy/pkg/gpconv.d.ts @@ -46,11 +46,11 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number) => number; readonly __wbindgen_export_2: WebAssembly.Table; - readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd: (a: number, b: number, c: number) => void; + readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e: (a: number, b: number, c: number) => void; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; readonly __wbindgen_free: (a: number, b: number) => void; readonly __wbindgen_exn_store: (a: number) => void; - readonly wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476: (a: number, b: number, c: number, d: number) => void; + readonly wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240: (a: number, b: number, c: number, d: number) => void; } export type SyncInitInput = BufferSource | WebAssembly.Module; diff --git a/apps/gipy/pkg/gpconv.js b/apps/gipy/pkg/gpconv.js index 97b37e340..b9271ad4b 100644 --- a/apps/gipy/pkg/gpconv.js +++ b/apps/gipy/pkg/gpconv.js @@ -98,14 +98,6 @@ function getInt32Memory0() { return cachedInt32Memory0; } -const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); - -cachedTextDecoder.decode(); - -function getStringFromWasm0(ptr, len) { - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); -} - function addHeapObject(obj) { if (heap_next === heap.length) heap.push(heap.length + 1); const idx = heap_next; @@ -115,6 +107,14 @@ function addHeapObject(obj) { return idx; } +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + function debugString(val) { // primitive types const type = typeof val; @@ -205,7 +205,7 @@ function makeMutClosure(arg0, arg1, dtor, f) { return real; } function __wbg_adapter_24(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(arg0, arg1, addHeapObject(arg2)); + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e(arg0, arg1, addHeapObject(arg2)); } function _assertClass(instance, klass) { @@ -310,7 +310,7 @@ function handleError(f, args) { } } function __wbg_adapter_69(arg0, arg1, arg2, arg3) { - wasm.wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); + wasm.wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); } /** @@ -371,13 +371,13 @@ async function load(module, imports) { function getImports() { const imports = {}; imports.wbg = {}; - imports.wbg.__wbindgen_object_drop_ref = function(arg0) { - takeObject(arg0); - }; imports.wbg.__wbg_gpcsvg_new = function(arg0) { const ret = GpcSvg.__wrap(arg0); return addHeapObject(ret); }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { const obj = getObject(arg1); const ret = typeof(obj) === 'string' ? obj : undefined; @@ -386,15 +386,15 @@ function getImports() { getInt32Memory0()[arg0 / 4 + 1] = len0; getInt32Memory0()[arg0 / 4 + 0] = ptr0; }; - imports.wbg.__wbindgen_string_new = function(arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; imports.wbg.__wbindgen_object_clone_ref = function(arg0) { const ret = getObject(arg0); return addHeapObject(ret); }; - imports.wbg.__wbg_fetch_386f87a3ebf5003c = function(arg0) { + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbg_fetch_3894579f6e2af3be = function(arg0) { const ret = fetch(getObject(arg0)); return addHeapObject(ret); }; @@ -558,10 +558,6 @@ function getImports() { const ret = new Uint8Array(getObject(arg0)); return addHeapObject(ret); }; - imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) { - const ret = JSON.stringify(getObject(arg0)); - return addHeapObject(ret); - }, arguments) }; imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) { const ret = Reflect.get(getObject(arg0), getObject(arg1)); return addHeapObject(ret); @@ -574,6 +570,10 @@ function getImports() { const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); return ret; }, arguments) }; + imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) { + const ret = JSON.stringify(getObject(arg0)); + return addHeapObject(ret); + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(getObject(arg1)); const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -588,8 +588,8 @@ function getImports() { const ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper947 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 147, __wbg_adapter_24); + imports.wbg.__wbindgen_closure_wrapper929 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 143, __wbg_adapter_24); return addHeapObject(ret); }; diff --git a/apps/gipy/pkg/gpconv_bg.wasm b/apps/gipy/pkg/gpconv_bg.wasm index edeb4eb59..245afc20c 100644 Binary files a/apps/gipy/pkg/gpconv_bg.wasm and b/apps/gipy/pkg/gpconv_bg.wasm differ diff --git a/apps/gipy/pkg/gpconv_bg.wasm.d.ts b/apps/gipy/pkg/gpconv_bg.wasm.d.ts index 6bc5d3719..cb912f3de 100644 --- a/apps/gipy/pkg/gpconv_bg.wasm.d.ts +++ b/apps/gipy/pkg/gpconv_bg.wasm.d.ts @@ -9,8 +9,8 @@ export function convert_gpx_strings(a: number, b: number, c: number, d: number, export function __wbindgen_malloc(a: number): number; export function __wbindgen_realloc(a: number, b: number, c: number): number; export const __wbindgen_export_2: WebAssembly.Table; -export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(a: number, b: number, c: number): void; +export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h317df853f2d4653e(a: number, b: number, c: number): void; export function __wbindgen_add_to_stack_pointer(a: number): number; export function __wbindgen_free(a: number, b: number): void; export function __wbindgen_exn_store(a: number): void; -export function wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(a: number, b: number, c: number, d: number): void; +export function wasm_bindgen__convert__closures__invoke2_mut__h573cb80e0bf72240(a: number, b: number, c: number, d: number): void; diff --git a/apps/gipy/screenshot1.png b/apps/gipy/screenshot1.png deleted file mode 100644 index c7c45fa3b..000000000 Binary files a/apps/gipy/screenshot1.png and /dev/null differ diff --git a/apps/gipy/screenshot2.png b/apps/gipy/screenshot2.png deleted file mode 100644 index ed61eb795..000000000 Binary files a/apps/gipy/screenshot2.png and /dev/null differ diff --git a/apps/gipy/splash.png b/apps/gipy/splash.png new file mode 100644 index 000000000..56d4de06b Binary files /dev/null and b/apps/gipy/splash.png differ diff --git a/apps/grocery/ChangeLog b/apps/grocery/ChangeLog index 906046782..294dab597 100644 --- a/apps/grocery/ChangeLog +++ b/apps/grocery/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Refactor code to store grocery list in separate file +0.03: Sort selected items to bottom and enable Widgets diff --git a/apps/grocery/app.js b/apps/grocery/app.js index 481efc3d9..a68f53010 100644 --- a/apps/grocery/app.js +++ b/apps/grocery/app.js @@ -1,5 +1,6 @@ var filename = 'grocery_list.json'; var settings = require("Storage").readJSON(filename,1)|| { products: [] }; +let menu; function updateSettings() { require("Storage").writeJSON(filename, settings); @@ -11,19 +12,32 @@ function twoChat(n){ return ''+n; } -const mainMenu = settings.products.reduce(function(m, p, i){ - const name = twoChat(p.quantity)+' '+p.name; - m[name] = { - value: p.ok, - format: v => v?'[x]':'[ ]', - onchange: v => { - settings.products[i].ok = v; - updateSettings(); - } - }; - return m; -}, { - '': { 'title': 'Grocery list' } -}); +function sortMenu() { + mainMenu.sort((a,b) => { + const byValue = a.value-b.value; + return byValue !== 0 ? byValue : a.index-b.index; + }); + if (menu) { + menu.draw(); + } +} + +const mainMenu = settings.products.map((p,i) => ({ + title: twoChat(p.quantity)+' '+p.name, + value: p.ok, + format: v => v?'[x]':'[ ]', + index: i, + onchange: v => { + settings.products[i].ok = v; + updateSettings(); + sortMenu(); + } +})); +sortMenu(); + +mainMenu[''] = { 'title': 'Grocery list' }; mainMenu['< Back'] = ()=>{load();}; -E.showMenu(mainMenu); + +Bangle.loadWidgets(); +menu = E.showMenu(mainMenu); +Bangle.drawWidgets(); diff --git a/apps/grocery/interface.html b/apps/grocery/interface.html new file mode 100644 index 000000000..65528c8e6 --- /dev/null +++ b/apps/grocery/interface.html @@ -0,0 +1,138 @@ + +
+ + + + +name | +quantity | +done | +actions | +
---|