diff --git a/README.md b/README.md index 1058787bb..fed13a358 100644 --- a/README.md +++ b/README.md @@ -282,8 +282,11 @@ and which gives information about the app for the Launcher. "dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above) "dependencies" : { "messages":"app" } // optional, depend on a specific app ID // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' - "dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require' + "dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require' - see provides_modules + "dependencies" : { "message":"widget" } // optional, depend on a specific type of widget - see provides_widgets "provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require' + "provides_widgets" : ["battery"] // optional, this app provides a type of widget - 'alarm/battery/bluetooth/pedometer/message' + "default" : true, // set if an app is the default implementer of something (a widget/module/etc) "readme": "README.md", // if supplied, a link to a markdown-style text file // that contains more information about this app (usage, etc) // A 'Read more...' link will be added under the app diff --git a/apps/aiclock/ChangeLog b/apps/aiclock/ChangeLog index 96b389f6e..fb5aed3e3 100644 --- a/apps/aiclock/ChangeLog +++ b/apps/aiclock/ChangeLog @@ -2,3 +2,4 @@ 0.02: Design improvements and fixes. 0.03: Indicate battery level through line occurrence. 0.04: Use widget_utils module. +0.05: Support for clkinfo. \ No newline at end of file diff --git a/apps/aiclock/README.md b/apps/aiclock/README.md index 9e23de3a6..31dd5aa29 100644 --- a/apps/aiclock/README.md +++ b/apps/aiclock/README.md @@ -10,7 +10,9 @@ 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 ;) +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. ![](impl.png) diff --git a/apps/aiclock/aiclock.app.js b/apps/aiclock/aiclock.app.js index 5d4e98fcd..b5bb30b9d 100644 --- a/apps/aiclock/aiclock.app.js +++ b/apps/aiclock/aiclock.app.js @@ -1,6 +1,14 @@ -/** +/************************************************ * AI Clock */ + const storage = require('Storage'); + const clock_info = require("clock_info"); + + + + /************************************************ + * Assets + */ require("Font7x11Numeric7Seg").add(Graphics); Graphics.prototype.setFontGochiHand = function(scale) { // Actual height 27 (29 - 3) @@ -13,7 +21,7 @@ Graphics.prototype.setFontGochiHand = function(scale) { return this; } -/* +/************************************************ * Set some important constants such as width, height and center */ var W = g.getWidth(),R=W/2; @@ -21,6 +29,120 @@ var H = g.getHeight(); var cx = W/2; var cy = H/2; var drawTimeout; +var lock_input = false; + + +/************************************************ + * 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; + } + + // Set draw functions for each item + menu.forEach((menuItm, x) => { + menuItm.items.forEach((item, y) => { + function drawItem() { + // For the clock, we have a special case, as we don't wanna redraw + // immediately when something changes. Instead, we update data each minute + // to save some battery etc. Therefore, we hide (and disable the listener) + // immedeately after redraw... + item.hide(); + + // After drawing the item, we enable inputs again... + lock_input = false; + + var info = item.get(); + drawMenuItem(info.text, info.img); + } + + item.on('redraw', drawItem); + }) + }); + + + 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/ @@ -76,7 +198,50 @@ function toAngle(a){ return a } + +function drawMenuItem(text, image){ + if(text == null){ + drawTime(); + return + } + // image = atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA=="); + + 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 + drawDigits(); + + // And now the analog time var drawHourHand = g.drawRotRect.bind(g,8,12,R-38); var drawMinuteHand = g.drawRotRect.bind(g,6,12,R-12 ); @@ -90,13 +255,6 @@ function drawTime(){ h += date.getMinutes()/60.0; h = parseInt(h*360/12); - // Draw minute and hour bg - g.setColor(g.theme.bg); - drawHourHand(toAngle(h-3)); - drawHourHand(toAngle(h+3)); - drawMinuteHand(toAngle(m-2)); - drawMinuteHand(toAngle(m+3)); - // Draw minute and hour fg g.setColor(g.theme.fg); drawHourHand(h); @@ -104,28 +262,6 @@ function drawTime(){ } - -function drawDate(){ - var date = new Date(); - g.setFontAlign(0,0); - g.setFontGochiHand(); - - var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2); - var w = g.stringWidth(text); - g.setColor(g.theme.bg); - g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12); - - g.setColor(g.theme.fg); - // Draw right line as designed by stable diffusion - g.drawLine(cx+w/2+5, 20, cx+w/2+5, 40+12); - g.drawLine(cx+w/2+6, 20, cx+w/2+6, 40+12); - g.drawLine(cx+w/2+7, 20, cx+w/2+7, 40+12); - - // And finally the text - g.drawString(text, cx, 40); -} - - function drawDigits(){ var date = new Date(); @@ -156,20 +292,35 @@ function drawDigits(){ } +function drawDate(){ + 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 + lock_input = true; + var item = menuEntry.items[settings.menuPosY-1]; + item.show(); +} + + + + function draw(){ // Queue draw in one minute queueDraw(); - g.reset(); g.clearRect(0, 0, g.getWidth(), g.getHeight()); - g.setColor(1,1,1); + drawBackground(); drawDate(); - drawDigits(); - drawTime(); drawCircle(Bangle.isLocked()); } @@ -190,6 +341,68 @@ 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(lock_input){ + return; + } + + 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... + } +}); + /* * Some helpers @@ -203,7 +416,6 @@ function queueDraw() { } - /* * Lets start widgets, listen for btn etc. */ @@ -216,6 +428,7 @@ Bangle.loadWidgets(); * area to the top bar doesn't get cleared. */ require('widget_utils').hide(); + // Clear the screen once, at startup and draw clock g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear(); draw(); diff --git a/apps/aiclock/impl.png b/apps/aiclock/impl.png index 92374b680..8a9e43e2d 100644 Binary files a/apps/aiclock/impl.png and b/apps/aiclock/impl.png differ diff --git a/apps/aiclock/impl_2.png b/apps/aiclock/impl_2.png new file mode 100644 index 000000000..be3519a4b Binary files /dev/null and b/apps/aiclock/impl_2.png differ diff --git a/apps/aiclock/impl_3.png b/apps/aiclock/impl_3.png new file mode 100644 index 000000000..c2a036d14 Binary files /dev/null and b/apps/aiclock/impl_3.png differ diff --git a/apps/aiclock/metadata.json b/apps/aiclock/metadata.json index 0da23e8ca..1dcda427f 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.04", + "version":"0.05", "readme": "README.md", "supports": ["BANGLEJS2"], "description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.", @@ -11,7 +11,9 @@ "tags": "clock", "screenshots": [ {"url":"orig.png"}, - {"url":"impl.png"} + {"url":"impl.png"}, + {"url":"impl_2.png"}, + {"url":"impl_3.png"} ], "storage": [ {"name":"aiclock.app.js","url":"aiclock.app.js"}, diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 6ce6147ca..9994d33d9 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -36,4 +36,4 @@ 0.33: Allow hiding timers&alarms 0.34: Add "Confirm" option to alarm/timer edit menus 0.35: Add automatic translation of more strings - +0.36: alarm widget moved out of app diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json index a02985851..dbf090774 100644 --- a/apps/alarm/metadata.json +++ b/apps/alarm/metadata.json @@ -2,17 +2,16 @@ "id": "alarm", "name": "Alarms & Timers", "shortName": "Alarms", - "version": "0.35", + "version": "0.36", "description": "Set alarms and timers on your Bangle", "icon": "app.png", - "tags": "tool,alarm,widget", + "tags": "tool,alarm", "supports": [ "BANGLEJS", "BANGLEJS2" ], "readme": "README.md", - "dependencies": { "scheduler":"type" }, + "dependencies": { "scheduler":"type", "alarm":"widget" }, "storage": [ { "name": "alarm.app.js", "url": "app.js" }, - { "name": "alarm.img", "url": "app-icon.js", "evaluate": true }, - { "name": "alarm.wid.js", "url": "widget.js" } + { "name": "alarm.img", "url": "app-icon.js", "evaluate": true } ], "screenshots": [ { "url": "screenshot-1.png" }, diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index e4a3e9de0..86dbdb649 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -16,4 +16,5 @@ 0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152) 0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge 0.18: Use new message library - If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged) \ No newline at end of file + If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged) +0.19: Add automatic translation for a couple of strings. diff --git a/apps/android/boot.js b/apps/android/boot.js index 8e75241e7..e1e5b028b 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -57,7 +57,7 @@ t:event.cmd=="incoming"?"add":"remove", id:"call", src:"Phone", positive:true, negative:true, - title:event.name||"Call", body:"Incoming call\n"+event.number}); + title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number}); require("messages").pushMessage(event); }, "alarm" : function() { @@ -148,7 +148,7 @@ Bangle.http = (url,options)=>{ options = options||{}; if (!NRF.getSecurityStatus().connected) - return Promise.reject("Not connected to Bluetooth"); + return Promise.reject(/*LANG*/"Not connected to Bluetooth"); if (Bangle.httpRequest === undefined) Bangle.httpRequest={}; if (options.id === undefined) { diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 45c08a75a..d5a45edb7 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.18", + "version": "0.19", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/astrocalc/ChangeLog b/apps/astrocalc/ChangeLog index 60ef5da0a..746ab2162 100644 --- a/apps/astrocalc/ChangeLog +++ b/apps/astrocalc/ChangeLog @@ -1,2 +1,3 @@ 0.01: Create astrocalc app 0.02: Store last GPS lock, can be used instead of waiting for new GPS on start +0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/astrocalc/astrocalc-app.js b/apps/astrocalc/astrocalc-app.js index 4e7aa0b40..46fb855ec 100644 --- a/apps/astrocalc/astrocalc-app.js +++ b/apps/astrocalc/astrocalc-app.js @@ -9,7 +9,7 @@ * Calculate the Sun and Moon positions based on watch GPS and display graphically */ -const SunCalc = require("suncalc.js"); +const SunCalc = require("suncalc"); // from modules folder const storage = require("Storage"); const LAST_GPS_FILE = "astrocalc.gps.json"; let lastGPS = (storage.readJSON(LAST_GPS_FILE, 1) || null); @@ -385,4 +385,4 @@ function init() { } let m; -init(); \ No newline at end of file +init(); diff --git a/apps/astrocalc/metadata.json b/apps/astrocalc/metadata.json index 384c7fa1e..d77474700 100644 --- a/apps/astrocalc/metadata.json +++ b/apps/astrocalc/metadata.json @@ -1,7 +1,7 @@ { "id": "astrocalc", "name": "Astrocalc", - "version": "0.02", + "version": "0.03", "description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.", "icon": "astrocalc.png", "tags": "app,sun,moon,cycles,tool,outdoors", @@ -9,7 +9,6 @@ "allow_emulator": true, "storage": [ {"name":"astrocalc.app.js","url":"astrocalc-app.js"}, - {"name":"suncalc.js","url":"suncalc.js"}, {"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true}, {"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true}, {"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true}, diff --git a/apps/astrocalc/suncalc.js b/apps/astrocalc/suncalc.js deleted file mode 100644 index e2beaedca..000000000 --- a/apps/astrocalc/suncalc.js +++ /dev/null @@ -1,328 +0,0 @@ -/* - (c) 2011-2015, Vladimir Agafonkin - SunCalc is a JavaScript library for calculating sun/moon position and light phases. - https://github.com/mourner/suncalc -*/ - -(function () { 'use strict'; - - // shortcuts for easier to read formulas - - var PI = Math.PI, - sin = Math.sin, - cos = Math.cos, - tan = Math.tan, - asin = Math.asin, - atan = Math.atan2, - acos = Math.acos, - rad = PI / 180; - - // sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas - - - // date/time constants and conversions - - var dayMs = 1000 * 60 * 60 * 24, - J1970 = 2440588, - J2000 = 2451545; - - function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } - function fromJulian(j) { return (j + 0.5 - J1970) * dayMs; } - function toDays(date) { return toJulian(date) - J2000; } - - - // general calculations for position - - var e = rad * 23.4397; // obliquity of the Earth - - function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } - function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } - - function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); } - function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } - - function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } - - function astroRefraction(h) { - if (h < 0) // the following formula works for positive altitudes only. - h = 0; // if h = -0.08901179 a div/0 would occur. - - // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: - return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); - } - - // general sun calculations - - function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } - - function eclipticLongitude(M) { - - var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center - P = rad * 102.9372; // perihelion of the Earth - - return M + C + P + PI; - } - - function sunCoords(d) { - - var M = solarMeanAnomaly(d), - L = eclipticLongitude(M); - - return { - dec: declination(L, 0), - ra: rightAscension(L, 0) - }; - } - - - var SunCalc = {}; - - - // calculates sun position for a given date and latitude/longitude - - SunCalc.getPosition = function (date, lat, lng) { - - var lw = rad * -lng, - phi = rad * lat, - d = toDays(date), - - c = sunCoords(d), - H = siderealTime(d, lw) - c.ra; - - return { - azimuth: azimuth(H, phi, c.dec), - altitude: altitude(H, phi, c.dec) - }; - }; - - - // sun times configuration (angle, morning name, evening name) - - var times = SunCalc.times = [ - [-0.833, 'sunrise', 'sunset' ], - [ -0.3, 'sunriseEnd', 'sunsetStart' ], - [ -6, 'dawn', 'dusk' ], - [ -12, 'nauticalDawn', 'nauticalDusk'], - [ -18, 'nightEnd', 'night' ], - [ 6, 'goldenHourEnd', 'goldenHour' ] - ]; - - // adds a custom time to the times config - - SunCalc.addTime = function (angle, riseName, setName) { - times.push([angle, riseName, setName]); - }; - - - // calculations for sun times - - var J0 = 0.0009; - - function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } - - function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } - function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } - - function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } - function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } - - // returns set time for the given sun altitude - function getSetJ(h, lw, phi, dec, n, M, L) { - - var w = hourAngle(h, phi, dec), - a = approxTransit(w, lw, n); - return solarTransitJ(a, M, L); - } - - - // calculates sun times for a given date, latitude/longitude, and, optionally, - // the observer height (in meters) relative to the horizon - - SunCalc.getTimes = function (date, lat, lng, height) { - - height = height || 0; - - var lw = rad * -lng, - phi = rad * lat, - - dh = observerAngle(height), - - d = toDays(date), - n = julianCycle(d, lw), - ds = approxTransit(0, lw, n), - - M = solarMeanAnomaly(ds), - L = eclipticLongitude(M), - dec = declination(L, 0), - - Jnoon = solarTransitJ(ds, M, L), - - i, len, time, h0, Jset, Jrise; - - - var result = { - solarNoon: new Date(fromJulian(Jnoon)), - nadir: new Date(fromJulian(Jnoon - 0.5)) - }; - - for (i = 0, len = times.length; i < len; i += 1) { - time = times[i]; - h0 = (time[0] + dh) * rad; - - Jset = getSetJ(h0, lw, phi, dec, n, M, L); - Jrise = Jnoon - (Jset - Jnoon); - - result[time[1]] = new Date(fromJulian(Jrise) - (dayMs / 2)); - result[time[2]] = new Date(fromJulian(Jset) + (dayMs / 2)); - } - - return result; - }; - - - // moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas - - function moonCoords(d) { // geocentric ecliptic coordinates of the moon - - var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude - M = rad * (134.963 + 13.064993 * d), // mean anomaly - F = rad * (93.272 + 13.229350 * d), // mean distance - - l = L + rad * 6.289 * sin(M), // longitude - b = rad * 5.128 * sin(F), // latitude - dt = 385001 - 20905 * cos(M); // distance to the moon in km - - return { - ra: rightAscension(l, b), - dec: declination(l, b), - dist: dt - }; - } - - SunCalc.getMoonPosition = function (date, lat, lng) { - - var lw = rad * -lng, - phi = rad * lat, - d = toDays(date), - - c = moonCoords(d), - H = siderealTime(d, lw) - c.ra, - h = altitude(H, phi, c.dec), - // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H)); - - h = h + astroRefraction(h); // altitude correction for refraction - - return { - azimuth: azimuth(H, phi, c.dec), - altitude: h, - distance: c.dist, - parallacticAngle: pa - }; - }; - - - // calculations for illumination parameters of the moon, - // based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and - // Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - - // Function updated from gist: https://gist.github.com/endel/dfe6bb2fbe679781948c - - SunCalc.getMoonIllumination = function (date) { - let month = date.getMonth(); - let year = date.getFullYear(); - let day = date.getDate(); - - let c = 0; - let e = 0; - let jd = 0; - let b = 0; - - if (month < 3) { - year--; - month += 12; - } - - ++month; - c = 365.25 * year; - e = 30.6 * month; - jd = c + e + day - 694039.09; // jd is total days elapsed - jd /= 29.5305882; // divide by the moon cycle - b = parseInt(jd); // int(jd) -> b, take integer part of jd - jd -= b; // subtract integer part to leave fractional part of original jd - b = Math.round(jd * 8); // scale fraction from 0-8 and round - - if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0 - - return {phase: b}; - }; - - - function hoursLater(date, h) { - return new Date(date.valueOf() + h * dayMs / 24); - } - - // calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article - - SunCalc.getMoonTimes = function (date, lat, lng, inUTC) { - var t = date; - if (inUTC) t.setUTCHours(0, 0, 0, 0); - else t.setHours(0, 0, 0, 0); - - var hc = 0.133 * rad, - h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc, - h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx; - - // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) - for (var i = 1; i <= 24; i += 2) { - h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc; - h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc; - - a = (h0 + h2) / 2 - h1; - b = (h2 - h0) / 2; - xe = -b / (2 * a); - ye = (a * xe + b) * xe + h1; - d = b * b - 4 * a * h1; - roots = 0; - - if (d >= 0) { - dx = Math.sqrt(d) / (Math.abs(a) * 2); - x1 = xe - dx; - x2 = xe + dx; - if (Math.abs(x1) <= 1) roots++; - if (Math.abs(x2) <= 1) roots++; - if (x1 < -1) x1 = x2; - } - - if (roots === 1) { - if (h0 < 0) rise = i + x1; - else set = i + x1; - - } else if (roots === 2) { - rise = i + (ye < 0 ? x2 : x1); - set = i + (ye < 0 ? x1 : x2); - } - - if (rise && set) break; - - h0 = h2; - } - - var result = {}; - - if (rise) result.rise = hoursLater(t, rise); - if (set) result.set = hoursLater(t, set); - - if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; - - return result; - }; - - - // export as Node module / AMD module / browser variable - if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc; - else if (typeof define === 'function' && define.amd) define(SunCalc); - else global.SunCalc = SunCalc; - -}()); diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 0eaf517d9..780d9cc7d 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -61,3 +61,6 @@ 0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass 0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock 0.54: Fix for invalid version comparison in polyfill +0.55: Add toLocalISOString polyfill for pre-2v15 firmwares + Only add boot info comments if settings.bootDebug was set + If settings.bootDebug is set, output timing for each section of .boot0 diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index ad58437ec..112dfeba8 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -1,16 +1,22 @@ /* This rewrites boot0.js based on current settings. If settings changed then it recalculates, but this avoids us doing a whole bunch of reconfiguration most of the time. */ +{ // execute in our own scope so we don't have to free variables... E.showMessage(/*LANG*/"Updating boot0..."); -var s = require('Storage').readJSON('setting.json',1)||{}; -var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 -var FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1")); -var boot = "", bootPost = ""; +let s = require('Storage').readJSON('setting.json',1)||{}; +const BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 +const FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1")); +const DEBUG = s.bootDebug; // we can set this to enable debugging output in boot0 +let boot = "", bootPost = ""; +if (DEBUG) { + boot += "var _tm=Date.now()\n"; + bootPost += "delete _tm;"; +} if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed - var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); + let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; } else { - var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT); + let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT); boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; } boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; @@ -88,14 +94,25 @@ delete Bangle.showClock; if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`; delete Bangle.load; if (!Bangle.load) boot += `Bangle.load = load;\n`; +let date = new Date(); +delete date.toLocalISOString; // toLocalISOString was only introduced in 2v15 +if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function() { + var o = this.getTimezoneOffset(); + var d = new Date(this.getTime() - o*60000); + var sign = o>0?"-":"+"; + o = Math.abs(o); + return d.toISOString().slice(0,-1)+sign+Math.floor(o/60).toString().padStart(2,0)+(o%60).toString().padStart(2,0); +};\n`; +// show timings +if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n` // ================================================== BOOT.JS // Append *.boot.js files // These could change bleServices/bleServiceOptions if needed -var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ - var getPriority = /.*\.(\d+)\.boot\.js$/; - var aPriority = a.match(getPriority); - var bPriority = b.match(getPriority); +let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ + let getPriority = /.*\.(\d+)\.boot\.js$/; + let aPriority = a.match(getPriority); + let bPriority = b.match(getPriority); if (aPriority && bPriority){ return parseInt(aPriority[1]) - parseInt(bPriority[1]); } else if (aPriority && !bPriority){ @@ -106,14 +123,16 @@ var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ return a==b ? 0 : (a>b ? 1 : -1); }); // precalculate file size -var fileSize = boot.length + bootPost.length; +let fileSize = boot.length + bootPost.length; bootFiles.forEach(bootFile=>{ // match the size of data we're adding below in bootFiles.forEach - fileSize += 2+bootFile.length+1+require('Storage').read(bootFile).length+2; + if (DEBUG) fileSize += 2+bootFile.length+1; // `//${bootFile}\n` comment + fileSize += require('Storage').read(bootFile).length+2; // boot code plus ";\n" + if (DEBUG) fileSize += 48+E.toJS(bootFile).length; // `print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n` }); // write file in chunks (so as not to use up all RAM) require('Storage').write('.boot0',boot,0,fileSize); -var fileOffset = boot.length; +let fileOffset = boot.length; bootFiles.forEach(bootFile=>{ // we add a semicolon so if the file is wrapped in (function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() @@ -122,16 +141,18 @@ bootFiles.forEach(bootFile=>{ // "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n"; // but we need to do this without ever loading everything into RAM as some // boot files seem to be getting pretty big now. - require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset); - fileOffset+=2+bootFile.length+1; - var bf = require('Storage').read(bootFile); + if (DEBUG) { + require('Storage').write('.boot0',`//${bootFile}\n`,fileOffset); + fileOffset+=2+bootFile.length+1; + } + let bf = require('Storage').read(bootFile); // we can't just write 'bf' in one go because at least in 2v13 and earlier // Espruino wants to read the whole file into RAM first, and on Bangle.js 1 // it can be too big (especially BTHRM). - var bflen = bf.length; - var bfoffset = 0; + let bflen = bf.length; + let bfoffset = 0; while (bflen) { - var bfchunk = Math.min(bflen, 2048); + let bfchunk = Math.min(bflen, 2048); require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset); fileOffset+=bfchunk; bfoffset+=bfchunk; @@ -139,15 +160,14 @@ bootFiles.forEach(bootFile=>{ } require('Storage').write('.boot0',";\n",fileOffset); fileOffset+=2; + if (DEBUG) { + require('Storage').write('.boot0',`print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset); + fileOffset += 48+E.toJS(bootFile).length + } }); require('Storage').write('.boot0',bootPost,fileOffset); - -delete boot; -delete bootPost; -delete bootFiles; -delete fileSize; -delete fileOffset; E.showMessage(/*LANG*/"Reloading..."); -eval(require('Storage').read('.boot0')); +} // .bootcde should be run automatically after if required, since // we normally get called automatically from '.boot0' +eval(require('Storage').read('.boot0')); diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json index bd39beb7f..455563a16 100644 --- a/apps/boot/metadata.json +++ b/apps/boot/metadata.json @@ -1,7 +1,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.54", + "version": "0.55", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 99cf0c670..000c5e3f8 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -40,3 +40,4 @@ 0.16: Set powerdownRequested correctly on BTHRM power on Additional logging on errors Add debug option for disabling active scanning +0.17: New GUI based on layout library diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js index fadf2a5d8..b07e7bd37 100644 --- a/apps/bthrm/bthrm.js +++ b/apps/bthrm/bthrm.js @@ -1,5 +1,5 @@ -var intervalInt; -var intervalBt; +const BPM_FONT_SIZE="19%"; +const VALUE_TIMEOUT=3000; var BODY_LOCS = { 0: 'Other', @@ -7,46 +7,119 @@ var BODY_LOCS = { 2: 'Wrist', 3: 'Finger', 4: 'Hand', - 5: 'Ear Lobe', + 5: 'Earlobe', 6: 'Foot', +}; + +var Layout = require("Layout"); + +function border(l,c) { + g.setColor(c).drawLine(l.x+l.w*0.05, l.y-4, l.x+l.w*0.95, l.y-4); } -function clear(y){ - g.reset(); - g.clearRect(0,y,g.getWidth(),y+75); -} - -function draw(y, type, event) { - clear(y); - var px = g.getWidth()/2; - var str = event.bpm + ""; - g.reset(); - g.setFontAlign(0,0); - g.setFontVector(40).drawString(str,px,y+20); - str = "Event: " + type; - if (type === "HRM") { - str += " Confidence: " + event.confidence; - g.setFontVector(12).drawString(str,px,y+40); - str = " Source: " + (event.src ? event.src : "internal"); - g.setFontVector(12).drawString(str,px,y+50); +function getRow(id, text, additionalInfo){ + let additional = []; + let l = { + type:"h", c: [ + { + type:"v", + width: g.getWidth()*0.4, + c: [ + {type:"txt", halign:1, font:"8%", label:text, id:id+"text" }, + {type:"txt", halign:1, font:BPM_FONT_SIZE, label:"--", id:id, bgCol: g.theme.bg } + ] + },{ + type:undefined, fillx:1 + },{ + type:"v", + valign: -1, + width: g.getWidth()*0.45, + c: additional + },{ + type:undefined, width:g.getWidth()*0.05 + } + ] + }; + for (let i of additionalInfo){ + let label = {type:"txt", font:"6x8", label:i + ":" }; + let value = {type:"txt", font:"6x8", label:"--", id:id + i }; + additional.push({type:"h", halign:-1, c:[ label, {type:undefined, fillx:1}, value ]}); } - if (type === "BTHRM"){ - if (event.battery) str += " Bat: " + (event.battery ? event.battery : ""); - g.setFontVector(12).drawString(str,px,y+40); - str= ""; - if (event.location) str += "Loc: " + BODY_LOCS[event.location]; - if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(","); - g.setFontVector(12).drawString(str,px,y+50); - str= ""; - if (event.contact) str += " Contact: " + event.contact; - if (event.energy) str += " kJoule: " + event.energy.toFixed(0); - g.setFontVector(12).drawString(str,px,y+60); + + return l; +} + +var layout = new Layout( { + type:"v", c: [ + getRow("int", "INT", ["Confidence"]), + getRow("agg", "HRM", ["Confidence", "Source"]), + getRow("bt", "BT", ["Battery","Location","Contact", "RR", "Energy"]), + { type:undefined, height:8 } //dummy to protect debug output + ] +}, { + lazy:true +}); + +var int,agg,bt; +var firstEvent = true; + +function draw(){ + if (!(int || agg || bt)) return; + + if (firstEvent) { + g.clearRect(Bangle.appRect); + firstEvent = false; + } + + let now = Date.now(); + + if (int && int.time > (now - VALUE_TIMEOUT)){ + layout.int.label = int.bpm; + if (!isNaN(int.confidence)) layout.intConfidence.label = int.confidence; + } else { + layout.int.label = "--"; + layout.intConfidence.label = "--"; + } + + if (agg && agg.time > (now - VALUE_TIMEOUT)){ + layout.agg.label = agg.bpm; + if (!isNaN(agg.confidence)) layout.aggConfidence.label = agg.confidence; + if (agg.src) layout.aggSource.label = agg.src; + } else { + layout.agg.label = "--"; + layout.aggConfidence.label = "--"; + layout.aggSource.label = "--"; + } + + if (bt && bt.time > (now - VALUE_TIMEOUT)) { + layout.bt.label = bt.bpm; + if (!isNaN(bt.battery)) layout.btBattery.label = bt.battery + "%"; + if (bt.rr) layout.btRR.label = bt.rr.join(","); + if (!isNaN(bt.location)) layout.btLocation.label = BODY_LOCS[bt.location]; + if (bt.contact !== undefined) layout.btContact.label = bt.contact ? "Yes":"No"; + if (!isNaN(bt.energy)) layout.btEnergy.label = bt.energy.toFixed(0) + "kJ"; + } else { + layout.bt.label = "--"; + layout.btBattery.label = "--"; + layout.btRR.label = "--"; + layout.btLocation.label = "--"; + layout.btContact.label = "--"; + layout.btEnergy.label = "--"; + } + + layout.update(); + layout.render(); + let first = true; + for (let c of layout.l.c){ + if (first) { + first = false; + continue; + } + if (c.type && c.type == "h") + border(c,g.theme.fg); } } -var firstEventBt = true; -var firstEventInt = true; - // This can get called for the boot code to show what's happening function showStatusInfo(txt) { @@ -57,41 +130,26 @@ function showStatusInfo(txt) { } function onBtHrm(e) { - if (firstEventBt){ - clear(24); - firstEventBt = false; - } - draw(100, "BTHRM", e); - if (e.bpm === 0){ - Bangle.buzz(100,0.2); - } - if (intervalBt){ - clearInterval(intervalBt); - } - intervalBt = setInterval(()=>{ - clear(100); - }, 2000); + bt = e; + bt.time = Date.now(); } -function onHrm(e) { - if (firstEventInt){ - clear(24); - firstEventInt = false; - } - draw(24, "HRM", e); - if (intervalInt){ - clearInterval(intervalInt); - } - intervalInt = setInterval(()=>{ - clear(24); - }, 2000); +function onInt(e) { + int = e; + int.time = Date.now(); } +function onAgg(e) { + agg = e; + agg.time = Date.now(); +} var settings = require('Storage').readJSON("bthrm.json", true) || {}; Bangle.on('BTHRM', onBtHrm); -Bangle.on('HRM', onHrm); +Bangle.on('HRM_int', onInt); +Bangle.on('HRM', onAgg); + Bangle.setHRMPower(1,'bthrm'); if (!(settings.startWithHrm)){ @@ -103,10 +161,11 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); if (Bangle.setBTHRMPower){ g.reset().setFont("6x8",2).setFontAlign(0,0); - g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 24); + g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2); + setInterval(draw, 1000); } else { g.reset().setFont("6x8",2).setFontAlign(0,0); - g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2 + 32); + g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2); } E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrm')); diff --git a/apps/bthrm/lib.js b/apps/bthrm/lib.js index f5e0e1e5b..a792167ca 100644 --- a/apps/bthrm/lib.js +++ b/apps/bthrm/lib.js @@ -553,14 +553,15 @@ exports.enable = () => { if (settings.replace){ // register a listener for original HRM events and emit as HRM_int - Bangle.on("HRM", (e) => { - e.modified = true; + Bangle.on("HRM", (o) => { + let e = Object.assign({},o); log("Emitting HRM_int", e); Bangle.emit("HRM_int", e); if (fallbackActive){ // if fallback to internal HRM is active, emit as HRM_R to which everyone listens - log("Emitting HRM_R(int)", e); - Bangle.emit("HRM_R", e); + o.src = "int"; + log("Emitting HRM_R(int)", o); + Bangle.emit("HRM_R", o); } }); @@ -576,6 +577,13 @@ exports.enable = () => { if (name == "HRM") o("HRM_R", cb); else o(name, cb); })(Bangle.removeListener); + } else { + Bangle.on("HRM", (o)=>{ + o.src = "int"; + let e = Object.assign({},o); + log("Emitting HRM_int", e); + Bangle.emit("HRM_int", e); + }); } Bangle.origSetHRMPower = Bangle.setHRMPower; diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index 18c34ea33..fea274ff3 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,9 +2,10 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.16", + "version": "0.17", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", + "screenshots": [{"url":"screen.png"}], "type": "app", "tags": "health,bluetooth,hrm,bthrm", "supports": ["BANGLEJS","BANGLEJS2"], diff --git a/apps/bthrm/screen.png b/apps/bthrm/screen.png new file mode 100644 index 000000000..6b6b85227 Binary files /dev/null and b/apps/bthrm/screen.png differ diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog index 8ec578fb6..cb5248e32 100644 --- a/apps/circlesclock/ChangeLog +++ b/apps/circlesclock/ChangeLog @@ -32,3 +32,9 @@ Use widget_utils if available 0.17: Load circles from clkinfo 0.18: Improved clkinfo handling and using it for the weather circle +0.19: Remove old code and fixing clkinfo handling (fix HRM and other items that change) + Remove settings for what is displayed and instead allow circles to be changed by swiping +0.20: Add much faster circle rendering (250ms -> 40ms) + Add fast load capability +0.21: Remade all icons without a palette for dark theme + Now re-adds widgets if they were hidden when fast-loading diff --git a/apps/circlesclock/README.md b/apps/circlesclock/README.md index 8c8fbe4ae..7f6a2585c 100644 --- a/apps/circlesclock/README.md +++ b/apps/circlesclock/README.md @@ -5,6 +5,7 @@ A clock with three or four circles for different data at the bottom in a probabl By default the time, date and day of week is shown. It can show the following information (this can be configured): + * Steps * Steps distance * Heart rate (automatically updates when screen is on and unlocked) @@ -14,15 +15,24 @@ It can show the following information (this can be configured): * Temperature inside circle * Condition as icon below circle * Big weather icon next to clock - * Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation)) - * Temperature, air pressure or altitude from internal pressure sensor + * Altitude from internal pressure sensor + * Active alarms (if `Alarm` app installed) + * Sunrise or sunset (if `Sunrise Clockinfo` app installed) +To change what is shown: -The color of each circle can be configured. The following colors are available: +* Unlock the watch +* Tap on the circle to change (a border is drawn around it) +* Swipe up/down to change the guage within the given group +* Swipe left/right to change the group (eg. between standard Bangle.js and Alarms/etc) + +Data is provided by ['Clock Info'](http://www.espruino.com/Bangle.js+Clock+Info) +so any apps that implement this feature can add extra information to be displayed. + +The color of each circle can be configured from `Settings -> Apps -> Circles Clock`. The following colors are available: * Basic colors (red, green, blue, yellow, magenta, cyan, black, white) * Color depending on value (green -> red, red -> green) - ## Screenshots ![Screenshot dark theme](screenshot-dark.png) ![Screenshot light theme](screenshot-light.png) @@ -38,5 +48,5 @@ The color of each circle can be configured. The following colors are available: Marco ([myxor](https://github.com/myxor)) ## Icons -Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from +Most of the icons are taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0 except the big weather icons which are from [icons8](https://icons8.com/icon/set/weather/small--static--black) diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js index ee6741398..444040ef0 100644 --- a/apps/circlesclock/app.js +++ b/apps/circlesclock/app.js @@ -1,6 +1,3 @@ -let clock_info = require("clock_info"); -let locale = require("locale"); -let storage = require("Storage"); Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) { // Actual height 39 (40 - 2) this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16)); @@ -13,12 +10,16 @@ Graphics.prototype.setFontRobotoRegular21 = function(scale) { return this; }; +{ +let clock_info = require("clock_info"); +let locale = require("locale"); +let storage = require("Storage"); + let SETTINGS_FILE = "circlesclock.json"; let settings = Object.assign( storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON(SETTINGS_FILE, true) || {} ); - //TODO deprecate this (and perhaps use in the clkinfo module) // Load step goal from health app and pedometer widget as fallback if (settings.stepGoal == undefined) { @@ -31,20 +32,10 @@ if (settings.stepGoal == undefined) { } } -let timerHrm; //TODO deprecate this let drawTimeout; - -/* - * Read location from myLocation app - */ -function getLocation() { - return storage.readJSON("mylocation.json", 1) || undefined; -} -let location = getLocation(); - -let showWidgets = settings.showWidgets || false; -let circleCount = settings.circleCount || 3; -let showBigWeather = settings.showBigWeather || false; +const showWidgets = settings.showWidgets || false; +const circleCount = settings.circleCount || 3; +const showBigWeather = settings.showBigWeather || false; let hrtValue; //TODO deprecate this let now = Math.round(new Date().getTime() / 1000); @@ -52,11 +43,6 @@ let now = Math.round(new Date().getTime() / 1000); // layout values: let colorFg = g.theme.dark ? '#fff' : '#000'; let colorBg = g.theme.dark ? '#000' : '#fff'; -let colorGrey = '#808080'; -let colorRed = '#ff0000'; -let colorGreen = '#008000'; -let colorBlue = '#0000ff'; -let colorYellow = '#ffff00'; let widgetOffset = showWidgets ? 24 : 0; let dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date let h = g.getHeight() - widgetOffset; @@ -64,7 +50,7 @@ let w = g.getWidth(); let hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset; let h1 = Math.round(1 * h / 5 - hOffset); let h2 = Math.round(3 * h / 5 - hOffset); -let h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position +let h3 = Math.round(8 * h / 8 - hOffset - 3); // circle middle y position /* * circle x positions @@ -87,58 +73,17 @@ let circlePosX = [ ]; let radiusOuter = circleCount == 3 ? 25 : 20; +let radiusBorder = radiusOuter+3; // absolute border of circles let radiusInner = circleCount == 3 ? 20 : 15; let circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10"; let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11"; let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12"; let iconOffset = circleCount == 3 ? 6 : 8; -let defaultCircleTypes = ["Bangle/Steps", "Bangle/HRM", "Bangle/Battery", "weather"]; -let circleInfoNum = [ - 0, // circle1 - 0, // circle2 - 0, // circle3 - 0, // circle4 -]; -let circleItemNum = [ - 0, // circle1 - 1, // circle2 - 2, // circle3 - 3, // circle4 -]; -let weatherCircleNum = 0; -let weatherCircleDataNum = 0; -let weatherCircleCondNum = 0; -let weatherCircleTempNum = 0; -function hideWidgets() { - /* - * we are not drawing the widgets as we are taking over the whole screen - * so we will blank out the draw() functions of each widget and change the - * area to the top bar doesn't get cleared. - */ - if (WIDGETS && typeof WIDGETS === "object") { - for (let wd of WIDGETS) { - wd.draw = () => {}; - wd.area = ""; - } - } -} - -function draw() { - g.clear(true); - let widgetUtils; - - try { - widgetUtils = require("widget_utils"); - } catch (e) { - } - if (!showWidgets) { - if (widgetUtils) widgetUtils.hide(); else hideWidgets(); - } else { - if (widgetUtils) widgetUtils.show(); - Bangle.drawWidgets(); - } +let draw = function() { + let R = Bangle.appRect; + g.reset().clearRect(R.x,R.y, R.x2, h3-(radiusBorder+1)); g.setColor(colorBg); g.fillRect(0, widgetOffset, w, h2 + 22); @@ -180,118 +125,16 @@ function draw() { if (icon) g.drawImage(icon, w - 48, h1, {scale:0.75}); } - drawCircle(1); - drawCircle(2); - drawCircle(3); - if (circleCount >= 4) drawCircle(4); - queueDraw(); } -function drawCircle(index) { - let type = settings['circle' + index]; - if (!type) type = defaultCircleTypes[index - 1]; - let w = getCircleXPosition(type); - - switch (type) { - case "weather": - drawWeather(w); - break; - case "sunprogress": - case "sunProgress": - drawSunProgress(w); - break; - //TODO those are going to be deprecated, keep for backwards compatibility for now - //ideally all data should come from some clkinfo - case "steps": - drawSteps(w); - break; - case "stepsDist": - drawStepsDistance(w); - break; - case "hr": - drawHeartRate(w); - break; - case "battery": - drawBattery(w); - break; - case "temperature": - drawTemperature(w); - break; - case "pressure": - drawPressure(w); - break; - case "altitude": - drawAltitude(w); - break; - //end deprecated - case "empty": - // we draw nothing here - return; - default: - drawClkInfo(index, w); - } -} - -// serves as cache for quicker lookup of circle positions -let circlePositionsCache = []; -/* - * Looks in the following order if a circle with the given type is somewhere visible/configured - * 1. circlePositionsCache - * 2. settings - * 3. defaultCircleTypes - * - * In case 2 and 3 the circlePositionsCache will be updated - */ -function getCirclePosition(type) { - if (circlePositionsCache[type] >= 0) { - return circlePositionsCache[type]; - } - for (let i = 1; i <= circleCount; i++) { - let setting = settings['circle' + i]; - if (setting == type) { - circlePositionsCache[type] = i - 1; - return i - 1; - } - } - for (let i = 0; i < defaultCircleTypes.length; i++) { - if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) { - circlePositionsCache[type] = i; - return i; - } - } - return undefined; -} - -function getCircleXPosition(type) { - let circlePos = getCirclePosition(type); - if (circlePos != undefined) { - return circlePosX[circlePos]; - } - return undefined; -} - -function isCircleEnabled(type) { - return getCirclePosition(type) != undefined; -} - -function getCircleColor(type) { - let pos = getCirclePosition(type); - let color = settings["circle" + (pos + 1) + "color"]; +let getCircleColor = function(index) { + let color = settings["circle" + index + "color"]; if (color && color != "") return color; + return g.theme.fg; } -function getCircleIconColor(type, color, percent) { - let pos = getCirclePosition(type); - let colorizeIcon = settings["circle" + (pos + 1) + "colorizeIcon"] == true; - if (colorizeIcon) { - return getGradientColor(color, percent); - } else { - return ""; - } -} - -function getGradientColor(color, percent) { +let getGradientColor = function(color, percent) { if (isNaN(percent)) percent = 0; if (percent > 1) percent = 1; let colorList = [ @@ -311,423 +154,16 @@ function getGradientColor(color, percent) { return color; } -function getImage(graphic, color) { - if (!color || color == "") { - return graphic; +let getCircleIconColor = function(index, color, percent) { + let colorizeIcon = settings["circle" + index + "colorizeIcon"] == true; + if (colorizeIcon) { + return getGradientColor(color, percent); } else { - return { - width: 16, - height: 16, - bpp: 1, - transparent: 0, - buffer: E.toArrayBuffer(graphic), - palette: new Uint16Array([colorBg, g.toColor(color)]) - }; + return g.theme.fg; } } -function drawWeather(w) { - if (!w) w = getCircleXPosition("weather"); - let weatherInfo = menu[weatherCircleNum]; - let weatherCond = weatherCircleCondNum >= 0? weatherInfo.items[weatherCircleCondNum]: undefined; - let weatherData = weatherCircleDataNum >= 0? weatherInfo.items[weatherCircleDataNum]: undefined; - let weatherTemp = weatherCircleTempNum >= 0? weatherInfo.items[weatherCircleTempNum]: undefined; - let color = getCircleColor("weather"); - let percent = 0; - let data = settings.weatherCircleData; - let tempString = "?", icon = undefined; - let scale = 16/24; //our icons are 16x16 while clkinfo's are 24x24 - - if(weatherCond) { - weatherCond.show() - weatherCond.hide() - let data = weatherCond.get() - if(settings.legacyWeatherIcons) { //may disappear in future - icon = getWeatherIconByCode(data.v); - scale = 1; - } else - icon = data.img; - } - if(weatherTemp) { - weatherTemp.show() - weatherTemp.hide() - tempString = weatherTemp.get().text; - } - - drawCircleBackground(w); - - if(weatherData) { - weatherData.show(); - weatherData.hide(); - let data = weatherData.get(); - if(weatherData.hasRange) percent = (data.v-data.min) / (data.max-data.min); - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, tempString); - - if(icon) { - g.setColor(getCircleIconColor("weather", color, percent)) - .drawImage(icon, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: scale}); - } else { - g.drawString("?", w, h3 + radiusOuter); - } -} -function drawWeatherOld(w) { - if (!w) w = getCircleXPosition("weather"); - let weather = getWeather(); - let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined; - let code = weather ? weather.code : -1; - - drawCircleBackground(w); - - let color = getCircleColor("weather"); - let percent; - let data = settings.weatherCircleData; - switch (data) { - case "humidity": - let humidity = weather ? weather.hum : undefined; - if (humidity >= 0) { - percent = humidity / 100; - drawGauge(w, h3, percent, color); - } - break; - case "wind": - if (weather) { - let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/); - if (wind[1] >= 0) { - if (wind[2] == "kmh") { - wind[1] = windAsBeaufort(wind[1]); - } - // wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) - percent = wind[1] / 12; - drawGauge(w, h3, percent, color); - } - } - break; - case "empty": - break; - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, tempString ? tempString : "?"); - - if (code > 0) { - let icon = getWeatherIconByCode(code); - if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - } else { - g.drawString("?", w, h3 + radiusOuter); - } -} - -function drawSunProgress(w) { - if (!w) w = getCircleXPosition("sunprogress"); - let percent = getSunProgress(); - - // sunset icons: - let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA"); - let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA"); - - drawCircleBackground(w); - - let color = getCircleColor("sunprogress"); - - drawGauge(w, h3, percent, color); - - drawInnerCircleAndTriangle(w); - - let icon = sunSetDown; - let text = "?"; - let times = getSunData(); - if (times != undefined) { - let sunRise = Math.round(times.sunrise.getTime() / 1000); - let sunSet = Math.round(times.sunset.getTime() / 1000); - if (!isDay()) { - // night - if (now > sunRise) { - // after sunRise - let upcomingSunRise = sunRise + 60 * 60 * 24; - text = formatSeconds(upcomingSunRise - now); - } else { - text = formatSeconds(sunRise - now); - } - icon = sunSetUp; - } else { - // day, approx sunrise tomorrow: - text = formatSeconds(sunSet - now); - icon = sunSetDown; - } - } - - writeCircleText(w, text); - - g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - -/* - * Deprecated but nice as references for clkinfo - */ - -function drawSteps(w) { - if (!w) w = getCircleXPosition("steps"); - let steps = getSteps(); - - drawCircleBackground(w); - - let color = getCircleColor("steps"); - - let percent; - let stepGoal = settings.stepGoal; - if (stepGoal > 0) { - percent = steps / stepGoal; - if (stepGoal < steps) percent = 1; - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, shortValue(steps)); - - g.drawImage(getImage(atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"), getCircleIconColor("steps", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - -function drawStepsDistance(w) { - if (!w) w = getCircleXPosition("stepsDistance"); - let steps = getSteps(); - let stepDistance = settings.stepLength; - let stepsDistance = Math.round(steps * stepDistance); - - drawCircleBackground(w); - - let color = getCircleColor("stepsDistance"); - - let percent; - let stepDistanceGoal = settings.stepDistanceGoal; - if (stepDistanceGoal > 0) { - percent = stepsDistance / stepDistanceGoal; - if (stepDistanceGoal < stepsDistance) percent = 1; - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, shortValue(stepsDistance)); - - g.drawImage(getImage(atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"), getCircleIconColor("stepsDistance", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - -function drawHeartRate(w) { - if (!w) w = getCircleXPosition("hr"); - - let heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA"); - - drawCircleBackground(w); - - let color = getCircleColor("hr"); - - let percent; - if (hrtValue != undefined) { - let minHR = settings.minHR; - let maxHR = settings.maxHR; - percent = (hrtValue - minHR) / (maxHR - minHR); - if (isNaN(percent)) percent = 0; - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - writeCircleText(w, hrtValue != undefined ? hrtValue : "-"); - - g.drawImage(getImage(heartIcon, getCircleIconColor("hr", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} - -function drawBattery(w) { - if (!w) w = getCircleXPosition("battery"); - let battery = E.getBattery(); - - let powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA"); - - drawCircleBackground(w); - - let color = getCircleColor("battery"); - - let percent; - if (battery > 0) { - percent = battery / 100; - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - if (Bangle.isCharging()) { - color = colorGreen; - } else { - if (settings.batteryWarn != undefined && battery <= settings.batteryWarn) { - color = colorRed; - } - } - writeCircleText(w, battery + '%'); - - g.drawImage(getImage(powerIcon, getCircleIconColor("battery", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); -} -function drawTemperature(w) { - if (!w) w = getCircleXPosition("temperature"); - - getPressureValue("temperature").then((temperature) => { - drawCircleBackground(w); - - let color = getCircleColor("temperature"); - - let percent; - if (temperature) { - let min = -40; - let max = 85; - percent = (temperature - min) / (max - min); - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - if (temperature) - writeCircleText(w, locale.temp(temperature)); - - g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("temperature", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - - }); -} - -function drawPressure(w) { - if (!w) w = getCircleXPosition("pressure"); - - getPressureValue("pressure").then((pressure) => { - drawCircleBackground(w); - - let color = getCircleColor("pressure"); - - let percent; - if (pressure && pressure > 0) { - let minPressure = 950; - let maxPressure = 1050; - percent = (pressure - minPressure) / (maxPressure - minPressure); - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - if (pressure) - writeCircleText(w, Math.round(pressure)); - - g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("pressure", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - - }); -} - -function drawAltitude(w) { - if (!w) w = getCircleXPosition("altitude"); - - getPressureValue("altitude").then((altitude) => { - drawCircleBackground(w); - - let color = getCircleColor("altitude"); - - let percent; - if (altitude) { - let min = 0; - let max = 10000; - percent = (altitude - min) / (max - min); - drawGauge(w, h3, percent, color); - } - - drawInnerCircleAndTriangle(w); - - if (altitude) - writeCircleText(w, locale.distance(Math.round(altitude))); - - g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("altitude", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset); - - }); -} - -function shortValue(v) { - if (isNaN(v)) return '-'; - if (v <= 999) return v; - if (v >= 1000 && v < 10000) { - v = Math.floor(v / 100) * 100; - return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - } - if (v >= 10000) { - v = Math.floor(v / 1000) * 1000; - return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - } -} - -function getSteps() { - if (Bangle.getHealthStatus) { - return Bangle.getHealthStatus("day").steps; - } - if (WIDGETS && WIDGETS.wpedom !== undefined) { - return WIDGETS.wpedom.getSteps(); - } - return 0; -} - -function getPressureValue(type) { - return new Promise((resolve) => { - if (Bangle.getPressure) { - if (!pressureLocked) { - pressureLocked = true; - if (pressureCache && pressureCache[type]) { - resolve(pressureCache[type]); - } - Bangle.getPressure().then(function(d) { - pressureLocked = false; - if (d) { - pressureCache = d; - if (d[type]) { - resolve(d[type]); - } - } - }).catch(() => {}); - } else { - if (pressureCache && pressureCache[type]) { - resolve(pressureCache[type]); - } - } - } - }); -} - -/* - * end deprecated - */ - -var menu = null; -function reloadMenu() { - menu = clock_info.load(); - for(var i=1; i<5; i++) - if(settings['circle'+i].includes("/")) { - let parts = settings['circle'+i].split("/"); - let infoName = parts[0], itemName = parts[1]; - let infoNum = menu.findIndex(e=>e.name==infoName); - let itemNum = 0; //get first if dynamic - if(!menu[infoNum].dynamic) - itemNum = menu[infoNum].items.findIndex(it=>it.name==itemName); - circleInfoNum[i-1] = infoNum; - circleItemNum[i-1] = itemNum; - } else if(settings['circle'+i] == "weather") { - weatherCircleNum = menu.findIndex(e=>e.name.toLowerCase() == "weather"); - weatherCircleDataNum = menu[weatherCircleNum].items.findIndex(it=>it.name==settings.weatherCircleData); - weatherCircleCondNum = menu[weatherCircleNum].items.findIndex(it=>it.name=="condition"); - weatherCircleTempNum = menu[weatherCircleNum].items.findIndex(it=>it.name=="temperature"); - } -} -//reload periodically for changes? -reloadMenu(); - -function drawEmpty(img, w, color) { +let drawEmpty = function(img, w, color) { drawGauge(w, h3, 0, color); drawInnerCircleAndTriangle(w); writeCircleText(w, "?"); @@ -736,66 +172,45 @@ function drawEmpty(img, w, color) { .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); } -function drawClkInfo(index, w) { - var info = menu[circleInfoNum[index-1]]; - var type = settings['circle'+index]; - if (!w) w = getCircleXPosition(type); +let drawCircle = function(index, item, data) { + var w = circlePosX[index-1]; drawCircleBackground(w); - const color = getCircleColor(type); - var item = info.items[circleItemNum[index-1]]; - if(!info || !item) { - drawEmpty(info? info.img : null, w, color); - return; - } - item.show(); - item.hide(); - var data=item.get(); + const color = getCircleColor(index); + //drawEmpty(info? info.img : null, w, color); var img = data.img; var percent = 1; //fill up if no range - var txt = data.text; - if(!img) img = info.img; + var txt = ""+data.text; + if (txt.endsWith(" bpm")) txt=txt.slice(0,-4); // hack for heart rate - remove the 'bpm' text if(item.hasRange) percent = (data.v-data.min) / (data.max-data.min); if(data.short) txt = data.short; drawGauge(w, h3, percent, color); drawInnerCircleAndTriangle(w); writeCircleText(w, txt); - g.setColor(getCircleIconColor(type, color, percent)) + g.setColor(getCircleIconColor(index, color, percent)) .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); } -/* - * wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale) - */ -function windAsBeaufort(windInKmh) { - let beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118]; - let l = 0; - while (l < beaufort.length && beaufort[l] < windInKmh) { - l++; - } - return l; -} - /* * Choose weather icon to display based on weather conditition code * https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 */ -function getWeatherIconByCode(code, big) { +let getWeatherIconByCode = function(code, big) { let codeGroup = Math.round(code / 100); if (big == undefined) big = false; // weather icons: - let weatherCloudy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); - let weatherSunny = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAA//8AAA//8AD//wAAD//wAP//AAAP//AA//8AAA//8AAADwAADwAAAAAHgAAeAAAAAAeAAB4AAAAAB8AAPgAAAAADwAA8AAAAAAPgAHwAAAAAAfgB+AAAAAAD/gf8AAAAAAf///4AAAAAD7//3wAAAAAfD/8PgAAAAD4B+AfAAAAAfADwA+AAAAD4APAB8AAAAfAA8AD4AAAB4ADwAHgAAADAAPAAMAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); - let weatherMoon = big ? atob("QEDBAP//wxgAAAYAAAAPAAAAD4AAAA8AAAAPwAAADwAAAA/gAAAPAAAAB/APAP/wAAAH+A8A//AAAAf4DwD/8AAAB/wPAP/wAAAH/gAADwAAAAe+AAAPAAAAB54AAA8AAAAHngAADwAAAAePAAAAAAAAD48OAAAAAAAPDw+AAAAAAB8PD8AAAAAAHg8P4AAAAAA+DwPwAAAAAHwfAfgAAAAB+D4A/AAA8AfwfgB/8AD//+D+AD/8AP//wfgAH/4Af/8B8AAf/wB//APgAAgfgD+AA8AAAAfAH8AHwAAAA+AP8B+AAAAB4Af//4AAAAHgA///gAAAAPAA//8AAAAA8AAf/wAAAADwAAAAAAAAAPAAAAAAAAAA8AcAAAAAAADwD+AAAAAAAfAfgAAAAAAB+D4AAAAAAAB8fAAAAAAAAD54AAAAAAAAHngAAAAAAAAe8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAPeAAAAAAAAB54AAAAAAAAHnwAAAAAAAA+PgAAAAAAAHwfgAAAAAAB+A/////////wB////////+AD////////wAB///////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); - let weatherPartlyCloudy = big ? atob("QEDBAP//wxgAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAABwAPAA4AAAAHgA8AHgAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+BAAAAAP+B/wOAAAAAfgB+B8AAAAD4AD8H4AAAAPAA/wPwAAAB8AH+Af/AAAHgA/AA//AAAeAH4AB/+AADwAfAAH/8A//AD4AAIH4D/8AfAAAAHwP/wB4AAAAPg//AHgAAAAeAA8B+AAAAB4AB4fwAAAADwAHn/AAAAAPAAff8AAAAA8AA/8AAAAADwAD/AAAAAAPAEH4AAAAAA8A4PgAAAAAHwHgcAAAAAAfg+AwAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); - let weatherRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4APAA8AAfg+AA8ADwAAfHwADwAPAAA+eAAPAA8AAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AADw8PDwAP8AAPDw8PAA/wAA8PDw8AD3gADw8PDwAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP/w8PDw8P8Af/Dw8PDw/gA/8PDw8PD8AAfw8PDw8OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAPAAAAAAAPAA8AAAAAAA8ADwAAAAAADwAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); - let weatherPartlyRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAA8AAfg+AAAADwAAfHwAAAAPAAA+eAAAAA8AAB54AAAADwAAHvAAAAAPAAAP8AAAAA8AAA/wAAAADwAAD/AAAA8PAAAP8AAADw8AAA/wAAAPDwAAD3gAAA8PAAAeeAAADw8AAB58AAAPDwAAPj4AAA8PAAB8H4AADw8AAfgP//8PDw//8Af//w8PD//gA///Dw8P/8AAf/8PDw/+AAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); - let weatherSnowy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAADwAfg+AAAAAPAAfHwAAAAA8AA+eAAAAADwAB54AA8AD/8AHvAADwAP/wAP8AAPAA//AA/wAA8AD/8AD/AA//AA8AAP8AD/8ADwAA/wAP/wAPAAD3gA//AA8AAeeAAPAAAAAB58AA8AAAAAPj4ADwAAAAB8H4APAAAAAfgP/wAA8A//8Af/AADwD//gA/8AAPAP/8AAfwAA8A/+AAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); - let weatherFoggy = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AD///AADwAAAP//8AAeAAAA///wAB4AAAD///AAPgAAAAAAAAA8AAAAAAAAAHwAAAAAAAAB+AAAAAAAAAf8AAAAD///D/4AAAAP//8P3wAAAA///w8PgAAAD///CAfAAAAAAAAAA+AAAAAAAAAB8AAAAAAAAAD4AAAAAAAAAHgAAP//8PAAMAAA///w8AAAAAD///DwAAAAAP//8PAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); - let weatherStormy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAD/AAHvAAAAAf4AAP8AAAAB/gAA/wAAAAP8AAD/AAAAA/gAAP8AAAAH+AAA/wAAAAfwAAD3gAAAD/AAAeeAAAAP4AAB58AAAB/AAAPj4AAAH8AAB8H4AAA/gAAfgP//+D//D/8Af//4f/4P/gA///B//B/8AAf/8P/8P+AAAAAAAPgAAAAAAAAB8AAAAAAAAAHwAAAAAAAAA+AAAAAAAAADwAAAAAAAAAfAAAAAAAAAB4AAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); - let unknown = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAH//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAA/AAD4AAAAAD4H4HwAAAAAfB/4PgAAAAB8P/weAAAAAHg//h4AAAAA+Hw+HwAAAAD4eB8PAAAAAP/wDw8AAAAA//APDwAAAAD/8A8PAAAAAH/gDw8AAAAAAAAfDwAAAAAAAH4fAAAAAAAB/B4AAAAAAAf4HgAAAAAAD/A+AAAAAAAfwHwAAAAAAD8A+AAAAAAAPgH4AAAAAAB8B/AAAAAAAHgf4AAAAAAA+H+AAAAAAADwfwAAAAAAAPD8AAAAAAAA8PAAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAADw8AAAAAAAAPDwAAAAAAAA8PAAAAAAAADw8AAAAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : undefined; - + let weatherCloudy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAAAAH4PgAAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA94AAAAAAAAHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/////////AH////////4AP////////AAH///////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA"); + let weatherSunny = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAMAA8AAwAAAB4ADwAHgAAAHwAPAA+AAAAPgA8AHwAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+AAAAAAP+B/wAAAAAAfgB+AAAAAAD4AB8AAAAAAPAADwAAAAAB8AAPgAAAAAHgAAeAAAAAAeAAB4AAAAADwAADwAAAP//AAAP//AA//8AAA//8AD//wAAD//wAP//AAAP//AAAA8AAA8AAAAAB4AAHgAAAAAHgAAeAAAAAAfAAD4AAAAAA8AAPAAAAAAD4AB8AAAAAAH4AfgAAAAAA/4H/AAAAAAH///+AAAAAA+//98AAAAAHw//D4AAAAA+AfgHwAAAAHwA8APgAAAA+ADwAfAAAAHwAPAA+AAAAeAA8AB4AAAAwADwADAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA"); + let weatherMoon = big ? atob("QECBAAAGAAAADwAAAA+AAAAPAAAAD8AAAA8AAAAP4AAADwAAAAfwDwD/8AAAB/gPAP/wAAAH+A8A//AAAAf8DwD/8AAAB/4AAA8AAAAHvgAADwAAAAeeAAAPAAAAB54AAA8AAAAHjwAAAAAAAA+PDgAAAAAADw8PgAAAAAAfDw/AAAAAAB4PD+AAAAAAPg8D8AAAAAB8HwH4AAAAAfg+APwAAPAH8H4Af/AA///g/gA//AD//8H4AB/+AH//AfAAH/8Af/wD4AAIH4A/gAPAAAAHwB/AB8AAAAPgD/AfgAAAAeAH//+AAAAB4AP//4AAAADwAP//AAAAAPAAH/8AAAAA8AAAAAAAAADwAAAAAAAAAPAHAAAAAAAA8A/gAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA"); + let weatherPartlyCloudy = big ? atob("QECBAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAcADwAOAAAAB4APAB4AAAAHwA8APgAAAAPgH4B8AAAAAfD/8PgAAAAA+//98AAAAAB////gQAAAAD/gf8DgAAAAH4AfgfAAAAA+AA/B+AAAADwAP8D8AAAAfAB/gH/wAAB4APwAP/wAAHgB+AAf/gAA8AHwAB//AP/wA+AACB+A//AHwAAAB8D/8AeAAAAD4P/wB4AAAAHgAPAfgAAAAeAAeH8AAAAA8AB5/wAAAADwAH3/AAAAAPAAP/AAAAAA8AA/wAAAAADwBB+AAAAAAPAOD4AAAAAB8B4HAAAAAAH4PgMAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA94AAAAAAAAHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/////////AH////////4AP////////AAH///////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA"); + let weatherRainy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+ADwAPAAH4PgAPAA8AAHx8AA8ADwAAPngADwAPAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAA8PDw8AD/AADw8PDwAP8AAPDw8PAA94AA8PDw8AHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/8PDw8PD/AH/w8PDw8P4AP/Dw8PDw/AAH8PDw8PDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8ADwAAAAAADwAPAAAAAAAPAA8AAAAAAA8ADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA"); + let weatherPartlyRainy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAPAAH4PgAAAA8AAHx8AAAADwAAPngAAAAPAAAeeAAAAA8AAB7wAAAADwAAD/AAAAAPAAAP8AAAAA8AAA/wAAAPDwAAD/AAAA8PAAAP8AAADw8AAA94AAAPDwAAHngAAA8PAAAefAAADw8AAD4+AAAPDwAAfB+AAA8PAAH4D///Dw8P//AH//8PDw//4AP//w8PD//AAH//Dw8P/gAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA"); + let weatherSnowy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAA8AH4PgAAAADwAHx8AAAAAPAAPngAAAAA8AAeeAAPAA//AB7wAA8AD/8AD/AADwAP/wAP8AAPAA//AA/wAP/wAPAAD/AA//AA8AAP8AD/8ADwAA94AP/wAPAAHngADwAAAAAefAAPAAAAAD4+AA8AAAAAfB+ADwAAAAH4D/8AAPAP//AH/wAA8A//4AP/AADwD//AAH8AAPAP/gAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA"); + let weatherFoggy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAMAA8AAwAAAB4ADwAHgAAAHwAPAA+AAAAPgA8AHwAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+AAAAAAP+B/wAAAAAAfgB+AAAAAAD4AB8AAAAAAPAADwAAAAAB8AAPgAAAAAHgAAeAAAAAAeAAB4AAAAADwAADwAAAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AA///wAA8AAAD///AAHgAAAP//8AAeAAAA///wAD4AAAAAAAAAPAAAAAAAAAB8AAAAAAAAAfgAAAAAAAAH/AAAAA///w/+AAAAD///D98AAAAP//8PD4AAAA///wgHwAAAAAAAAAPgAAAAAAAAAfAAAAAAAAAA+AAAAAAAAAB4AAD///DwADAAAP//8PAAAAAA///w8AAAAAD///DwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA"); + let weatherStormy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAAAAH4PgAAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAA/wAB7wAAAAH+AAD/AAAAAf4AAP8AAAAD/AAA/wAAAAP4AAD/AAAAB/gAAP8AAAAH8AAA94AAAA/wAAHngAAAD+AAAefAAAAfwAAD4+AAAB/AAAfB+AAAP4AAH4D///g//w//AH//+H/+D/4AP//wf/wf/AAH//D//D/gAAAAAAD4AAAAAAAAAfAAAAAAAAAB8AAAAAAAAAPgAAAAAAAAA8AAAAAAAAAHwAAAAAAAAAeAAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA"); + let unknown = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAH/+AAAAAAAB//+AAAAAAAP//8AAAAAAB/gf4AAAAAAPwAPwAAAAAB+AAfgAAAAAPwAA+AAAAAA+B+B8AAAAAHwf+D4AAAAAfD/8HgAAAAB4P/4eAAAAAPh8Ph8AAAAA+HgfDwAAAAD/8A8PAAAAAP/wDw8AAAAA//APDwAAAAB/4A8PAAAAAAAAHw8AAAAAAAB+HwAAAAAAAfweAAAAAAAH+B4AAAAAAA/wPgAAAAAAH8B8AAAAAAA/APgAAAAAAD4B+AAAAAAAfAfwAAAAAAB4H+AAAAAAAPh/gAAAAAAA8H8AAAAAAADw/AAAAAAAAPDwAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA8PAAAAAAAADw8AAAAAAAAPDwAAAAAAAA8PAAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : undefined; + switch (codeGroup) { case 2: return weatherStormy; @@ -823,7 +238,9 @@ function getWeatherIconByCode(code, big) { case 8: switch (code) { case 800: - return isDay() ? weatherSunny : weatherMoon; + var hr = (new Date()).getHours(); + var isDay = (hr>6) && (hr<=18); // fixme we don't want to include ALL of suncalc just to choose one icon + return isDay ? weatherSunny : weatherMoon; case 801: return weatherPartlyCloudy; case 802: @@ -836,80 +253,19 @@ function getWeatherIconByCode(code, big) { } } - -function isDay() { - let times = getSunData(); - if (times == undefined) return true; - let sunRise = Math.round(times.sunrise.getTime() / 1000); - let sunSet = Math.round(times.sunset.getTime() / 1000); - - return (now > sunRise && now < sunSet); -} - -function formatSeconds(s) { - if (s > 60 * 60) { // hours - return Math.round(s / (60 * 60)) + "h"; - } - if (s > 60) { // minutes - return Math.round(s / 60) + "m"; - } - return "<1m"; -} - -function getSunData() { - if (location != undefined && location.lat != undefined) { - let SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); - // get today's sunlight times for lat/lon - return SunCalc ? SunCalc.getTimes(new Date(), location.lat, location.lon) : undefined; - } - return undefined; -} - -/* - * Calculated progress of the sun between sunrise and sunset in percent - * - * Taken from rebble app and modified - */ -function getSunProgress() { - let times = getSunData(); - if (times == undefined) return 0; - let sunRise = Math.round(times.sunrise.getTime() / 1000); - let sunSet = Math.round(times.sunset.getTime() / 1000); - - if (isDay()) { - // during day - let dayLength = sunSet - sunRise; - if (now > sunRise) { - return (now - sunRise) / dayLength; - } else { - return (sunRise - now) / dayLength; - } - } else { - // during night - if (now < sunRise) { - let prevSunSet = sunSet - 60 * 60 * 24; - return 1 - (sunRise - now) / (sunRise - prevSunSet); - } else { - let upcomingSunRise = sunRise + 60 * 60 * 24; - return (upcomingSunRise - now) / (upcomingSunRise - sunSet); - } - } -} - /* * Draws the background and the grey circle */ -function drawCircleBackground(w) { - g.clearRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3); +let drawCircleBackground = function(w) { // Draw rectangle background: g.setColor(colorBg); - g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3); + g.fillRect(w - radiusBorder, h3 - radiusBorder, w + radiusBorder, g.getHeight()-1); // Draw grey background circle: - g.setColor(colorGrey); + g.setColor('#808080'); // grey g.fillCircle(w, h3, radiusOuter); } -function drawInnerCircleAndTriangle(w) { +let drawInnerCircleAndTriangle = function(w) { // Draw inner circle g.setColor(colorBg); g.fillCircle(w, h3, radiusInner); @@ -917,18 +273,13 @@ function drawInnerCircleAndTriangle(w) { g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]); } -function radians(a) { - return a * Math.PI / 180; -} - /* * This draws the actual gauge consisting out of lots of little filled circles */ -function drawGauge(cx, cy, percent, color) { +let drawGauge = function(cx, cy, percent, color) { let offset = 15; let end = 360 - offset; - let radius = radiusInner + (circleCount == 3 ? 3 : 2); - let size = radiusOuter - radiusInner - 2; + let radius = radiusOuter+1; if (percent <= 0) return; // no gauge needed if (percent > 1) percent = 1; @@ -938,15 +289,21 @@ function drawGauge(cx, cy, percent, color) { color = getGradientColor(color, percent); g.setColor(color); - - for (let i = startRotation; i > endRotation - size; i -= size) { - x = cx + radius * Math.sin(radians(i)); - y = cy + radius * Math.cos(radians(i)); - g.fillCircle(x, y, size); - } + // convert to radians + startRotation *= Math.PI / 180; + let amt = Math.PI / 10; + endRotation = (endRotation * Math.PI / 180) - amt; + // all we need to draw is an arc, because we'll fill the center + let poly = [cx,cy]; + for (let r = startRotation; r > endRotation; r -= amt) + poly.push( + cx + radius * Math.sin(r), + cy + radius * Math.cos(r) + ); + g.fillPoly(poly); } -function writeCircleText(w, content) { +let writeCircleText = function(w, content) { if (content == undefined) return; let font = String(content).length > 4 ? circleFontSmall : String(content).length > 3 ? circleFont : circleFontBig; g.setFont(font); @@ -956,26 +313,48 @@ function writeCircleText(w, content) { g.drawString(content, w, h3); } -function getWeather() { +let getWeather=function() { let jsonWeather = storage.readJSON('weather.json'); return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined; } +g.clear(1); // clear the whole screen + Bangle.setUI({ mode : "clock", remove : function() { - // Called to unload all of the clock app + // Called to unload all of the clock app (allowing for 'fast load') if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; - + clockInfoMenu.forEach(c => c.remove()); delete Graphics.prototype.setFontRobotoRegular50NumericOnly; delete Graphics.prototype.setFontRobotoRegular21; - }}); + if (!showWidgets) require("widget_utils").show(); + } +}); + +let clockInfoDraw = (itm, info, options) => { + //print("Draw",itm.name,options); + drawCircle(options.circlePosition, itm, info); + if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1) +}; +let clockInfoItems = require("clock_info").load(); +let clockInfoMenu = []; +for(var i=0;i{ - if(e.dynamic) { - valuesCircleTypes = valuesCircleTypes.concat([e.name+"/"]); - namesCircleTypes = namesCircleTypes.concat([e.name]); - } else { - let values = e.items.map(i=>e.name+"/"+i.name); - let names =e.name=="Bangle" ? e.items.map(i=>i.name) : values; - valuesCircleTypes = valuesCircleTypes.concat(values); - namesCircleTypes = namesCircleTypes.concat(names); - } - }) - - const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff", "#fff", "#000", "green-red", "red-green", "fg"]; const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", @@ -105,12 +88,6 @@ const menu = { '': { 'title': /*LANG*/'Circle ' + circleId }, /*LANG*/'< Back': ()=>showMainMenu(), - /*LANG*/'data': { - value: valuesCircleTypes.indexOf(settings[circleName]), - min: 0, max: valuesCircleTypes.length - 1, - format: v => namesCircleTypes[v], - onchange: x => save(circleName, valuesCircleTypes[x]), - }, /*LANG*/'color': { value: valuesColors.indexOf(settings[colorKey]) || 0, min: 0, max: valuesColors.length - 1, diff --git a/apps/clkinfocal/ChangeLog b/apps/clkinfocal/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/clkinfocal/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/clkinfocal/app.png b/apps/clkinfocal/app.png new file mode 100644 index 000000000..ed79cd884 Binary files /dev/null and b/apps/clkinfocal/app.png differ diff --git a/apps/clkinfocal/clkinfo.js b/apps/clkinfocal/clkinfo.js new file mode 100644 index 000000000..a7949cda4 --- /dev/null +++ b/apps/clkinfocal/clkinfo.js @@ -0,0 +1,32 @@ +(function() { + require("Font4x8Numeric").add(Graphics); + return { + name: "Bangle", + items: [ + { name : "Date", + get : () => { + let d = new Date(); + let g = Graphics.createArrayBuffer(24,24,1,{msb:true}); + g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0); + g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17); + return { + text : require("locale").dow(d,1).toUpperCase(), + img : g.asImage("string") + }; + }, + show : function() { + this.interval = setTimeout(()=>{ + this.emit("redraw"); + this.interval = setInterval(()=>{ + this.emit("redraw"); + }, 86400000); + }, 86400000 - (Date.now() % 86400000)); + }, + hide : function() { + clearInterval(this.interval); + this.interval = undefined; + } + } + ] + }; +}) diff --git a/apps/clkinfocal/metadata.json b/apps/clkinfocal/metadata.json new file mode 100644 index 000000000..6d6dd63fc --- /dev/null +++ b/apps/clkinfocal/metadata.json @@ -0,0 +1,12 @@ +{ "id": "clkinfocal", + "name": "Calendar Clockinfo", + "version":"0.01", + "description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday", + "icon": "app.png", + "type": "clkinfo", + "tags": "clkinfo,calendar", + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"clkinfocal.clkinfo.js","url":"clkinfo.js"} + ] +} diff --git a/apps/clkinfosunrise/ChangeLog b/apps/clkinfosunrise/ChangeLog index 5560f00bc..86e7a7fa8 100644 --- a/apps/clkinfosunrise/ChangeLog +++ b/apps/clkinfosunrise/ChangeLog @@ -1 +1,4 @@ 0.01: New App! +0.02: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps + Add a 'time' clockinfo that also displays a percentage of day left +0.03: Change 3rd mode to show the time to next sunrise/sunset time (not actual time) diff --git a/apps/clkinfosunrise/clkinfo.js b/apps/clkinfosunrise/clkinfo.js index 1454a83f3..22c507f34 100644 --- a/apps/clkinfosunrise/clkinfo.js +++ b/apps/clkinfosunrise/clkinfo.js @@ -1,32 +1,75 @@ (function() { // get today's sunlight times for lat/lon - var sunrise, sunset; + var sunrise, sunset, date; + var SunCalc = require("suncalc"); // from modules folder + const locale = require("locale"); function calculate() { - var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); - const locale = require("locale"); var location = require("Storage").readJSON("mylocation.json",1)||{}; location.lat = location.lat||51.5072; - location.lon = location.lon||0.1276; - location.location = location.location||"London"; - var times = SunCalc.getTimes(new Date(), location.lat, location.lon); - sunrise = locale.time(times.sunrise,1); - sunset = locale.time(times.sunset,1); + location.lon = location.lon||0.1276; // London + date = new Date(Date.now()); + var times = SunCalc.getTimes(date, location.lat, location.lon); + sunrise = times.sunrise; + sunset = times.sunset; /* do we want to re-calculate this every day? Or we just assume that 'show' will get called once a day? */ } + function show() { + this.interval = setTimeout(()=>{ + this.emit("redraw"); + this.interval = setInterval(()=>{ + this.emit("redraw"); + }, 60000); + }, 60000 - (Date.now() % 60000)); + } + function hide() { + clearInterval(this.interval); + this.interval = undefined; + } + return { name: "Bangle", items: [ { name : "Sunrise", - get : () => ({ text : sunrise, - img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }), - show : calculate, hide : () => {} + get : () => { calculate(); + return { text : locale.time(sunrise,1), + img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }}, + show : show, hide : hide }, { name : "Sunset", - get : () => ({ text : sunset, - img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }), - show : calculate, hide : () => {} + get : () => { calculate(); + return { text : locale.time(sunset,1), + img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }}, + show : show, hide : hide + }, { name : "Sunrise/set", // Time in day (uses v/min/max to show percentage through day) + hasRange : true, + get : () => { + calculate(); + let day = true; + let d = date.getTime(); + let dayLength = sunset.getTime()-sunrise.getTime(); + let timeUntil, timeTotal; + if (d < sunrise.getTime()) { + day = false; // early morning + timePast = sunrise.getTime()-d; + timeTotal = 86400000-dayLength; + } else if (d > sunset.getTime()) { + day = false; // evening + timePast = d-sunset.getTime(); + timeTotal = 86400000-dayLength; + } else { // day! + timePast = d-sunrise.getTime(); + timeTotal = dayLength; + } + let v = Math.round(100 * timePast / timeTotal); + let minutesTo = (timeTotal-timePast)/60000; + return { text : (minutesTo>90) ? (Math.round(minutesTo/60)+"h") : (Math.round(minutesTo)+"m"), + v : v, min : 0, max : 100, + img : day ? atob("GBiBAAAYAAAYAAAYAAgAEBwAOAx+MAD/AAH/gAP/wAf/4Af/4Of/5+f/5wf/4Af/4AP/wAH/gAD/AAx+MBwAOAgAEAAYAAAYAAAYAA==") : atob("GBiBAAfwAA/8AAP/AAH/gAD/wAB/wAB/4AA/8AA/8AA/8AAf8AAf8AAf8AAf8AA/8AA/8AA/4AB/4AB/wAD/wAH/gAf/AA/8AAfwAA==") + } + }, + show : show, hide : hide } ] }; diff --git a/apps/clkinfosunrise/metadata.json b/apps/clkinfosunrise/metadata.json index f8b68e11f..d130c6453 100644 --- a/apps/clkinfosunrise/metadata.json +++ b/apps/clkinfosunrise/metadata.json @@ -1,6 +1,6 @@ { "id": "clkinfosunrise", "name": "Sunrise Clockinfo", - "version":"0.01", + "version":"0.03", "description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays sunrise and sunset based on the location from the 'My Location' app", "icon": "app.png", "type": "clkinfo", diff --git a/apps/daisy/ChangeLog b/apps/daisy/ChangeLog index b13ce261b..61a09a18d 100644 --- a/apps/daisy/ChangeLog +++ b/apps/daisy/ChangeLog @@ -6,3 +6,4 @@ 0.06: better contrast for light theme, use fg color instead of dithered for ring 0.07: Use default Bangle formatter for booleans 0.08: fix idle timer always getting set to true +0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/daisy/app.js b/apps/daisy/app.js index 848cd1801..c99b19228 100644 --- a/apps/daisy/app.js +++ b/apps/daisy/app.js @@ -1,4 +1,4 @@ -var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); +var SunCalc = require("suncalc"); // from modules folder const storage = require('Storage'); const locale = require("locale"); const SETTINGS_FILE = "daisy.json"; @@ -70,7 +70,7 @@ function getSteps() { try { return Bangle.getHealthStatus("day").steps; } catch (e) { - if (WIDGETS.wpedom !== undefined) + if (WIDGETS.wpedom !== undefined) return WIDGETS.wpedom.getSteps(); else return 0; @@ -151,7 +151,7 @@ function prevInfo() { function clearInfo() { g.setColor(g.theme.bg); //g.setColor(g.theme.fg); - g.fillRect((w/2) - infoWidth, infoLine - infoHeight, (w/2) + infoWidth, infoLine + infoHeight); + g.fillRect((w/2) - infoWidth, infoLine - infoHeight, (w/2) + infoWidth, infoLine + infoHeight); } function drawInfo() { @@ -202,7 +202,7 @@ function drawClock() { var mm = da[4].substr(3,2); var steps = getSteps(); var p_steps = Math.round(100*(steps/10000)); - + g.reset(); g.setColor(g.theme.bg); g.fillRect(0, 0, w, h); @@ -218,7 +218,7 @@ function drawClock() { g.drawString(mm, (w/2) + 1, h/2); drawInfo(); - + // recalc sunrise / sunset every hour if (drawCount % 60 == 0) updateSunRiseSunSet(new Date(), location.lat, location.lon); @@ -254,7 +254,7 @@ function resetHrm() { Bangle.on('HRM', function(hrm) { hrmCurrent = hrm.bpm; hrmConfidence = hrm.confidence; - log_debug("HRM=" + hrm.bpm + " (" + hrm.confidence + ")"); + log_debug("HRM=" + hrm.bpm + " (" + hrm.confidence + ")"); if (infoMode == "ID_HRM" ) drawHrm(); }); @@ -360,7 +360,7 @@ function getGaugeImage(p) { palette : pal2, buffer : require("heatshrink").decompress(atob("AH4A/AH4AChWq1WpqtUFUgpBFYYABoApggQqDFYlVqBVjFYxZfFQorGLLrWCFZbgbVguoBQcFLD8qFQYMHiosDKzoOJFgZYYKwYPLFgZWawARMLDJWCawgAJcAZWYCZ6FCLCkKFQOgCZ8BFYNUFaZWSLAlAQShWQLAiESQQRtTLAKESFQOoFacFQiSCCwArTgCESQSyEUlTZTboyCnQiSCYQiSCYQiSCZQgdAVxwqYQgSwMVwOoFbMFWBquaWCArBVzKwDbRoqaWATcKbQKuaWAbcKbQKuaWAbcKVzqwNFYIqcWATaKVziwDbhDaebhjaebhgrBbTrcCFZDafbheqFcTcHbT7cDFY0CbT7cDqArxhWqwArfgFVqgrHFUDcBFY0qFcdVFY2oFcMFFY2qFclAFYugFcMBFYsCFctQFYuAFcMAFYsKFctUFYoqigEVFeEqFctVFYmoFccFFYmqFc1AcIdQFccBFf4rbGAoAhKQYr/Fa8FFc9UFYYqkgEVFf4r/FYwDDAEZTDFf4r/Ff4rbqorooArBqArlgIr/Ff4r/Ff4r/Ff4r/Ff4r/Ff4r/Ff4rbqgrlgorCioroAYIr/Ff4r/FbYDDAEZTDFf4r/FYtAFclVFYUBFc9QFf4rZAgoAgKQor/FbFUFccFFYkVFcwFDioFEAD4lFGIorgPogrtWoYAfqorEgIrlqArFAwgAdEg4rlPgqKFADrUHcQorfA4sVA4wAbEY4zHFbh7GRY4AbaY7jBqAqfERArrMBAAZUxNVbkEVFZAJBFcJhRAC6lJFYLcebQIrIBRTaXJhIrhUhLcfD5YLBbjtVFZTceZ5jceJRpkLVyaiLWDpJNFYKwaUIIrMSIKwaDhw6OVx50NFYKwZDZ6waOaCTBQjBGBZZw8CQi4ZBOR6EYeySEYQSCEaQSITDH6BvGIaKEWQSSEEbqQVVQgRYSKwLGUQgRCQKwTFUC4RYQKwSCTDAhEONQTwULAqcNCARWVLAhGMB55YPDhQqDKy4dFFhAMMLCzgFawZWbEI4AIGogAYFZtAFbgsMFTyyGVkBZOKr7gJazoA/AHIA=")) }; - + // p90 if (p >= 90 && p < 100) return { width : 176, height : 176, bpp : 2, @@ -410,7 +410,7 @@ function BUTTON(name,x,y,w,h,c,f,tx) { // if pressed the callback BUTTON.prototype.check = function(x,y) { //console.log(this.name + ":check() x=" + x + " y=" + y +"\n"); - + if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { log_debug(this.name + ":callback\n"); this.callback(); @@ -472,7 +472,7 @@ function checkIdle() { warned = false; return; } - + let hour = (new Date()).getHours(); let active = (hour >= 9 && hour < 21); //let active = true; @@ -501,7 +501,7 @@ function buzzer(n) { if (n-- < 1) return; Bangle.buzz(250); - + if (buzzTimeout) clearTimeout(buzzTimeout); buzzTimeout = setTimeout(function() { buzzTimeout = undefined; diff --git a/apps/daisy/metadata.json b/apps/daisy/metadata.json index c6cc93620..0bad50151 100644 --- a/apps/daisy/metadata.json +++ b/apps/daisy/metadata.json @@ -1,6 +1,6 @@ { "id": "daisy", "name": "Daisy", - "version":"0.08", + "version":"0.09", "dependencies": {"mylocation":"app"}, "description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times", "icon": "app.png", diff --git a/apps/espruinoprog/metadata.json b/apps/espruinoprog/metadata.json index ebb55b23d..7371e005d 100644 --- a/apps/espruinoprog/metadata.json +++ b/apps/espruinoprog/metadata.json @@ -11,7 +11,8 @@ "custom": "custom.html", "storage": [ {"name":"espruinoprog.app.js","url":"app.js"}, - {"name":"espruinoprog.img","url":"app-icon.js","evaluate":true}, + {"name":"espruinoprog.img","url":"app-icon.js","evaluate":true} + ], "data": [ {"name":"espruinoprog.json"} ] } diff --git a/apps/files/ChangeLog b/apps/files/ChangeLog index 1908f7e5c..4622e6f0f 100644 --- a/apps/files/ChangeLog +++ b/apps/files/ChangeLog @@ -3,4 +3,5 @@ 0.04: Add functionality to sort apps manually or alphabetically ascending/descending. 0.05: Tweaks to help with memory usage 0.06: Reduce memory usage -0.07: Allow negative numbers when manual-sorting \ No newline at end of file +0.07: Allow negative numbers when manual-sorting +0.08: Automatic translation of strings. diff --git a/apps/files/files.js b/apps/files/files.js index e81e9589f..2f7b5c9a1 100644 --- a/apps/files/files.js +++ b/apps/files/files.js @@ -3,20 +3,20 @@ const store = require('Storage'); function showMainMenu() { const mainmenu = { '': { - 'title': 'App Manager', + 'title': /*LANG*/'App Manager', }, '< Back': ()=> {load();}, - 'Sort Apps': () => showSortAppsMenu(), - 'Manage Apps': ()=> showApps(), - 'Compact': () => { - E.showMessage('Compacting...'); + /*LANG*/'Sort Apps': () => showSortAppsMenu(), + /*LANG*/'Manage Apps': ()=> showApps(), + /*LANG*/'Compact': () => { + E.showMessage(/*LANG*/'Compacting...'); try { store.compact(); } catch (e) { } showMainMenu(); }, - 'Free': { + /*LANG*/'Free': { value: undefined, format: (v) => { return store.getFree(); @@ -65,13 +65,13 @@ function eraseData(info) { }); } function eraseApp(app, files,data) { - E.showMessage('Erasing\n' + app.name + '...'); + E.showMessage(/*LANG*/'Erasing\n' + app.name + '...'); var info = store.readJSON(app.id + ".info", 1)||{}; if (files) eraseFiles(info); if (data) eraseData(info); } function eraseOne(app, files,data){ - E.showPrompt('Erase\n'+app.name+'?').then((v) => { + E.showPrompt(/*LANG*/'Erase\n'+app.name+'?').then((v) => { if (v) { Bangle.buzz(100, 1); eraseApp(app, files, data); @@ -82,7 +82,7 @@ function eraseOne(app, files,data){ }); } function eraseAll(apps, files,data) { - E.showPrompt('Erase all?').then((v) => { + E.showPrompt(/*LANG*/'Erase all?').then((v) => { if (v) { Bangle.buzz(100, 1); apps.forEach(app => eraseApp(app, files, data)); @@ -99,11 +99,11 @@ function showAppMenu(app) { '< Back': () => showApps(), }; if (app.hasData) { - appmenu['Erase Completely'] = () => eraseOne(app, true, true); - appmenu['Erase App,Keep Data'] = () => eraseOne(app, true, false); - appmenu['Only Erase Data'] = () => eraseOne(app, false, true); + appmenu[/*LANG*/'Erase Completely'] = () => eraseOne(app, true, true); + appmenu[/*LANG*/'Erase App,Keep Data'] = () => eraseOne(app, true, false); + appmenu[/*LANG*/'Only Erase Data'] = () => eraseOne(app, false, true); } else { - appmenu['Erase'] = () => eraseOne(app, true, false); + appmenu[/*LANG*/'Erase'] = () => eraseOne(app, true, false); } E.showMenu(appmenu); } @@ -111,7 +111,7 @@ function showAppMenu(app) { function showApps() { const appsmenu = { '': { - 'title': 'Apps', + 'title': /*LANG*/'Apps', }, '< Back': () => showMainMenu(), }; @@ -128,17 +128,17 @@ function showApps() { menu[app.name] = () => showAppMenu(app); return menu; }, appsmenu); - appsmenu['Erase All'] = () => { + appsmenu[/*LANG*/'Erase All'] = () => { E.showMenu({ - '': {'title': 'Erase All'}, - 'Erase Everything': () => eraseAll(list, true, true), - 'Erase Apps,Keep Data': () => eraseAll(list, true, false), - 'Only Erase Data': () => eraseAll(list, false, true), + '': {'title': /*LANG*/'Erase All'}, + /*LANG*/'Erase Everything': () => eraseAll(list, true, true), + /*LANG*/'Erase Apps,Keep Data': () => eraseAll(list, true, false), + /*LANG*/'Only Erase Data': () => eraseAll(list, false, true), '< Back': () => showApps(), }); }; } else { - appsmenu['...No Apps...'] = { + appsmenu[/*LANG*/'...No Apps...'] = { value: undefined, format: ()=> '', onchange: ()=> {} @@ -150,16 +150,16 @@ function showApps() { function showSortAppsMenu() { const sorterMenu = { '': { - 'title': 'App Sorter', + 'title': /*LANG*/'App Sorter', }, '< Back': () => showMainMenu(), - 'Sort: manually': ()=> showSortAppsManually(), - 'Sort: alph. ASC': () => { - E.showMessage('Sorting:\nAlphabetically\nascending ...'); + /*LANG*/'Sort: manually': ()=> showSortAppsManually(), + /*LANG*/'Sort: alph. ASC': () => { + E.showMessage(/*LANG*/'Sorting:\nAlphabetically\nascending ...'); sortAlphabet(false); }, 'Sort: alph. DESC': () => { - E.showMessage('Sorting:\nAlphabetically\ndescending ...'); + E.showMessage(/*LANG*/'Sorting:\nAlphabetically\ndescending ...'); sortAlphabet(true); } }; @@ -169,7 +169,7 @@ function showSortAppsMenu() { function showSortAppsManually() { const appsSorterMenu = { '': { - 'title': 'Sort: manually', + 'title': /*LANG*/'Sort: manually', }, '< Back': () => showSortAppsMenu(), }; @@ -186,7 +186,7 @@ function showSortAppsManually() { return menu; }, appsSorterMenu); } else { - appsSorterMenu['...No Apps...'] = { + appsSorterMenu[/*LANG*/'...No Apps...'] = { value: undefined, format: ()=> '', onchange: ()=> {} diff --git a/apps/files/metadata.json b/apps/files/metadata.json index ac73a7717..a53f914e6 100644 --- a/apps/files/metadata.json +++ b/apps/files/metadata.json @@ -1,7 +1,7 @@ { "id": "files", "name": "App Manager", - "version": "0.07", + "version": "0.08", "description": "Show currently installed apps, free space, and allow their deletion from the watch", "icon": "files.png", "tags": "tool,system,files", diff --git a/apps/gallery/ChangeLog b/apps/gallery/ChangeLog index 76db22053..0167bc1f9 100644 --- a/apps/gallery/ChangeLog +++ b/apps/gallery/ChangeLog @@ -1,2 +1,3 @@ 0.01: New app! -0.02: Submitted to app loader \ No newline at end of file +0.02: Submitted to app loader +0.03: Do not invert colors diff --git a/apps/gallery/app.js b/apps/gallery/app.js index ca9392f13..6fb2bdf49 100644 --- a/apps/gallery/app.js +++ b/apps/gallery/app.js @@ -35,7 +35,7 @@ function drawImage(fileName) { Bangle.setLCDBrightness(1); // Full brightness image = eval(storage.read(fileName)); // Sadly, the only reasonable way to do this - g.clear().reset().drawImage(image, 88, 88, { rotate: angle }); + g.clear().reset().setBgColor(0).setColor("#fff").drawImage(image, 88, 88, { rotate: angle }); } setWatch(info => { @@ -44,9 +44,9 @@ setWatch(info => { else angle = 0; Bangle.buzz(); - g.clear().reset().drawImage(image, 88, 88, { rotate: angle }) + g.clear().reset().setBgColor(0).setColor("#fff").drawImage(image, 88, 88, { rotate: angle }) } }, BTN1, { repeat: true }); // We don't load the widgets because there is no reasonable way to unload them -drawMenu(); \ No newline at end of file +drawMenu(); diff --git a/apps/gallery/metadata.json b/apps/gallery/metadata.json index 0dc8d1613..00ac42075 100644 --- a/apps/gallery/metadata.json +++ b/apps/gallery/metadata.json @@ -1,7 +1,7 @@ { "id": "gallery", "name": "Gallery", - "version": "0.02", + "version": "0.03", "description": "A gallery that lets you view images uploaded with the IDE (see README)", "readme": "README.md", "icon": "icon.png", diff --git a/apps/gpstrek/ChangeLog b/apps/gpstrek/ChangeLog index c36c23c72..849088c64 100644 --- a/apps/gpstrek/ChangeLog +++ b/apps/gpstrek/ChangeLog @@ -13,3 +13,4 @@ Reconstruct battery voltage by using calibrated batFullVoltage Averaging for smoothing compass headings Save state if route or waypoint has been chosen +0.09: Workaround a minifier issue allowing to install gpstrek with minification enabled diff --git a/apps/gpstrek/app.js b/apps/gpstrek/app.js index b3ec79fd2..1f46ae2b4 100644 --- a/apps/gpstrek/app.js +++ b/apps/gpstrek/app.js @@ -259,7 +259,8 @@ let getCompassSlice = function(compassDataSource){ if (compassDataSource.getPoints){ - for (let p of compassDataSource.getPoints()){ + let points = compassDataSource.getPoints(); //storing this in a variable works around a minifier bug causing a problem in the next line: for(let a of a.getPoints()) + for (let p of points){ g.reset(); var bpos = p.bearing - lastDrawnValue; if (bpos>180) bpos -=360; @@ -285,7 +286,8 @@ let getCompassSlice = function(compassDataSource){ } } if (compassDataSource.getMarkers){ - for (let m of compassDataSource.getMarkers()){ + let markers = compassDataSource.getMarkers(); //storing this in a variable works around a minifier bug causing a problem in the next line: for(let a of a.getMarkers()) + for (let m of markers){ g.reset(); g.setColor(m.fillcolor); let mpos = m.xpos * width; diff --git a/apps/gpstrek/metadata.json b/apps/gpstrek/metadata.json index 3e27a3247..2e2e481af 100644 --- a/apps/gpstrek/metadata.json +++ b/apps/gpstrek/metadata.json @@ -1,7 +1,7 @@ { "id": "gpstrek", "name": "GPS Trekking", - "version": "0.08", + "version": "0.09", "description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!", "icon": "icon.png", "screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}], diff --git a/apps/ha/ChangeLog b/apps/ha/ChangeLog index a4865be3f..f9ca3c16d 100644 --- a/apps/ha/ChangeLog +++ b/apps/ha/ChangeLog @@ -2,4 +2,5 @@ 0.02: Includeas the ha.lib.js library that can be used by other apps or clocks. 0.03: Added clkinfo for clocks. 0.04: Feedback if clkinfo run is called. -0.05: Clkinfo improvements. \ No newline at end of file +0.05: Clkinfo improvements. +0.06: Updated clkinfo icon. \ No newline at end of file diff --git a/apps/ha/ha.clkinfo.js b/apps/ha/ha.clkinfo.js index 1b1e468d7..d6a0f72a0 100644 --- a/apps/ha/ha.clkinfo.js +++ b/apps/ha/ha.clkinfo.js @@ -4,7 +4,7 @@ var haItems = { name: "Home", - img: atob("GBiBAf/////////n///D//+B//8A//48T/wkD/gkD/A8D+AYB8AYA4eZ4QyZMOyZN+fb5+D/B+B+B+A8B+AYB+AYB+AYB+AYB+A8Bw=="), + img: atob("GBiBAAAAAAAAAAAAAAAYAAA+AAB+AADD4AHb4APD4Afn8A/n+BxmOD0mnA0ksAwAMA+B8A/D8A/n8A/n8A/n8A/n8AAAAAAAAAAAAA=="), items: [] }; diff --git a/apps/ha/metadata.json b/apps/ha/metadata.json index fad052544..089450f55 100644 --- a/apps/ha/metadata.json +++ b/apps/ha/metadata.json @@ -1,7 +1,7 @@ { "id": "ha", "name": "HomeAssistant", - "version": "0.05", + "version": "0.06", "description": "Integrates your BangleJS into HomeAssistant.", "icon": "ha.png", "type": "app", diff --git a/apps/hourstrike/metadata.json b/apps/hourstrike/metadata.json index 614db54e4..d0ddb511a 100644 --- a/apps/hourstrike/metadata.json +++ b/apps/hourstrike/metadata.json @@ -11,7 +11,8 @@ "storage": [ {"name":"hourstrike.app.js","url":"app.js"}, {"name":"hourstrike.boot.js","url":"boot.js"}, - {"name":"hourstrike.img","url":"app-icon.js","evaluate":true}, + {"name":"hourstrike.img","url":"app-icon.js","evaluate":true} + ], "data" : [ {"name":"hourstrike.json","url":"hourstrike.json"} ] } diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog index 62956e8cd..b55ba8930 100644 --- a/apps/hrm/ChangeLog +++ b/apps/hrm/ChangeLog @@ -8,3 +8,4 @@ 0.08: Don't force backlight on/watch unlocked on Bangle 2 0.09: Grey out BPM until confidence is over 50% 0.10: Autoscale raw graph to maximum value seen +0.11: Automatic translation of strings. diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js index 386341e6d..2e5a720e5 100644 --- a/apps/hrm/heartrate.js +++ b/apps/hrm/heartrate.js @@ -37,7 +37,7 @@ function updateHrm(){ var px = g.getWidth()/2; g.setFontAlign(0,-1); g.clearRect(0,24,g.getWidth(),80); - g.setFont("6x8").drawString("Confidence "+(hrmInfo.confidence || "--")+"%", px, 70); + g.setFont("6x8").drawString(/*LANG*/"Confidence "+(hrmInfo.confidence || "--")+"%", px, 70); updateScale(); @@ -46,7 +46,7 @@ function updateHrm(){ g.setFontVector(40).setColor(hrmInfo.confidence > 50 ? g.theme.fg : "#888").drawString(str,px,45); px += g.stringWidth(str)/2; g.setFont("6x8").setColor(g.theme.fg); - g.drawString("BPM",px+15,45); + g.drawString(/*LANG*/"BPM",px+15,45); } function updateScale(){ @@ -101,7 +101,7 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); g.setColor(g.theme.fg); g.reset().setFont("6x8",2).setFontAlign(0,-1); -g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); +g.drawString(/*LANG*/"Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); countDown(); diff --git a/apps/hrm/metadata.json b/apps/hrm/metadata.json index c5a5f4f4d..f254b5d23 100644 --- a/apps/hrm/metadata.json +++ b/apps/hrm/metadata.json @@ -1,7 +1,7 @@ { "id": "hrm", "name": "Heart Rate Monitor", - "version": "0.10", + "version": "0.11", "description": "Measure your heart rate and see live sensor data", "icon": "heartrate.png", "tags": "health", diff --git a/apps/hworldclock/ChangeLog b/apps/hworldclock/ChangeLog index 8c1517842..13e3fa809 100644 --- a/apps/hworldclock/ChangeLog +++ b/apps/hworldclock/ChangeLog @@ -9,3 +9,7 @@ 0.23: Added note to configure position in "my location" if not done yet. Small fixes. 0.24: Added fast load 0.25: Minor code optimization +0.26: BJS2: Swipe down to rotate 180 degree +0.27: BJS2: Changed swipe down to swipe up +0.28: Reverted changes to implementation of 0.25 +0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/hworldclock/README.md b/apps/hworldclock/README.md index 25cc368ca..93780bc2d 100644 --- a/apps/hworldclock/README.md +++ b/apps/hworldclock/README.md @@ -14,7 +14,6 @@ Provide names and the UTC offsets for up to three other timezones in the app sto The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the app store and update. Currently the clock only supports 24 hour time format for the additional time zones. - ## Requests Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if you have feature requests or notice bugs. diff --git a/apps/hworldclock/app.js b/apps/hworldclock/app.js index c80b712da..6bd30be8e 100644 --- a/apps/hworldclock/app.js +++ b/apps/hworldclock/app.js @@ -15,7 +15,7 @@ require("Font5x9Numeric7Seg").add(Graphics); require("FontTeletext10x18Ascii").add(Graphics); // Font for single secondary time -const secondaryTimeFontSize = 4; +const secondaryTimeFontSize = 4; const secondaryTimeZoneFontSize = 2; // Font / columns for multiple secondary times @@ -39,14 +39,14 @@ const yposWorld = big ? 170 : 120; const OFFSET_TIME_ZONE = 0; const OFFSET_HOURS = 1; -var PosInterval = 0; +var PosInterval = 0; var offsets = require("Storage").readJSON("hworldclock.settings.json") || []; //=======Sun setting = require("Storage").readJSON("setting.json",1); E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ -SunCalc = require("hsuncalc.js"); +SunCalc = require("suncalc"); // from modules folder const LOCATION_FILE = "mylocation.json"; var rise = "read"; var set = "..."; @@ -87,7 +87,7 @@ const mockOffsets = { //offsets = mockOffsets.fourOffsets; // should render in columns // END TESTING CODE - + // Load settings function loadMySettings() { @@ -141,11 +141,9 @@ function getCurrentTimeFromOffset(dt, offset) { function updatePos() { coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":0,"lon":0,"location":"-"}; //{"lat":53.3,"lon":10.1,"location":"Pattensen"}; if (coord.lat != 0 && coord.lon != 0) { - //pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon); times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon); rise = "^" + times.sunrise.toString().split(" ")[4].substr(0,5); set = "v" + times.sunset.toString().split(" ")[4].substr(0,5); - //noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon); } else { rise = null; set = null; @@ -179,7 +177,7 @@ function drawSeconds() { //console.log(seconds); if (Bangle.isLocked() && secondsMode != "always") seconds = seconds.slice(0, -1) + ':::'; // we use :: as the font does not have an x //console.log(seconds); - g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true); + g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true); queueDrawSeconds(); } @@ -196,18 +194,18 @@ function draw() { let time = da[4].split(":"); let hours = time[0], minutes = time[1]; - - + + if (_12hour){ //do 12 hour stuff if (hours > 12) { ampm = "PM"; - hours = hours - 12; - if (hours < 10) hours = doublenum(hours); + hours = hours - 12; + if (hours < 10) hours = doublenum(hours); } else { - ampm = "AM"; - } - } + ampm = "AM"; + } + } //g.setFont(font, primaryTimeFontSize); g.setFont("5x9Numeric7Seg",primaryTimeFontSize); @@ -221,18 +219,18 @@ function draw() { g.setColor(g.theme.fg); } g.drawString(`${hours}:${minutes}`, xyCenter-10, yposTime, true); - + // am / PM ? if (_12hour){ //do 12 hour stuff //let ampm = require("locale").medidian(new Date()); Not working g.setFont("Vector", 17); g.drawString(ampm, xyCenterSeconds, yAmPm, true); - } + } if (secondsMode != "none") drawSeconds(); // To make sure... - - // draw Day, name of month, Date + + // draw Day, name of month, Date //DATE let localDate = require("locale").date(new Date(), 1); localDate = localDate.substring(0, localDate.length - 5); @@ -251,7 +249,7 @@ function draw() { if (offsets.length === 1) { - let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)]; + let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)]; // For a single secondary timezone, draw it bigger and drop time zone to second line const xOffset = 30; g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true); @@ -277,7 +275,7 @@ function draw() { g.setFontAlign(-1, 0).setFont("Vector",12).drawString(`${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw rise g.setFontAlign(1, 0).drawString(`${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw set } else { - g.setFontAlign(-1, 0).setFont("Vector",11).drawString("set city in \'my location\' app!", 10, 3 + yposWorld + 3 * 15, true); + g.setFontAlign(-1, 0).setFont("Vector",11).drawString("set city in \'my location\' app!", 10, 3 + yposWorld + 3 * 15, true); } } //debug settings @@ -287,7 +285,7 @@ function draw() { //g.drawString(colorWhenDark, xcol2, 3 + yposWorld + 3 * 15, true); queueDraw(); - + if (secondsMode != "none") queueDrawSeconds(); } @@ -307,7 +305,7 @@ Bangle.setUI({ if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; + drawTimeout = undefined; }}); Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -328,15 +326,15 @@ if (!Bangle.isLocked()) { // Initial state if (secondsMode != "none") { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; - } + } if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; draw(); // draw immediately, queue redraw - + }else{ if (secondsMode == "always") secondsTimeout = 1000; if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000; - + if (secondsMode != "none") { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; @@ -350,9 +348,9 @@ if (!Bangle.isLocked()) { // Initial state updatePos(); } draw(); // draw immediately, queue redraw - + } - + Bangle.on('lock',on=>{ if (!on) { // UNlocked @@ -366,7 +364,7 @@ Bangle.on('lock',on=>{ if (secondsMode != "none") { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; - } + } if (drawTimeout) clearTimeout(drawTimeout); drawTimeout = undefined; @@ -375,7 +373,7 @@ Bangle.on('lock',on=>{ if (secondsMode == "always") secondsTimeout = 1000; if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000; - + if (secondsMode != "none") { if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds); drawTimeoutSeconds = undefined; @@ -388,7 +386,7 @@ Bangle.on('lock',on=>{ PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins updatePos(); } - draw(); // draw immediately, queue redraw + draw(); // draw immediately, queue redraw } }); -} \ No newline at end of file +} diff --git a/apps/hworldclock/hsuncalc.js b/apps/hworldclock/hsuncalc.js deleted file mode 100644 index b1af0a0d9..000000000 --- a/apps/hworldclock/hsuncalc.js +++ /dev/null @@ -1,298 +0,0 @@ -/* Module suncalc.js - (c) 2011-2015, Vladimir Agafonkin - SunCalc is a JavaScript library for calculating sun/moon position and light phases. - https://github.com/mourner/suncalc - -PB: Usage: -E.setTimeZone(2); // 1 = MEZ, 2 = MESZ -SunCalc = require("suncalc.js"); -pos = SunCalc.getPosition(Date.now(), 53.3, 10.1); -times = SunCalc.getTimes(Date.now(), 53.3, 10.1); -rise = times.sunrise; // Date object -rise_str = rise.getHours() + ':' + rise.getMinutes(); //hh:mm -*/ -var exports={}; - -// shortcuts for easier to read formulas - -var PI = Math.PI, - sin = Math.sin, - cos = Math.cos, - tan = Math.tan, - asin = Math.asin, - atan = Math.atan2, - acos = Math.acos, - rad = PI / 180; - -// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas - -// date/time constants and conversions - -var dayMs = 1000 * 60 * 60 * 24, - J1970 = 2440588, - J2000 = 2451545; - -function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } -function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } // PB: onece removed + 0.5; included it again 4 Jan 2021 -function toDays(date) { return toJulian(date) - J2000; } - - -// general calculations for position - -var e = rad * 23.4397; // obliquity of the Earth - -function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); } -function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } - -function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); } -function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); } - -function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; } - -function astroRefraction(h) { - if (h < 0) // the following formula works for positive altitudes only. - h = 0; // if h = -0.08901179 a div/0 would occur. - - // formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - // 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: - return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179)); -} - -// general sun calculations - -function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } - -function eclipticLongitude(M) { - - var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center - P = rad * 102.9372; // perihelion of the Earth - - return M + C + P + PI; -} - -function sunCoords(d) { - - var M = solarMeanAnomaly(d), - L = eclipticLongitude(M); - - return { - dec: declination(L, 0), - ra: rightAscension(L, 0) - }; -} - -// calculates sun position for a given date and latitude/longitude - -exports.getPosition = function (date, lat, lng) { - - var lw = rad * -lng, - phi = rad * lat, - d = toDays(date), - - c = sunCoords(d), - H = siderealTime(d, lw) - c.ra; - - return { - azimuth: Math.round((azimuth(H, phi, c.dec) / rad + 180) % 360), // PB: converted to deg - altitude: Math.round( altitude(H, phi, c.dec) / rad) // PB: converted to deg - }; -}; - - -// sun times configuration (angle, morning name, evening name) - -var times = [ - [-0.833, 'sunrise', 'sunset' ] -]; - -// calculations for sun times -var J0 = 0.0009; - -function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } - -function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } -function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } - -function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } -function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } - -// returns set time for the given sun altitude -function getSetJ(h, lw, phi, dec, n, M, L) { - - var w = hourAngle(h, phi, dec), - a = approxTransit(w, lw, n); - return solarTransitJ(a, M, L); -} - - -// calculates sun times for a given date, latitude/longitude, and, optionally, -// the observer height (in meters) relative to the horizon - -exports.getTimes = function (date, lat, lng, height) { - - height = height || 0; - - var lw = rad * -lng, - phi = rad * lat, - - dh = observerAngle(height), - - d = toDays(date), - n = julianCycle(d, lw), - ds = approxTransit(0, lw, n), - - M = solarMeanAnomaly(ds), - L = eclipticLongitude(M), - dec = declination(L, 0), - - Jnoon = solarTransitJ(ds, M, L), - - i, len, time, h0, Jset, Jrise; - - - var result = { - solarNoon: fromJulian(Jnoon), - nadir: fromJulian(Jnoon - 0.5) - }; - - for (i = 0, len = times.length; i < len; i += 1) { - time = times[i]; - h0 = (time[0] + dh) * rad; - - Jset = getSetJ(h0, lw, phi, dec, n, M, L); - Jrise = Jnoon - (Jset - Jnoon); - - result[time[1]] = fromJulian(Jrise); - result[time[2]] = fromJulian(Jset); - } - - return result; -}; - - -// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas - -function moonCoords(d) { // geocentric ecliptic coordinates of the moon - - var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude - M = rad * (134.963 + 13.064993 * d), // mean anomaly - F = rad * (93.272 + 13.229350 * d), // mean distance - - l = L + rad * 6.289 * sin(M), // longitude - b = rad * 5.128 * sin(F), // latitude - dt = 385001 - 20905 * cos(M); // distance to the moon in km - - return { - ra: rightAscension(l, b), - dec: declination(l, b), - dist: dt - }; -} - -getMoonPosition = function (date, lat, lng) { - - var lw = rad * -lng, - phi = rad * lat, - d = toDays(date), - - c = moonCoords(d), - H = siderealTime(d, lw) - c.ra, - h = altitude(H, phi, c.dec), - // formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H)); - - h = h + astroRefraction(h); // altitude correction for refraction - - return { - azimuth: azimuth(H, phi, c.dec), - altitude: h, - distance: c.dist, - parallacticAngle: pa - }; -}; - - -// calculations for illumination parameters of the moon, -// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and -// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. - -getMoonIllumination = function (date) { - - var d = toDays(date || new Date()), - s = sunCoords(d), - m = moonCoords(d), - - sdist = 149598000, // distance from Earth to Sun in km - - phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)), - inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)), - angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) - - cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra)); - - return { - fraction: (1 + cos(inc)) / 2, - phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI, - angle: angle - }; -}; - - -function hoursLater(date, h) { - return new Date(date.valueOf() + h * dayMs / 24); -} - -// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article - -getMoonTimes = function (date, lat, lng, inUTC) { - var t = new Date(date); - if (inUTC) t.setUTCHours(0, 0, 0, 0); - else t.setHours(0, 0, 0, 0); - - var hc = 0.133 * rad, - h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc, - h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx; - - // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) - for (var i = 1; i <= 24; i += 2) { - h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc; - h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc; - - a = (h0 + h2) / 2 - h1; - b = (h2 - h0) / 2; - xe = -b / (2 * a); - ye = (a * xe + b) * xe + h1; - d = b * b - 4 * a * h1; - roots = 0; - - if (d >= 0) { - dx = Math.sqrt(d) / (Math.abs(a) * 2); - x1 = xe - dx; - x2 = xe + dx; - if (Math.abs(x1) <= 1) roots++; - if (Math.abs(x2) <= 1) roots++; - if (x1 < -1) x1 = x2; - } - - if (roots === 1) { - if (h0 < 0) rise = i + x1; - else set = i + x1; - - } else if (roots === 2) { - rise = i + (ye < 0 ? x2 : x1); - set = i + (ye < 0 ? x1 : x2); - } - - if (rise && set) break; - - h0 = h2; - } - - var result = {}; - - if (rise) result.rise = hoursLater(t, rise); - if (set) result.set = hoursLater(t, set); - - if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; - - return result; -}; \ No newline at end of file diff --git a/apps/hworldclock/metadata.json b/apps/hworldclock/metadata.json index e26599373..3b633ead7 100644 --- a/apps/hworldclock/metadata.json +++ b/apps/hworldclock/metadata.json @@ -2,7 +2,7 @@ "id": "hworldclock", "name": "Hanks World Clock", "shortName": "Hanks World Clock", - "version": "0.25", + "version": "0.29", "description": "Current time zone plus up to three others", "allow_emulator":true, "icon": "app.png", @@ -15,11 +15,10 @@ "storage": [ {"name":"hworldclock.app.js","url":"app.js"}, {"name":"hworldclock.img","url":"hworldclock-icon.js","evaluate":true}, - {"name":"hworldclock.settings.js","url":"settings.js"}, - {"name":"hsuncalc.js","url":"hsuncalc.js"} + {"name":"hworldclock.settings.js","url":"settings.js"} ], "data": [ {"name":"hworldclock.settings.json"}, - {"name":"hworldclock.json"} + {"name":"hworldclock.json"} ] -} \ No newline at end of file +} diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js index 90c6163cd..dc21d7162 100644 --- a/apps/imageclock/app.js +++ b/apps/imageclock/app.js @@ -622,12 +622,12 @@ s.pl = {}; endPerfLog("fullDraw", true); if (!Bangle.uiRemove){ - setUi(); + setUi(); // Calls Bangle.setUI() (this comment also fixes lint warning) let orig = Bangle.drawWidgets; Bangle.drawWidgets = ()=>{}; Bangle.loadWidgets(); Bangle.drawWidgets = orig; - require("widget_utils").swipeOn(); + require("widget_utils").swipeOn(0); Bangle.drawWidgets(); } }).catch((e)=>{ diff --git a/apps/kbswipe/ChangeLog b/apps/kbswipe/ChangeLog index 1804c4a89..568db372e 100644 --- a/apps/kbswipe/ChangeLog +++ b/apps/kbswipe/ChangeLog @@ -3,3 +3,4 @@ 0.03: Positioning of marker now takes the height of the widget field into account. 0.04: Fix issue if going back without typing. 0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat. +0.06: Support input of numbers and uppercase characters. diff --git a/apps/kbswipe/README.md b/apps/kbswipe/README.md index 3f5575777..c4c3fbd99 100644 --- a/apps/kbswipe/README.md +++ b/apps/kbswipe/README.md @@ -4,6 +4,10 @@ A library that provides the ability to input text by swiping PalmOS Graffiti-sty To get a legend of available characters, just tap the screen. +To switch between the input of alphabetic and numeric characters tap the widget which displays either "123" or "ABC". + +To switch between lowercase and uppercase characters do an up swipe. + ![](key.png) ## Usage diff --git a/apps/kbswipe/lib.js b/apps/kbswipe/lib.js index fe5f7e977..75a434999 100644 --- a/apps/kbswipe/lib.js +++ b/apps/kbswipe/lib.js @@ -1,47 +1,68 @@ +exports.INPUT_MODE_ALPHA = 0; +exports.INPUT_MODE_NUM = 1; + /* To make your own strokes, type: Bangle.on('stroke',print) on the left of the IDE, then do a stroke and copy out the Uint8Array line */ -exports.getStrokes = function(cb) { - cb("a", new Uint8Array([58, 159, 58, 155, 62, 144, 69, 127, 77, 106, 86, 90, 94, 77, 101, 68, 108, 62, 114, 59, 121, 59, 133, 61, 146, 70, 158, 88, 169, 107, 176, 124, 180, 135, 183, 144, 185, 152])); - cb("b", new Uint8Array([51, 47, 51, 77, 56, 123, 60, 151, 65, 163, 68, 164, 68, 144, 67, 108, 67, 76, 72, 43, 104, 51, 121, 74, 110, 87, 109, 95, 131, 117, 131, 140, 109, 152, 88, 157])); - cb("c", new Uint8Array([153, 62, 150, 62, 145, 62, 136, 62, 123, 62, 106, 65, 85, 70, 65, 75, 50, 82, 42, 93, 37, 106, 36, 119, 36, 130, 40, 140, 49, 147, 61, 153, 72, 156, 85, 157, 106, 158, 116, 158])); - cb("d", new Uint8Array([57, 178, 57, 176, 55, 171, 52, 163, 50, 154, 49, 146, 47, 135, 45, 121, 44, 108, 44, 97, 44, 85, 44, 75, 44, 66, 44, 58, 44, 48, 44, 38, 46, 31, 48, 26, 58, 21, 75, 20, 99, 26, 120, 35, 136, 51, 144, 70, 144, 88, 137, 110, 124, 131, 106, 145, 88, 153])); - cb("e", new Uint8Array([150, 72, 141, 69, 114, 68, 79, 69, 48, 77, 32, 81, 31, 85, 46, 91, 73, 95, 107, 100, 114, 103, 83, 117, 58, 134, 66, 143, 105, 148, 133, 148, 144, 148])); - cb("f", new Uint8Array([157, 52, 155, 52, 148, 52, 137, 52, 124, 52, 110, 52, 96, 52, 83, 52, 74, 52, 67, 52, 61, 52, 57, 52, 55, 52, 52, 52, 52, 54, 52, 58, 52, 64, 54, 75, 58, 97, 59, 117, 60, 130])); - cb("g", new Uint8Array([160, 66, 153, 62, 129, 58, 90, 56, 58, 57, 38, 65, 31, 86, 43, 125, 69, 152, 116, 166, 145, 154, 146, 134, 112, 116, 85, 108, 97, 106, 140, 106, 164, 106])); - cb("h", new Uint8Array([58, 50, 58, 55, 58, 64, 58, 80, 58, 102, 58, 122, 58, 139, 58, 153, 58, 164, 58, 171, 58, 177, 58, 179, 58, 181, 58, 180, 58, 173, 58, 163, 59, 154, 61, 138, 64, 114, 68, 95, 72, 84, 80, 79, 91, 79, 107, 82, 123, 93, 137, 111, 145, 130, 149, 147, 150, 154, 150, 159])); - cb("i", new Uint8Array([89, 48, 89, 49, 89, 51, 89, 55, 89, 60, 89, 68, 89, 78, 89, 91, 89, 103, 89, 114, 89, 124, 89, 132, 89, 138, 89, 144, 89, 148, 89, 151, 89, 154, 89, 156, 89, 157, 89, 158])); - cb("j", new Uint8Array([130, 57, 130, 61, 130, 73, 130, 91, 130, 113, 130, 133, 130, 147, 130, 156, 130, 161, 130, 164, 130, 166, 129, 168, 127, 168, 120, 168, 110, 168, 91, 167, 81, 167, 68, 167])); - cb("k", new Uint8Array([149, 63, 147, 68, 143, 76, 136, 89, 126, 106, 114, 123, 100, 136, 86, 147, 72, 153, 57, 155, 45, 152, 36, 145, 29, 131, 26, 117, 26, 104, 27, 93, 30, 86, 35, 80, 45, 77, 62, 80, 88, 96, 113, 116, 130, 131, 140, 142, 145, 149, 148, 153])); - cb("l", new Uint8Array([42, 55, 42, 59, 42, 69, 44, 87, 44, 107, 44, 128, 44, 143, 44, 156, 44, 163, 44, 167, 44, 169, 45, 170, 49, 170, 59, 169, 76, 167, 100, 164, 119, 162, 139, 160, 163, 159])); - cb("m", new Uint8Array([49, 165, 48, 162, 46, 156, 44, 148, 42, 138, 42, 126, 42, 113, 43, 101, 45, 91, 47, 82, 49, 75, 51, 71, 54, 70, 57, 70, 61, 74, 69, 81, 75, 91, 84, 104, 94, 121, 101, 132, 103, 137, 106, 130, 110, 114, 116, 92, 125, 75, 134, 65, 139, 62, 144, 66, 148, 83, 151, 108, 155, 132, 157, 149])); - cb("n", new Uint8Array([50, 165, 50, 160, 50, 153, 50, 140, 50, 122, 50, 103, 50, 83, 50, 65, 50, 52, 50, 45, 50, 43, 52, 52, 57, 67, 66, 90, 78, 112, 93, 131, 104, 143, 116, 152, 127, 159, 135, 160, 141, 150, 148, 125, 154, 96, 158, 71, 161, 56, 162, 49])); - cb("o", new Uint8Array([107, 58, 104, 58, 97, 61, 87, 68, 75, 77, 65, 88, 58, 103, 54, 116, 53, 126, 55, 135, 61, 143, 75, 149, 91, 150, 106, 148, 119, 141, 137, 125, 143, 115, 146, 104, 146, 89, 142, 78, 130, 70, 116, 65, 104, 62])); - cb("p", new Uint8Array([52, 59, 52, 64, 54, 73, 58, 88, 61, 104, 65, 119, 67, 130, 69, 138, 71, 145, 71, 147, 71, 148, 71, 143, 70, 133, 68, 120, 67, 108, 67, 97, 67, 89, 68, 79, 72, 67, 83, 60, 99, 58, 118, 58, 136, 63, 146, 70, 148, 77, 145, 84, 136, 91, 121, 95, 106, 97, 93, 97, 82, 97])); - cb("q", new Uint8Array([95, 59, 93, 59, 88, 59, 79, 59, 68, 61, 57, 67, 50, 77, 48, 89, 48, 103, 50, 117, 55, 130, 65, 140, 76, 145, 85, 146, 94, 144, 101, 140, 105, 136, 106, 127, 106, 113, 100, 98, 92, 86, 86, 79, 84, 75, 84, 72, 91, 69, 106, 67, 126, 67, 144, 67, 158, 67, 168, 67, 173, 67, 177, 67])); - cb("r", new Uint8Array([53, 49, 53, 62, 53, 91, 53, 127, 53, 146, 53, 147, 53, 128, 53, 94, 53, 69, 62, 44, 82, 42, 94, 50, 92, 68, 82, 85, 77, 93, 80, 102, 95, 119, 114, 134, 129, 145, 137, 150])); - cb("s", new Uint8Array([159, 72, 157, 70, 155, 68, 151, 66, 145, 63, 134, 60, 121, 58, 108, 56, 96, 55, 83, 55, 73, 55, 64, 56, 57, 60, 52, 65, 49, 71, 49, 76, 50, 81, 55, 87, 71, 94, 94, 100, 116, 104, 131, 108, 141, 114, 145, 124, 142, 135, 124, 146, 97, 153, 70, 157, 52, 158])); - cb("t", new Uint8Array([45, 55, 48, 55, 55, 55, 72, 55, 96, 55, 120, 55, 136, 55, 147, 55, 152, 55, 155, 55, 157, 55, 158, 56, 158, 60, 156, 70, 154, 86, 151, 102, 150, 114, 148, 125, 148, 138, 148, 146])); - cb("u", new Uint8Array([35, 52, 35, 59, 35, 73, 35, 90, 36, 114, 38, 133, 42, 146, 49, 153, 60, 157, 73, 158, 86, 156, 100, 152, 112, 144, 121, 131, 127, 114, 132, 97, 134, 85, 135, 73, 136, 61, 136, 56])); - cb("v", new Uint8Array([36, 55, 37, 59, 40, 68, 45, 83, 51, 100, 58, 118, 64, 132, 69, 142, 71, 149, 73, 156, 76, 158, 77, 160, 77, 159, 80, 151, 82, 137, 84, 122, 86, 111, 90, 91, 91, 78, 91, 68, 91, 63, 92, 61, 97, 61, 111, 61, 132, 61, 150, 61, 162, 61])); - cb("w", new Uint8Array([33, 58, 34, 81, 39, 127, 44, 151, 48, 161, 52, 162, 57, 154, 61, 136, 65, 115, 70, 95, 76, 95, 93, 121, 110, 146, 119, 151, 130, 129, 138, 84, 140, 56, 140, 45])); - cb("x", new Uint8Array([56, 63, 56, 67, 57, 74, 60, 89, 66, 109, 74, 129, 85, 145, 96, 158, 107, 164, 117, 167, 128, 164, 141, 155, 151, 140, 159, 122, 166, 105, 168, 89, 170, 81, 170, 73, 169, 66, 161, 63, 141, 68, 110, 83, 77, 110, 55, 134, 47, 145])); - cb("y", new Uint8Array([42, 56, 42, 70, 48, 97, 62, 109, 85, 106, 109, 90, 126, 65, 134, 47, 137, 45, 137, 75, 127, 125, 98, 141, 70, 133, 65, 126, 92, 137, 132, 156, 149, 166])); - cb("z", new Uint8Array([29, 62, 35, 62, 43, 62, 63, 62, 87, 62, 110, 62, 125, 62, 134, 62, 138, 62, 136, 63, 122, 68, 103, 77, 85, 91, 70, 107, 59, 120, 50, 132, 47, 138, 43, 143, 41, 148, 42, 151, 53, 155, 80, 157, 116, 158, 146, 158, 163, 158])); +exports.getStrokes = function(mode, cb) { + if (mode === exports.INPUT_MODE_ALPHA) { + cb("a", new Uint8Array([58, 159, 58, 155, 62, 144, 69, 127, 77, 106, 86, 90, 94, 77, 101, 68, 108, 62, 114, 59, 121, 59, 133, 61, 146, 70, 158, 88, 169, 107, 176, 124, 180, 135, 183, 144, 185, 152])); + cb("b", new Uint8Array([51, 47, 51, 77, 56, 123, 60, 151, 65, 163, 68, 164, 68, 144, 67, 108, 67, 76, 72, 43, 104, 51, 121, 74, 110, 87, 109, 95, 131, 117, 131, 140, 109, 152, 88, 157])); + cb("c", new Uint8Array([153, 62, 150, 62, 145, 62, 136, 62, 123, 62, 106, 65, 85, 70, 65, 75, 50, 82, 42, 93, 37, 106, 36, 119, 36, 130, 40, 140, 49, 147, 61, 153, 72, 156, 85, 157, 106, 158, 116, 158])); + cb("d", new Uint8Array([57, 178, 57, 176, 55, 171, 52, 163, 50, 154, 49, 146, 47, 135, 45, 121, 44, 108, 44, 97, 44, 85, 44, 75, 44, 66, 44, 58, 44, 48, 44, 38, 46, 31, 48, 26, 58, 21, 75, 20, 99, 26, 120, 35, 136, 51, 144, 70, 144, 88, 137, 110, 124, 131, 106, 145, 88, 153])); + cb("e", new Uint8Array([150, 72, 141, 69, 114, 68, 79, 69, 48, 77, 32, 81, 31, 85, 46, 91, 73, 95, 107, 100, 114, 103, 83, 117, 58, 134, 66, 143, 105, 148, 133, 148, 144, 148])); + cb("f", new Uint8Array([157, 52, 155, 52, 148, 52, 137, 52, 124, 52, 110, 52, 96, 52, 83, 52, 74, 52, 67, 52, 61, 52, 57, 52, 55, 52, 52, 52, 52, 54, 52, 58, 52, 64, 54, 75, 58, 97, 59, 117, 60, 130])); + cb("g", new Uint8Array([160, 66, 153, 62, 129, 58, 90, 56, 58, 57, 38, 65, 31, 86, 43, 125, 69, 152, 116, 166, 145, 154, 146, 134, 112, 116, 85, 108, 97, 106, 140, 106, 164, 106])); + cb("h", new Uint8Array([58, 50, 58, 55, 58, 64, 58, 80, 58, 102, 58, 122, 58, 139, 58, 153, 58, 164, 58, 171, 58, 177, 58, 179, 58, 181, 58, 180, 58, 173, 58, 163, 59, 154, 61, 138, 64, 114, 68, 95, 72, 84, 80, 79, 91, 79, 107, 82, 123, 93, 137, 111, 145, 130, 149, 147, 150, 154, 150, 159])); + cb("i", new Uint8Array([89, 48, 89, 49, 89, 51, 89, 55, 89, 60, 89, 68, 89, 78, 89, 91, 89, 103, 89, 114, 89, 124, 89, 132, 89, 138, 89, 144, 89, 148, 89, 151, 89, 154, 89, 156, 89, 157, 89, 158])); + cb("j", new Uint8Array([130, 57, 130, 61, 130, 73, 130, 91, 130, 113, 130, 133, 130, 147, 130, 156, 130, 161, 130, 164, 130, 166, 129, 168, 127, 168, 120, 168, 110, 168, 91, 167, 81, 167, 68, 167])); + cb("k", new Uint8Array([149, 63, 147, 68, 143, 76, 136, 89, 126, 106, 114, 123, 100, 136, 86, 147, 72, 153, 57, 155, 45, 152, 36, 145, 29, 131, 26, 117, 26, 104, 27, 93, 30, 86, 35, 80, 45, 77, 62, 80, 88, 96, 113, 116, 130, 131, 140, 142, 145, 149, 148, 153])); + cb("l", new Uint8Array([42, 55, 42, 59, 42, 69, 44, 87, 44, 107, 44, 128, 44, 143, 44, 156, 44, 163, 44, 167, 44, 169, 45, 170, 49, 170, 59, 169, 76, 167, 100, 164, 119, 162, 139, 160, 163, 159])); + cb("m", new Uint8Array([49, 165, 48, 162, 46, 156, 44, 148, 42, 138, 42, 126, 42, 113, 43, 101, 45, 91, 47, 82, 49, 75, 51, 71, 54, 70, 57, 70, 61, 74, 69, 81, 75, 91, 84, 104, 94, 121, 101, 132, 103, 137, 106, 130, 110, 114, 116, 92, 125, 75, 134, 65, 139, 62, 144, 66, 148, 83, 151, 108, 155, 132, 157, 149])); + cb("n", new Uint8Array([50, 165, 50, 160, 50, 153, 50, 140, 50, 122, 50, 103, 50, 83, 50, 65, 50, 52, 50, 45, 50, 43, 52, 52, 57, 67, 66, 90, 78, 112, 93, 131, 104, 143, 116, 152, 127, 159, 135, 160, 141, 150, 148, 125, 154, 96, 158, 71, 161, 56, 162, 49])); + cb("o", new Uint8Array([107, 58, 104, 58, 97, 61, 87, 68, 75, 77, 65, 88, 58, 103, 54, 116, 53, 126, 55, 135, 61, 143, 75, 149, 91, 150, 106, 148, 119, 141, 137, 125, 143, 115, 146, 104, 146, 89, 142, 78, 130, 70, 116, 65, 104, 62])); + cb("p", new Uint8Array([29, 47, 29, 55, 29, 75, 29, 110, 29, 145, 29, 165, 29, 172, 29, 164, 30, 149, 37, 120, 50, 91, 61, 74, 72, 65, 85, 61, 103, 61, 118, 63, 126, 69, 129, 76, 130, 87, 126, 98, 112, 108, 97, 114, 87, 116])); + cb("q", new Uint8Array([95, 59, 93, 59, 88, 59, 79, 59, 68, 61, 57, 67, 50, 77, 48, 89, 48, 103, 50, 117, 55, 130, 65, 140, 76, 145, 85, 146, 94, 144, 101, 140, 105, 136, 106, 127, 106, 113, 100, 98, 92, 86, 86, 79, 84, 75, 84, 72, 91, 69, 106, 67, 126, 67, 144, 67, 158, 67, 168, 67, 173, 67, 177, 67])); + cb("r", new Uint8Array([53, 49, 53, 62, 53, 91, 53, 127, 53, 146, 53, 147, 53, 128, 53, 94, 53, 69, 62, 44, 82, 42, 94, 50, 92, 68, 82, 85, 77, 93, 80, 102, 95, 119, 114, 134, 129, 145, 137, 150])); + cb("s", new Uint8Array([159, 72, 157, 70, 155, 68, 151, 66, 145, 63, 134, 60, 121, 58, 108, 56, 96, 55, 83, 55, 73, 55, 64, 56, 57, 60, 52, 65, 49, 71, 49, 76, 50, 81, 55, 87, 71, 94, 94, 100, 116, 104, 131, 108, 141, 114, 145, 124, 142, 135, 124, 146, 97, 153, 70, 157, 52, 158])); + cb("t", new Uint8Array([45, 55, 48, 55, 55, 55, 72, 55, 96, 55, 120, 55, 136, 55, 147, 55, 152, 55, 155, 55, 157, 55, 158, 56, 158, 60, 156, 70, 154, 86, 151, 102, 150, 114, 148, 125, 148, 138, 148, 146])); + cb("u", new Uint8Array([35, 52, 35, 59, 35, 73, 35, 90, 36, 114, 38, 133, 42, 146, 49, 153, 60, 157, 73, 158, 86, 156, 100, 152, 112, 144, 121, 131, 127, 114, 132, 97, 134, 85, 135, 73, 136, 61, 136, 56])); + cb("v", new Uint8Array([36, 55, 37, 59, 40, 68, 45, 83, 51, 100, 58, 118, 64, 132, 69, 142, 71, 149, 73, 156, 76, 158, 77, 160, 77, 159, 80, 151, 82, 137, 84, 122, 86, 111, 90, 91, 91, 78, 91, 68, 91, 63, 92, 61, 97, 61, 111, 61, 132, 61, 150, 61, 162, 61])); + cb("w", new Uint8Array([25, 46, 25, 82, 25, 119, 33, 143, 43, 153, 60, 147, 73, 118, 75, 91, 76, 88, 85, 109, 96, 134, 107, 143, 118, 137, 129, 112, 134, 81, 134, 64, 134, 55])); + cb("x", new Uint8Array([56, 63, 56, 67, 57, 74, 60, 89, 66, 109, 74, 129, 85, 145, 96, 158, 107, 164, 117, 167, 128, 164, 141, 155, 151, 140, 159, 122, 166, 105, 168, 89, 170, 81, 170, 73, 169, 66, 161, 63, 141, 68, 110, 83, 77, 110, 55, 134, 47, 145])); + cb("y", new Uint8Array([30, 41, 30, 46, 30, 52, 30, 63, 30, 79, 33, 92, 38, 100, 47, 104, 54, 107, 66, 105, 79, 94, 88, 82, 92, 74, 94, 77, 96, 98, 96, 131, 94, 151, 91, 164, 85, 171, 75, 171, 71, 162, 74, 146, 84, 130, 95, 119, 106, 113])); + cb("z", new Uint8Array([29, 62, 35, 62, 43, 62, 63, 62, 87, 62, 110, 62, 125, 62, 134, 62, 138, 62, 136, 63, 122, 68, 103, 77, 85, 91, 70, 107, 59, 120, 50, 132, 47, 138, 43, 143, 41, 148, 42, 151, 53, 155, 80, 157, 116, 158, 146, 158, 163, 158])); + cb("SHIFT", new Uint8Array([100, 160, 100, 50])); + } else if (mode === exports.INPUT_MODE_NUM) { + cb("0", new Uint8Array([82, 50, 76, 50, 67, 50, 59, 50, 50, 51, 43, 57, 38, 68, 34, 83, 33, 95, 33, 108, 34, 121, 42, 136, 57, 148, 72, 155, 85, 157, 98, 155, 110, 149, 120, 139, 128, 127, 134, 119, 137, 114, 138, 107, 138, 98, 138, 88, 138, 77, 137, 71, 134, 65, 128, 60, 123, 58])); + cb("1", new Uint8Array([100, 50, 100, 160])); + cb("2", new Uint8Array([40, 79, 46, 74, 56, 66, 68, 58, 77, 49, 87, 45, 100, 45, 111, 46, 119, 50, 128, 58, 133, 71, 130, 88, 120, 106, 98, 128, 69, 150, 50, 162, 42, 167, 43, 168, 58, 169, 78, 170, 93, 170, 103, 170, 109, 170])); + cb("3", new Uint8Array([47, 65, 51, 60, 57, 56, 65, 51, 74, 47, 84, 45, 93, 45, 102, 45, 109, 46, 122, 51, 129, 58, 130, 65, 127, 74, 120, 85, 112, 92, 107, 96, 112, 101, 117, 105, 125, 113, 128, 123, 127, 134, 122, 145, 108, 156, 91, 161, 70, 163, 55, 163])); + cb("4", new Uint8Array([37, 58, 37, 60, 37, 64, 37, 69, 37, 75, 37, 86, 37, 96, 37, 105, 37, 112, 37, 117, 37, 122, 37, 126, 37, 128, 38, 129, 40, 129, 45, 129, 48, 129, 53, 129, 67, 129, 85, 129, 104, 129, 119, 129, 129, 129, 136, 129])); + cb("5", new Uint8Array([142, 60, 119, 60, 79, 60, 45, 60, 37, 64, 37, 86, 37, 103, 47, 107, 66, 106, 81, 103, 97, 103, 116, 103, 129, 108, 131, 130, 122, 152, 101, 168, 85, 172, 70, 172, 59, 172])); + cb("6", new Uint8Array([136, 54, 135, 49, 129, 47, 114, 47, 89, 54, 66, 66, 50, 81, 39, 95, 35, 109, 34, 128, 38, 145, 52, 158, 81, 164, 114, 157, 133, 139, 136, 125, 132, 118, 120, 115, 102, 117, 85, 123])); + cb("7", new Uint8Array([47, 38, 48, 38, 53, 38, 66, 38, 85, 38, 103, 38, 117, 38, 125, 38, 129, 38, 134, 41, 135, 47, 135, 54, 135, 66, 131, 93, 124, 126, 116, 149, 109, 161, 105, 168])); + cb("8", new Uint8Array([122, 61, 102, 61, 83, 61, 60, 61, 47, 62, 45, 78, 58, 99, 84, 112, 105, 122, 118, 134, 121, 149, 113, 165, 86, 171, 59, 171, 47, 164, 45, 144, 50, 132, 57, 125, 67, 117, 78, 109, 87, 102, 96, 94, 105, 86, 113, 85])); + cb("9", new Uint8Array([122, 58, 117, 55, 112, 51, 104, 51, 95, 51, 86, 51, 77, 51, 68, 51, 60, 51, 54, 56, 47, 64, 46, 77, 46, 89, 46, 96, 51, 103, 64, 109, 74, 110, 83, 110, 94, 107, 106, 102, 116, 94, 124, 84, 127, 79, 128, 78, 128, 94, 128, 123, 128, 161, 128, 175])); + } cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103])); cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116])); }; exports.input = function(options) { options = options||{}; + let input_mode = exports.INPUT_MODE_ALPHA; var text = options.text; if ("string"!=typeof text) text=""; -Bangle.strokes = {}; -exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); + function setupStrokes() { + Bangle.strokes = {}; + exports.getStrokes(input_mode, (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); + } + setupStrokes(); var flashToggle = false; const R = Bangle.appRect; @@ -49,6 +70,9 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); var Rx2; var Ry1; var Ry2; + let flashInterval; + let shift = false; + let lastDrag; function findMarker(strArr) { if (strArr.length == 0) { @@ -101,10 +125,13 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); */ function show() { + if (flashInterval) clearInterval(flashInterval); + flashInterval = undefined; + g.reset(); g.clearRect(R).setColor("#f00"); var n=0; - exports.getStrokes((id,s) => { + exports.getStrokes(input_mode, (id,s) => { var x = n%6; var y = (n-x)/6; s = g.transformVertices(s, {scale:0.16, x:R.x+x*30-4, y:R.y+y*30-4}); @@ -114,39 +141,87 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); }); } + function isInside(rect, e) { + return e.x>=rect.x && e.x=rect.y && e.y<=rect.y+rect.h; + } + + function isStrokeInside(rect, stroke) { + for(let i=0; i < stroke.length; i+=2) { + if (!isInside(rect, {x: stroke[i], y: stroke[i+1]})) { + return false; + } + } + return true; + } + function strokeHandler(o) { //print(o); if (!flashInterval) flashInterval = setInterval(() => { flashToggle = !flashToggle; - draw(); + draw(false); }, 1000); - if (o.stroke!==undefined) { + if (o.stroke!==undefined && o.xy.length >= 6 && isStrokeInside(R, o.xy)) { var ch = o.stroke; if (ch=="\b") text = text.slice(0,-1); - else text += ch; - g.clearRect(R); + else if (ch==="SHIFT") { shift=!shift; Bangle.drawWidgets(); } + else text += shift ? ch.toUpperCase() : ch; } + lastDrag = undefined; + g.clearRect(R); flashToggle = true; - draw(); + draw(false); } + + // Switches between alphabetic and numeric input + function cycleInput() { + input_mode++; + if (input_mode > exports.INPUT_MODE_NUM) input_mode = 0; + shift = false; + setupStrokes(); + show(); + Bangle.drawWidgets(); + } + Bangle.on('stroke',strokeHandler); g.reset().clearRect(R); show(); draw(false); - var flashInterval; + + // Create Widget to switch between alphabetic and numeric input + WIDGETS.kbswipe={ + area:"tl", + width: 36, // 3 chars, 6*2 px/char + draw: function() { + g.reset(); + g.setFont("6x8:2x3"); + g.setColor("#f00"); + if (input_mode === exports.INPUT_MODE_ALPHA) { + g.drawString(shift ? "ABC" : "abc", this.x, this.y); + } else if (input_mode === exports.INPUT_MODE_NUM) { + g.drawString("123", this.x, this.y); + } + } + }; return new Promise((resolve,reject) => { - var l;//last event Bangle.setUI({mode:"custom", drag:e=>{ "ram"; - if (l) g.reset().setColor("#f00").drawLine(l.x,l.y,e.x,e.y); - l = e.b ? e : 0; - },touch:() => { - if (flashInterval) clearInterval(flashInterval); - flashInterval = undefined; - show(); + if (isInside(R, e)) { + if (lastDrag) g.reset().setColor("#f00").drawLine(lastDrag.x,lastDrag.y,e.x,e.y); + lastDrag = e.b ? e : 0; + } + },touch:(n,e) => { + if (WIDGETS.kbswipe && isInside({x: WIDGETS.kbswipe.x, y: WIDGETS.kbswipe.y, w: WIDGETS.kbswipe.width, h: 24}, e)) { + // touch inside widget + cycleInput(); + } else if (isInside(R, e)) { + // touch inside app area + show(); + } }, back:()=>{ + delete WIDGETS.kbswipe; Bangle.removeListener("stroke", strokeHandler); if (flashInterval) clearInterval(flashInterval); Bangle.setUI(); diff --git a/apps/kbswipe/metadata.json b/apps/kbswipe/metadata.json index 59622cb96..5e290e1dd 100644 --- a/apps/kbswipe/metadata.json +++ b/apps/kbswipe/metadata.json @@ -1,6 +1,6 @@ { "id": "kbswipe", "name": "Swipe keyboard", - "version":"0.05", + "version":"0.06", "description": "A library for text input via PalmOS style swipe gestures (beta!)", "icon": "app.png", "type":"textinput", diff --git a/apps/lato/README.md b/apps/lato/README.md new file mode 100644 index 000000000..556ee6fbc --- /dev/null +++ b/apps/lato/README.md @@ -0,0 +1,54 @@ +# Lato + +A simple clock with the Lato font, with fast load and clock_info + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) + +This clock is a Lato version of Simplest++. Simplest++ provided the +smallest example of a clock that supports 'fast load' and 'clock +info'. Lato takes this one step further and adds the lovely Lato +font. The clock is derived from Simplest++ and inspired by the +Pastel Clock. + +## Usage + +* When the screen is unlocked, tap at the bottom of the csreen on the information text. + It should change color showing it is selected. + +* Swipe up or down to cycle through the info screens that can be displayed + when you have finished tap again towards the centre of the screen to unselect. + +* Swipe left or right to change the type of info screens displayed (by default + there is only one type of data so this will have no effect) + +* Settings are saved automatically and reloaded along with the clock. + +## About Clock Info's + +* The clock info modules enable all clocks to add the display of information to the clock face. + +* The default clock_info module provides a display of battery %, Steps, Heart Rate and Altitude. + +* Installing the [Sunrise ClockInfo](https://banglejs.com/apps/?id=clkinfosunrise) adds Sunrise and Sunset times into the list of info's. + + +## References + +* [What is Fast Load and how does it work](http://www.espruino.com/Bangle.js+Fast+Load) + +* [Clock Info Tutorial](http://www.espruino.com/Bangle.js+Clock+Info) + +* [How to load modules through the IDE](https://github.com/espruino/BangleApps/blob/master/modules/README.md) + + +## With Thanks + +* Gordon for support +* David Peer for his work on BW Clock + + +Written by: [Hugh Barney](https://github.com/hughbarney) For support +and discussion please post in the [Bangle JS +Forum](http://forum.espruino.com/microcosms/1424/) diff --git a/apps/lato/app.js b/apps/lato/app.js new file mode 100644 index 000000000..6045d7f17 --- /dev/null +++ b/apps/lato/app.js @@ -0,0 +1,138 @@ +/** + * + * Lato Clock + * + * The entire clock code is contained within the block below this + * supports 'fast load' + * + * To add support for clock_info_supprt we add the code marked at [1] and [2] + * + */ + +Graphics.prototype.setFontLato = function(scale) { + // Actual height 50 (54 - 5) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('ADkD8AHFg/wA4sP/AHVD44HHgPALD0OA40+F43+H4wHGn/8A4v/L4sH/5PFj//CxkD/6eFCw9/GooWHh//wAWLgP/TgoWHn5rFCw41BMYqCHaRDKGgzLYKAJgFv//LIhQBAAI7DWgIABU4adBAAJTDn4HCVAaOCQQhvDAYQuBDYaxBgJEDh4HBgYzDPgUDIYYECA5DUDgIHBg4HEEgIHfF44/EA45HDL4xvHP46PHT5CvHX47PDGYcDb4zvHf5AA/AA9wA4yoDZYq/DXAgHDXYQHEXYQHEj4HHXYQHDn6UCA4d/e4sAXYYHCd4gHCXwbADA86DFA/4HGAGA3Db40HA4UDe40Hc4YHCh7nDA4UfA4X/A4U/A4b/Cv7vGX4UB/A+CZ4YaCgf9A4sH+IHCHwfjA4JWDj/DA4s/wYHFv4kCA4f+A4pKBA4sD/AHCG4R9BA4YCBj/gA4s/4AECN4R5BA4f/gf/Mgn///+A4wZBA4d//6JBA4c/VATHEVASUEEwIHEAAbnDAGbyCAAg+DgKwDA4S4DLQSlCSYQHCn4HDFAV/bAX/4ADFCYgbCh4zHZ4SlBR4iSEA46XCe4QHCDgJWCngHOnwHGvwHRG4iFBI4ppBA4f4OIRnCN4MD9+AO4f///v8CHCDoP/54CBS4f/44CBU4f/wYHBX4f/EQLHDh6gB/6jDZQaTDAEUcA40/A4xODYoYHGgYHGh4HGNIIuG74uGz4uGj4uF/gHFh/4A4sf+AHFn/AA4q0BA4kBVgIHEFwIHFFwIHFj7jBA4guBA4rjCA4YuCA4guCA4r0BAATgBA75SEa4wHvAEEBA40DUYIAEg4HDgZ0Bh67BXAQHCZYJMBA4UHA4KPCA4SXEAgQHL4IEBgIHC/AMCgP4CQUDFgIHoIQY3DA4wCEDggHFO4YHB/iHDCQX+gE/S4IHCOIP/U4IHCv6CBA4k/A4K1CEQKpBEIIHDh//HILSDTQK+CAAd/f64Amn4GFgLxCAAZfBSIIADN4heDP4YeDR4Z5CEwN/U4IABg4NBj6ADEwLHDIoQtBVgQuCHoIHDFwIHBe4QLB/14A4kH/i1BeQQuB/AHFn/wA4pLBA4guBwAHELoMAA4o9BA4Q3BgYFBJ4gCCA4pqBvxvDf4T2Bh4HCIIc/R4MCKISfBS4aQCU4gHDX4ioBY4paBNwQAD/6uDAAUOf4wAjO4QHNdQYHYmAHGW4gUEA4kPA4z7BA4v/A4qYBY4QHCh63CA4c/V4QHDV4Y6DV4YHCDwYHDDwYHDv7ODA4MBZwgHBcwL1DA4MfdogHBDwgHB+LtFgf3DwhMCDwgHCDwhcFA4geEA4IeFA4IeFd5AArj77EsCgB/gGCg5QBOQkf/6oB/77D//DA4JrCv//44HB4DkC//n/E/MgIcCRIMPA4X8RIUHegQCBFoL8DA4cBA4QaBv4HGvwHBTgMHHQM+HgIHhF44HFJ5RfGN45/Bz6NBP4SPHT4XnT4ivHX47PHgCQCb4bvIAHxdBMgRfD/58CKgf/WgIADP4JlFR4J1ET4QHCiACBQwQEBuC/CDIIHBX4QtBn+Aa4sfZ4bvCh+Ah4HGUAUHA4d/AgIHEa4QHDwJyCA4eDKIQHDx5pCA4bPDG4c/RIRPCjwuCA4aJBUwZnCRAcBP4SgE/+D/7+ET4ImDA4jIEX4KvFh7HGgbXGgF+f6oAggZeBSgShEb4RYCagQHGh5iDA5QXEE443HADoA=='))), + 46, + atob("DhglJSUlJSUlJSUlEA=="), + 64+(scale<<8)+(1<<16) + ); + return this; +} + + +Graphics.prototype.setFontLatoSmall = function(scale) { + // Actual height 25 (24 - 0) + this.setFontCustom( + E.toString(require('heatshrink').decompress(atob('ADE//lwj/+nEP/kcDCGAgEfAQIAFgfwgEB/AZIggCB4YCBsO8gEz/0Av/8gP/DgP+jAiBhvggO/+ED//gh/9wEH+HAgEYsEAhhMJkEB8E4gf4h0B70HgPDgOA4P//f///9//4mEYjk4h0PnkDgZEBwH8FhMfAQXAiEYDoMMjE8g0MDwOOnBgBvEAnwCBgFwAQJsBgHn8ACBGIPjg0B4ODgPA4OA4FxNoIvBgEHAQIAGVIMBTAM4/6bB8PAv/gsE4+BmCJQMHhgvB50D4F2gHgLwMwh7eCFwM+JwJhBZwgAHGwMAvwNJe4QCBv4TBVYPB/EB/J0Bj1AgECC4rZC8/AgfxDAPgn//4BsCABECEQMBkCkDCgaBBGQICBhJhCwAgIUgVgAQMwAQJ4CIoMB/4pB/6uCD4QYLABMHJgMDJgT0CO48GSogxMAA0cAQMOGIQMFVoMBAQMHAQMPEoM/agcBMoJIDGYTWDTIMHXQMH4H4g+Aj0DwEHJgIBBDAPAJgNgnEAuEPXgIeBSoQCBj49BABYjBjA+BhgCBgwCBga3B/+AAQPAv5EBZZIAK4CKB8D5B+ACB/CQB9iwB8ywB8Y9B8OA8Hg4E/8FgKwMwPILkLhA/BWgM8gY0CuACBnEMAAMGAAMDg5jB4eDMYPjGIO/4EPx6dBeAYACAoTZCZATOBgPMAQPGZQPDAQOBwDEBSYKPBSoyLDOgIAJjixB/4gB/+B4FxwFgmHAmEYsEYhk4CYMcjhjB/0BwP+D4N8FZSFBgEHLQMPCoMPPgMPGIMe4FgjwzBjwzBhwuBgkPToMDFwQCCBAIAFe4prBgTgBg6gBh6EBj8AsE+gEwKAMYIYMNUgMHVQJPCXIwADEIMH5/gg///EHj0OcAeDDQPBGYNgAIM4+Fwh/8vEH841B8ICBABZBCh4RBg57Bg8HGIMBx4vB58A4FvgFwv0AngCBGIJdBAQIMBFY8CgGAcoPggPACAJPBCQ0ICYI2B2CaB+A4BExBUCcwSTBgZaBgOwAQPcBgPGAQPjB4KGBJQIkBdIJ/MAAczAQMZDwMMDwMGsA0BmAxBzAPB5gCBswYKAB7QBI4ICBjhNBgwuBge4B4PYAQN8AQMcAQMOUoYAJDoMAVIUYhk4hkPjkGh6pBxxcB/wPBbIJTBD4s/LQN/foN4jyYBV4LVCmF+nEwv+MjFw80MuEjLIMw4cYmFhx/8mHH/0wseBzC2BxkGZAMB8bLBv40Bh6VBAAb0BPoIMCc4MfI4QLB/+Agf44ED+FggPgO4P8F4M/xgdBNoQYCg/wAwIJCh4xEPAv/+Ef//4h///kGAAMDAAMBwOBwHAAANgAAM4uFwj/8nEH+/4gPx/gmBvDHGgILBgZdBg//8BuB/CpBjgCBg8BNAOA8AEBsC0CcoL4BnD4BjkHaoIQBA4NAUwIAIMYo3B/0DH4J6BAIKmBGIydBjAxEhwxDf4SPBUgKhCOQQAHN4P/gICBwACBSocwAAMYAAMMAAKuKJQIsJAAJjDGIrGBMIJkBGAJhBMgIwBgAwBJQJ9Be47KEEoOAFYPwgPgvB8CY41wSo5hBhkHgZjBwOHLwIlBuF/wEQn45IMZR7DMYIwBAQIyBMgICBeQRjBMggYBv//8DNB+D5CTxcAY4QYFCpgyDPgIGB8ACBQIMAvEPGgJjB/hjBHRpKCDAP8PgRjGXAIIBEIMD7wCB47HB4HgAQM8YQMPC4IEBSwkgGgoxFVwSWHV6QAEVxEHEYR4Cj4aCRwQJGCYIWCAQUPIYIOCnwCBvgxKcCsHUIgoDh5AEgDyCjwCBCwiVKABH8C4P/XgI3BDoN8gPAhwCBY4PgAgNwgHgVgMwVgMYh0AjkHgEOCYIEB8ACBnhRBOwIrCGgN+H5I9BCQJ8FF4bcBhjcBgzcBgb1BgPBPgPxAQM/RgMPAQJSBFgkfQoM/R4N//xKCwE4CYM4gFwjkAnBjCGYx8ICoP4g+BVAXzwF/8C1B4CLBAA75FY4RjGwBXBF4VgR4M4m+Ah/5UYP4nkB/BPBDQIqCNQIABZ4Q9BIAPwMYM4Y4MOGYMHGYOBwJjB8IzBvPgjEf8EMg6ZBE4J8BF4ZKBAYKkCsACBNoRjFg//QQIYOcQIAGTYPwfILHBfgTbEQYKBBAQL9BY4ICBg4dCCoICCg/AVwPAJQKCBE4IxDJQRuCBYUfGAMD/DLCEIXwAwK7BgEPCYQMBv4cDDASuBAQIoBg5CCPgoqCv4GBj/8AwJKBDIIGCGIU/BgQfBBYIrCn4UBj5CCGIMDLQU/AwxhCBIIcD8ClBwEHKwimDAANAY4NgVIPwGIPwgfBDwP5EgMfNQQtEJoUH74CBwfgh+A/B8Bjx8BgcBFwOAP4TbCnhgCdAStCvgJCPIJUCAQPAB4Q3CHoUPAQKuGMQYABMYUwMAMYXIMMgP8g0B+xIB8cBwfhwHD4HAs/AsE34EwdYMYJoMMg8AgxjCGAoADv///8/AQOYBAPMAQNkAQMQfhDdCgZ5CnwCBg5tBQAgXCBITLBC4JmBgIxC543B84CBEYQAKkC4EewQWBgIsCAQTEGBIQ1BABcMAQMGMQRvFZIK9CEAa9BDAwMDjh7CD4oANLYMw/gpBvwfBsYsBmOAg0Y4EDhlggPmIAN/O4MfDAIALTwPgcAIuBuBMBmBWBmBWBjBvBhhPBg8BGIP3OQIbBgE/PAQAEgY6Bgf/AQPvwEDwHgEAIuB4CIBsAxEjiBBgykCAAouCv5NBn18gE4hxKGEAJKBXoXA8E///4j//Phf+PoS5B/pjB804gFjJQMxwwxB4YxBsJ8BmPAgP4GIN8d4QABoDnEUoICCwACB4DPBE4IzBZ4IZCgUAh0T4EP3/wh//jEGmeMgcI40BwwdB4wdBu4dBn+O8Efw/gPgPgEYKXHPgqzBMAICESoLKBAQLlBLQTXBn4YBgaMCAAhcBgHjewNx//wnAYCAAliAQMzx///PHB4IYBbQIAHOoP+g6VCgCkCXoLwBAQN/HIN5ZQN4BgMwfIMQLYRJDAAd/GgL5PHAPAgZjBgB8CAQRACcAUcKIeAgIYBAQPgSoYYIj4uDDAY+JZwauBKILEDY4xrCEAVwDAhHBagR8GB4IIBn5pBnzKBnCVBjApBhgpBGIJ8BLYJjBFgN/ZoMfAQMHaZJKBagJpBHwNgO4Nghh8BFIIxFg4sBDAJeBAQUfQo8DZoKVBAoPnDAOAVwOAFwPAjAxEAQMMMwJ/B/AbBdpSuIg6rEGIKuHcAQACg+GAQPDMYPwJQMYSoOOJQPHJQNhHoM4AQMIngeDgg0FPgJWB+BWDSoxFBA4LjDVwIFBDALKBN4R5BFIZbJAQKIBDAgAEhBpCDQMDdgV/AQMP8ADBDAUeTIQCBTYMBE4IYCcoILBHgQ5CnwCBj4hBgIYBeAcBBIKcBBAY3CSIUfIgQ6Cn4YEGoUPPgQLBGIS2BAASVCY4OB8EB+IbBn4CBh4YBgYCCLoMH7wCBw4YBwAYBgEQEwdAJRHwS4N+BQMPQYRUBJoUHGgJpCj7MEfITgDwFwKQN4SoN8fIP2MYPzfIPhwAkBJQPgsCuBmAYBOghUBgHB4OB///+P/7/8HgMGYAMDkDJEABEDDYP9AQLOCABJcCmYCBjPHx8cDAP8j4CBGwIaIH4MAOQICDL4LUDRIUHQwYDBLYMATwMAUgIbDAA8gS4NwnEAvAnBv8MgH+4kA/MygP4zsB+FOwfwl1D8EUk/ghkX4EEh/AgUHWAL8BgOBwEDIQUAnEGgUYh8BxEPwF0j9Aj0fkEPn0Qh2+hEOvkEgk8gRvBgZtCXRYANg8f/kDz/+gPD/4WNdwTcBgP/LgPgT4PgRoNgRQM/BgLXB4F8WoLWB4AEBDAOAagQAEMQMARQIrBweAeYPAEgPgAoMwjkYjEMAAMGAAIXBgcB4KjBbgPAIQT9EAA8wbwJ4BPgICBgeOHQQPB4KhBsBTBnBsBj/wgEd7kAHgIqJPQInBwMggPwyEAn1IBIPkBoJjBgF/EoP//EB/VAgfxkED8GQgPADALhOj/4v/v/k/EoIALjhsBz6SB//Dwf88HBx04sHHhkwsPHjEw8f8jk//kEh+8EYpKBAYJeBgCxBAAcIAQMOPgQVCDgsH/ACBx4SBEYMGnEwg1/zECv/mgdwucB2EcwGYg1ApkDkE2gOQjeBzEE4HMgazC4ACBmA8Bh88OYLjBAAsIK4MMSAMGtARBkhQByzGCUoS1MD4MBFYMD44FBxIWBj4OBn6KBniHBhCECABcwAQMYAQJcBJAICBM4IrBIISyCsACBnxOEh4MCAA6uJGoICBjEC//2gd//cB2PAVwNgVwM4kE3n0QjP77EEvHsIwMcVx50CL554CiBWEgYPBgbHBgYwBgOGBgPDAQNhVoQCBg7KIgw5BgY5BgOBCAPAFINgsED/+wgP/7CxBF4IYMAA8MK4MOnAaBPATABgP7B4N5I4VADAhzBDAdMDAMmDAP/B4N/DAMBbZKVE8ACETYQAGn//8Ef//wh//e4LzBWY8YJIQCDDALdB/6jGg4CCHAMHEwMHdgKeBLoQXB/40Bv///gvIg4PBwYCB8ICB8BIIbQgZCFYMDCYMBO4QAMgSzBgegAwPwAQKzBAA8QJoUggEcAwMPNIgjBDA8PIYMPIAMGKgMDhBJBwgPB9y6CAQMOJ5cOQgMDzwGBGoWcFgPhOAM9AQMHOwTAGLo0cLocILoMMLoMeLoM8PYl4Qgi2BUIPgU4PQgeB5ACB40BbwI4BHYY+DgjGCOwMHgkGgf+g75Bh4NBMwUcAQI9CHQVwAQPxw0B8PHgPA4+A4FnOAM+fYMOfY0DjACBwxFByIJB5gaBv/B8Ed8YeBgZQBg7LCVwkeX4M8hqBBjmAAQNgiDxCfYICBM4J2DgaQBgbIBgOOgPHwcA8fBwFx4A4BUAICBLQLDDEoR6C/weB/kgg/4jA3Bh0/xkDn8GgHegcAl4qBgf4FQM/FQIYBEIN/AQMPGgTbCGIRtBNwISEG4JZCfoMAj/GgHfLgPvNgPnfINh/lgmEfOQg7BT4QxCbAQxCGhkDGgMDz/GgOfGgPPGgNnGgM5GgMMGiaGBcIcfQwQFB/8B4P/gHH+IuDmfAsEN/Ewh1/jEGPgQYBTYjBBMAgxCeYYxBCQUf4JvBwPA/+A8bHBgfwsEB8EwgH8jDgCg8D/0BDAJmFRgIoBGIkAGIIgBCQY3Cn4LBv/AmKSBnpjBiXwjEPv0MgbgCgJmCDAUD/DEESoQxCTwsPBAMfAoM/C4l9BAJsBgJsHCIJfBn//IQMf/EMAAMGAAMDAAMBwOBwHAAANgAAMwAAIgBAIIAFVobABFYLCBg/AjkA8ACBnEOgEco5dB0ZjB6OAgO4DoPcuAVBnEAuAVBuECgBaBABEf//4h///nH//+sZaBnJaBxxZB4ZaGAAJzDgABBABaMCGIqMChYxBhoxBxgxB5wwBswwBmQxDgABBABf///Av//8G/GgPYDYPMJoNmGgMzGgMZGgOGGgPBMwaJLFw/nMYPzzAuBxjwHSoWcMYguJ4ACBsACBnIuBxwCB4ZgCIhgABgoVBwwCB4xKCBYMDAQMBCQUgAQI7CmYVBjICBxgbCCoWAAYNAAQI7CuACBiISBwB8FGIQYBgJgCGQcYAQLPBg///0DDYMBEISFB4CFDAALpDhCeBgCeBwEHFYIEBuACBj0HBQIhBEoI8Bn6JKj///EP//8IIUA+BJBnkAh0PwEGgPgSQN4GgMeYIMDJoIWBMoM8LQZ8EY5UfI4QyBv43BngyBnEB404gFzjjvB50Ajl2OYMbUIJzD8AEBXAPgCoPgg+BKIP/FYQ9Bh62DAAcB/AjB/64CDAPA/ApBjilFwQxB4ZwBsewgEzzhKBxxKBw6OBCoMORYKJBn4rCNIMA/h8V4G4gFw7kAnB8C8x8BuZYBjJKBwxKB4J8niZ8BjoxBxgxB4x8CmEAmBKFo58m475CVwIxDgx8BgZ8EzhKB5x8YAAUYhhbBLIMDcIMA9wCBnwCBBYIeBKYMOEgMOJYIYCgRFBABJsEwF///AvhBBdIU4gfwjkD3EOMQMGg5sBg8DPoK/B94VBvxpBUwPgj+B+EfNgMMNhzmD//8gP//8QOIOMBwPnAQNxKQMYoEAhkgO4cHAQh9BMAOAcwOAj7jIAAIxB/EAGgK6BPIILBKILgBGIMcGIMGmBEBUYMDzAdB5ACBDAMDOYJdBh4CBMYKxKv/+WII0BRIQxBnBjCGIMHAQMBGIMA5wCB8YCBuB8BuAFB/AxB/CVB/BjBVBQxBwBKB+AYB/gfBhxjCzhgDgFgAQMxAQM5BIMcQYMcBAM+GIJdBAQV/+AxDaILCEKIMBBwU+YwcBD4PhZAPxAQP54APB4F8gFAYYMBAQMADwRICRgIAGTwPwNgP4NgLtB4CaBsEYgEwhkAjEGJQMHJQMDIIPnJwKVCn7cCFw7gCTAQCB/BsCEwMMgcDg8BwfBwHD+HAue4sEfx1wh+D+EBwJkCFgw3Bh/AoOH8Ex40wjnDjEPsOMgOw40Aj1mLQLdBdpkAvBzBvxMBn52BmOAi0Y4E7hlgnOGmEY+cwhE/SoMPcAIAJgY0BNQMGsP4g1xxkHmHGgcYscBxgxBs+ZwEZSoMAv7YCABEeTgMfwFjPgNzxlgmJKBPgQ0BxkDnnMgeP/5DBPgIoKGgMw/kHPgMDzhKEgx8BkZ8C81gjl/YgMfPgIAJg6xBY4J8CjnjjEfJQMIY4MG7AxB98zJoSSBPgTKLhjKBh0/JQMw4CfBsBUBmEA404gFzAQMfKAMHKAMH94CBmIYIjAYBhgYBxwSB4w3BIwIADv4CCBIN9SoNwjlAmEHkEYgfQhkBdoMA7kDwAVB4FgRY4eBJQuHJQNjJQM5JQMOJQVjJQM5JQMPJQMD8aLHQoICBTYM/SQIYCjHDgHssOA8yVBGIUh5lwgF+P4MfGgIAFgJNBgP/gER/fAjIYBjhKBhhKBg0xw0DzAxBt1jwED+JEB/CcFAAMPJoMP/EDx/egPODAJKCY4oxCh1zjkCj+MY4gABF4MAVIU5//whn//ECv5aBABE3//Amf/8AYCO4UCAQMDAQUf/8Bx//wHnDAKNBgE4AQMcAQMEGIU//0Dz4YBOg0/AQN/8EQnhNBnEcg3Yg0D9g2B80BwFzgHAvnA8BQB8C3BcASeHAAUHJQMTCQM4CIMYAQMMQwMDK4MBzACB5wYB44YBFYcf+AoHBAMHNIMH78Aw5oBsawBnOAmEO4CXBsEMQwMOcYP+HAICBgACCAAsfJYI3Cj98T4MHKgJ8Bmx8BP4IxDiBoBPgP4FwICBgYCBAA1/AQQuBvvwg1wFgMwVwMYgcBxgxB8wxBmeAPgKrDAQMPAQIAFgJ/BSQMAmP34E54FwVwMYVwMMGISuBGIPOgeA4f/wAuBAQM/AQKuKgOHVwPnVwJ8CKQLYBGIMOGISuBgKuPYYMAgwCBgZgCHoMI4kAh1nHQJ9BgeZSoIYLABM/xACBRIM8boM4n0AjE7EgLYBXYPAgdwMYPwuEA/p2B/4CBs4CBQoxpC//AWgPwiAsBJgJ8BJwKNBJwoCBDAPgDARWJj/4dILdBg4uBBQLwCmEOLYICBhh+Bg0CQYQYBwAYEAA9/EIKCCz4uBJoOABQPAsDgBbwMwjgxBFIMYDAJzBj4YBABBjBNgJmBh1//h8BhwsBZY3AVINgnACBhH/OYIYBFA0IVwQaBgaRCv0BOAPHwA4B4eAn+DwAMBwAhBwYnBgZnBXYIbBHgTZF///+YCB/EDwInBwD5BwB+B4B5BsDiBnCzBj5/BewUAAQRrCAYRQDCIMP4EjwP4+PAn5IBg/wkAMBnEfwEcv8Agl8DYKGBgEQgA='))), + 32, + atob("BQkKDw8UEgYICAoPBQkFCQ8PDw8PDw8PDw8GBg8PDwoVERAREw8OEhMICxENFxMUDxQQDQ8SERkQEBAICQgPCggNDgwODQgNDgYGDQYVDg4ODgoLCQ4NEw0NDAgICA8AAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAADAAFCQ8PDw8IDQgUCQwPABQICg8ICAgOEQcICAoMEhISChERERERERcRDw8PDwgICAgUExQUFBQUDxQSEhISEA8PDQ0NDQ0NFAwNDQ0NBgYGBg4ODg4ODg4PDg4ODg4NDg0="), + 25+(scale<<8)+(1<<16) + ); + return this; +} + + + +{ + // 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 draw = function() { + var date = new Date(); + var timeStr = require("locale").time(date,1); + var h = g.getHeight(); + var w = g.getWidth(); + + g.reset(); + g.setColor(g.theme.bg); + g.fillRect(Bangle.appRect); + + //g.setFont('Vector', w/3); + g.setFontLato(); + g.setFontAlign(0, 0); + g.setColor(g.theme.fg); + g.drawString(timeStr, w/2, h/2); + clockInfoMenu.redraw(); // clock_info_support + + // schedule a draw for the next minute + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); + }; + + /** + * clock_info_support + * this is the callback function that get invoked by clockInfoMenu.redraw(); + * + * We will display the image and text on the same line and centre the combined + * length of the image+text + * + * + */ + let clockInfoDraw = (itm, info, options) => { + //g.reset().setFont('Vector',24).setBgColor(options.bg).setColor(options.fg); + g.reset().setFontLatoSmall(); + g.setBgColor(options.bg).setColor(options.fg); + + //use info.text.toString(), steps does not have length defined + var text_w = g.stringWidth(info.text.toString()); + // gap between image and text + var gap = 10; + // width of the image and text combined + var w = gap + (info.img ? 24 :0) + text_w; + // different fg color if we tapped on the menu + if (options.focus) g.setColor(options.hl); + + // clear the whole info line, allow additional 2 pixels in case LatoFont overflows area + g.clearRect(0, options.y -2, g.getWidth(), options.y+ 23 + 2); + + // draw the image if we have one + if (info.img) { + // image start + var x = (g.getWidth() / 2) - (w/2); + g.drawImage(info.img, x, options.y); + // draw the text to the side of the image (left/centre alignment) + g.setFontAlign(-1,0).drawString(info.text, x + 23 + gap, options.y+12); + } else { + // text only option, not tested yet + g.setFontAlign(0,0).drawString(info.text, g.getWidth() / 2, options.y+12); + } + + }; + + // clock_info_support + // retrieve all the clock_info modules that are installed + let clockInfoItems = require("clock_info").load(); + + // clock_info_support + // setup the way we wish to interact with the menu + // the hl property defines the color the of the info when the menu is selected after tapping on it + let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:64, y:132, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#0ff"} ); + + // timeout used to update every minute + var drawTimeout; + g.clear(); + + // Show launcher when middle button pressed, add updown button handlers + Bangle.setUI({ + mode : "clock", + remove : function() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + // remove info menu + clockInfoMenu.remove(); + delete clockInfoMenu; + // delete the custom fonts + delete Graphics.prototype.setFontLato; + delete Graphics.prototype.setFontLatoSmall; + } + }); + + // Load widgets + Bangle.loadWidgets(); + draw(); + setTimeout(Bangle.drawWidgets,0); +} // end of clock diff --git a/apps/lato/app.png b/apps/lato/app.png new file mode 100644 index 000000000..02a4031a3 Binary files /dev/null and b/apps/lato/app.png differ diff --git a/apps/lato/icon.js b/apps/lato/icon.js new file mode 100644 index 000000000..345458d53 --- /dev/null +++ b/apps/lato/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkA/4A/AH4AUiIAKCxXzC5c/C5PyC5cvC8PxC5cfLxNEABhgI+gXNp4X3//+9wAK96PJC6/zC5bvKC6//C5YWKC4nUoMUpoXS8lDn/zmlOC6NCA4ckC6Hkl4HD+QwCC5c+LoIsCoSKBMIPjC5tD//0olEp//mgXNmMRiYuBC4JjBBAYAK+MRj//CwIABBAgXkI5AXOiRyBC4J8BkIXN+dEoKnFiNEAYIXNa4sUC59EJAIACkIHBC5iMCoMTn/zmIuBSQIXODAMRAAKqDABikCAAqqBC8i8CAArCBC/n0C49PC5oA/AH4AIA==")) diff --git a/apps/lato/metadata.json b/apps/lato/metadata.json new file mode 100644 index 000000000..0b5e4a0f3 --- /dev/null +++ b/apps/lato/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "lato", + "name": "Lato", + "version": "0.01", + "description": "A Lato Font clock with fast load and clock_info", + "readme": "README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot3.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"lato.app.js","url":"app.js"}, + {"name":"lato.img","url":"icon.js","evaluate":true} + ] +} diff --git a/apps/lato/screenshot1..png b/apps/lato/screenshot1..png new file mode 100644 index 000000000..14c8d6d04 Binary files /dev/null and b/apps/lato/screenshot1..png differ diff --git a/apps/lato/screenshot2.png b/apps/lato/screenshot2.png new file mode 100644 index 000000000..f40495c79 Binary files /dev/null and b/apps/lato/screenshot2.png differ diff --git a/apps/lato/screenshot3.png b/apps/lato/screenshot3.png new file mode 100644 index 000000000..1cf135a60 Binary files /dev/null and b/apps/lato/screenshot3.png differ diff --git a/apps/launch/metadata.json b/apps/launch/metadata.json index 4d0270e37..85fcdd02f 100644 --- a/apps/launch/metadata.json +++ b/apps/launch/metadata.json @@ -7,6 +7,7 @@ "readme": "README.md", "icon": "app.png", "type": "launch", + "default": true, "tags": "tool,system,launcher", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ diff --git a/apps/messagegui/ChangeLog b/apps/messagegui/ChangeLog index 854979d18..3f5ff70fd 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -79,3 +79,6 @@ Move widget to widmessage 0.56: Fix handling of music messages 0.57: Fix "unread Timeout" = off (previously defaulted to 60s) +0.58: Fast load messages without writing to flash + Don't write messages to flash until the app closes +0.59: Ensure we do write messages if messages app can't be fast loaded (see #2373) diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index f6f7779eb..0f3d90a9d 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -48,6 +48,11 @@ to the clock. */ var unreadTimeout; /// List of all our messages var MESSAGES = require("messages").getMessages(); +if (Bangle.MESSAGES) { + // fast loading messages + Bangle.MESSAGES.forEach(m => require("messages").apply(m, MESSAGES)); + delete Bangle.MESSAGES; +} var onMessagesModified = function(type,msg) { if (msg.handled) return; @@ -105,7 +110,6 @@ function showMapMessage(msg) { layout.render(); function back() { // mark as not new and return to menu msg.new = false; - saveMessages(); layout = undefined; checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:0}); } @@ -140,7 +144,6 @@ function showMusicMessage(msg) { openMusic = false; var wasNew = msg.new; msg.new = false; - saveMessages(); layout = undefined; if (wasNew) checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:0,openMusic:0}); else checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0}); @@ -223,24 +226,20 @@ function showMessageSettings(msg) { }, /*LANG*/"Delete" : () => { MESSAGES = MESSAGES.filter(m=>m.id!=msg.id); - saveMessages(); checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0}); }, /*LANG*/"Mark Unread" : () => { msg.new = true; - saveMessages(); checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0}); }, /*LANG*/"Mark all read" : () => { MESSAGES.forEach(msg => msg.new = false); - saveMessages(); checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0}); }, /*LANG*/"Delete all messages" : () => { E.showPrompt(/*LANG*/"Are you sure?", {title:/*LANG*/"Delete All Messages"}).then(isYes => { if (isYes) { MESSAGES = []; - saveMessages(); } checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0}); }); @@ -295,7 +294,7 @@ function showMessage(msgid) { } function goBack() { layout = undefined; - msg.new = false; saveMessages(); // read mail + msg.new = false; // read mail cancelReloadTimeout(); // don't auto-reload to clock now checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0,openMusic:openMusic}); } @@ -303,7 +302,7 @@ function showMessage(msgid) { ]; if (msg.positive) { buttons.push({type:"btn", src:atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="), cb:()=>{ - msg.new = false; saveMessages(); + msg.new = false; cancelReloadTimeout(); // don't auto-reload to clock now Bangle.messageResponse(msg,true); checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:openMusic}); @@ -312,7 +311,7 @@ function showMessage(msgid) { if (msg.negative) { if (buttons.length) buttons.push({width:32}); // nasty hack... buttons.push({type:"btn", src:atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="), cb:()=>{ - msg.new = false; saveMessages(); + msg.new = false; cancelReloadTimeout(); // don't auto-reload to clock now Bangle.messageResponse(msg,false); checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:openMusic}); diff --git a/apps/messagegui/lib.js b/apps/messagegui/lib.js index e45e6e5a0..f9919c01c 100644 --- a/apps/messagegui/lib.js +++ b/apps/messagegui/lib.js @@ -18,9 +18,25 @@ exports.listener = function(type, msg) { if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true; else return; } - require("messages").save(msg); + if (Bangle.load === load || !Bangle.uiRemove) { + // no fast loading: store message to flash + /* FIXME: Maybe we need a better way of deciding if an app will + be fast loaded than just hard-coding a Bangle.uiRemove check. + Bangle.load could return a bool (as the load doesn't happen immediately). */ + require("messages").save(msg); + } else { + if (!Bangle.MESSAGES) Bangle.MESSAGES = []; + Bangle.MESSAGES.push(msg); + } + const saveToFlash = () => { + // save messages from RAM to flash after all, if we decide not to launch app + if (!Bangle.MESSAGES) return; + Bangle.MESSAGES.forEach(m => require("messages").save(m)); + delete Bangle.MESSAGES; + } msg.handled = true; if ((msg.t!=="add" || !msg.new) && (type!=="music")) { // music always has t:"modify" + saveToFlash(); return; } @@ -35,7 +51,11 @@ exports.listener = function(type, msg) { exports.messageTimeout = setTimeout(function() { delete exports.messageTimeout; if (type!=="music") { - if (!loadMessages) return require("messages").buzz(msg.src); // no opening the app, just buzz + if (!loadMessages) { + // not opening the app, just buzz + saveToFlash(); + return require("messages").buzz(msg.src); + } if (!quiet && unlockWatch) { Bangle.setLocked(false); Bangle.setLCDPower(1); // turn screen on @@ -51,9 +71,11 @@ exports.listener = function(type, msg) { */ exports.open = function(msg) { if (msg && msg.id && !msg.show) { - // store which message to load msg.show = 1; - require("messages").save(msg, {force: 1}); + if (Bangle.load === load) { + // no fast loading: store message to load in flash + require("messages").save(msg, {force: 1}); + } } Bangle.load((msg && msg.new && msg.id!=="music") ? "messagegui.new.js" : "messagegui.app.js"); diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index b3e70d5d4..80b362551 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -1,7 +1,8 @@ { "id": "messagegui", "name": "Message UI", - "version": "0.57", + "shortName": "Messages", + "version": "0.59", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", @@ -9,6 +10,7 @@ "supports": ["BANGLEJS","BANGLEJS2"], "dependencies" : { "messageicons":"module" }, "provides_modules": ["messagegui"], + "default": true, "readme": "README.md", "storage": [ {"name":"messagegui","url":"lib.js"}, diff --git a/apps/messageicons/ChangeLog b/apps/messageicons/ChangeLog index 52a4b35a7..fefc6ee6e 100644 --- a/apps/messageicons/ChangeLog +++ b/apps/messageicons/ChangeLog @@ -1 +1,2 @@ 0.01: Moved message icons from messages into standalone library +0.02: Added several new icons and colors diff --git a/apps/messageicons/metadata.json b/apps/messageicons/metadata.json index eb907f893..b621c55b9 100644 --- a/apps/messageicons/metadata.json +++ b/apps/messageicons/metadata.json @@ -1,13 +1,14 @@ { "id": "messageicons", "name": "Message Icons", - "version": "0.01", + "version": "0.02", "description": "Library containing a list of icons and colors for apps", "icon": "app.png", "type": "module", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], "provides_modules" : ["messageicons"], + "default": true, "storage": [ {"name":"messageicons","url":"lib.js"} ] diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 3df056d62..7d3414c1a 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -1,2 +1,4 @@ 0.55: Moved messages library into standalone library 0.56: Fix handling of music messages +0.57: Optimize saving empty message list +0.58: show/hide "messages" widget directly, instead of through library stub diff --git a/apps/messages/lib.js b/apps/messages/lib.js index e5c81f3fd..a710c81c4 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -125,9 +125,10 @@ exports.openGUI = function(msg) { * @param {boolean} show */ exports.toggleWidget = function(show) { - if (!require("Storage").read("messagewidget")) return; // "messagewidget" module is missing! - if (show) require("messagewidget").show(); - else require("messagewidget").hide(); + if (!global.WIDGETS || !WIDGETS["messages"]) return; // widget is missing! + const method = WIDGETS["messages"][show ? "show" : "hide"]; + /* if (typeof(method)!=="function") return; // widget must always have show()+hide(), fail hard rather than hide problems */ + method.apply(WIDGETS["messages"]); }; /** @@ -135,7 +136,8 @@ exports.toggleWidget = function(show) { * @param {array} messages Messages to save */ exports.write = function(messages) { - require("Storage").writeJSON("messages.json", messages.map(m => { + if (!messages.length) require("Storage").erase("messages.json"); + else require("Storage").writeJSON("messages.json", messages.map(m => { // we never want to save saved/handled status to file; delete m.saved; delete m.handled; diff --git a/apps/messages/metadata.json b/apps/messages/metadata.json index 8bcf3da25..9c7c8b49e 100644 --- a/apps/messages/metadata.json +++ b/apps/messages/metadata.json @@ -1,14 +1,18 @@ { "id": "messages", "name": "Messages", - "version": "0.56", + "version": "0.58", "description": "Library to handle, load and store message events received from Android/iOS", "icon": "app.png", "type": "module", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], "provides_modules" : ["messages"], - "dependencies" : { "messagegui":"module","messagewidget":"module" }, + "dependencies" : { + "messagegui":"module", + "message":"widget" + }, + "default": true, "readme": "README.md", "storage": [ {"name":"messages","url":"lib.js"}, diff --git a/apps/noteify/ChangeLog b/apps/noteify/ChangeLog index d7bc46dcd..a37a66731 100644 --- a/apps/noteify/ChangeLog +++ b/apps/noteify/ChangeLog @@ -1,2 +1,3 @@ 0.01: Initial version 0.02: Use default Bangle formatter for booleans +0.03: Drop duplicate alarm widget diff --git a/apps/noteify/metadata.json b/apps/noteify/metadata.json index fbd5a88f1..850628c46 100644 --- a/apps/noteify/metadata.json +++ b/apps/noteify/metadata.json @@ -1,7 +1,7 @@ { "id": "noteify", "name": "Noteify", - "version": "0.02", + "version": "0.03", "description": "Write notes using an onscreen keyboard and use them as custom messages for alarms or timers.", "icon": "app.png", "tags": "tool,alarm", @@ -9,8 +9,7 @@ "readme": "README.md", "storage": [ {"name":"noteify.app.js","url":"app.js"}, - {"name":"noteify.img","url":"app-icon.js","evaluate":true}, - {"name":"noteify.wid.js","url":"widget.js"} + {"name":"noteify.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"noteify.json"}], "dependencies": {"scheduler":"type","textinput":"type"}, diff --git a/apps/noteify/widget.js b/apps/noteify/widget.js deleted file mode 100644 index 052ac9ebd..000000000 --- a/apps/noteify/widget.js +++ /dev/null @@ -1,8 +0,0 @@ -WIDGETS["alarm"]={area:"tl",width:0,draw:function() { - if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); - },reload:function() { - // don't include library here as we're trying to use as little RAM as possible - WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0; - } -}; -WIDGETS["alarm"].reload(); diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index 28dcc0c28..02cef7774 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -19,3 +19,4 @@ 0.16: make check_idle boolean setting work properly with new B2 menu 0.17: Use default Bangle formatter for booleans 0.18: fix idle option always getting defaulted to true +0.19: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/pastel/metadata.json b/apps/pastel/metadata.json index 860ed833b..cf4bbbe9b 100644 --- a/apps/pastel/metadata.json +++ b/apps/pastel/metadata.json @@ -2,7 +2,7 @@ "id": "pastel", "name": "Pastel Clock", "shortName": "Pastel", - "version": "0.18", + "version": "0.19", "description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times", "icon": "pastel.png", "dependencies": {"mylocation":"app","weather":"app"}, diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 05c0e2367..bc41588d8 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -1,4 +1,4 @@ -var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); +var SunCalc = require("suncalc"); // from modules folder require("f_latosmall").add(Graphics); const storage = require('Storage'); const locale = require("locale"); @@ -85,7 +85,7 @@ function getSteps() { try { return Bangle.getHealthStatus("day").steps; } catch (e) { - if (WIDGETS.wpedom !== undefined) + if (WIDGETS.wpedom !== undefined) return WIDGETS.wpedom.getSteps(); else return '???'; @@ -181,12 +181,12 @@ function drawClock() { var d = new Date(); var da = d.toString().split(" "); var time = da[4].substr(0,5); - + var hh = da[4].substr(0,2); var mm = da[4].substr(3,2); var day = da[0]; var month_day = da[1] + " " + da[2]; - + // fix hh for 12hr clock var h2 = "0" + parseInt(hh) % 12 || 12; if (parseInt(hh) > 12) @@ -215,12 +215,12 @@ function drawClock() { g.reset(); g.setColor(g.theme.bg); g.fillRect(Bangle.appRect); - + // draw a grid like graph paper if (settings.grid && process.env.HWVERSION !=1) { g.setColor("#0f0"); for (var gx=20; gx <= w; gx += 20) - g.drawLine(gx, 30, gx, h - 24); + g.drawLine(gx, 30, gx, h - 24); for (var gy=30; gy <= h - 24; gy += 20) g.drawLine(0, gy, w, gy); } @@ -238,7 +238,7 @@ function drawClock() { g.drawString( (w_wind.split(' ').slice(0, 2).join(' ')), (w/2) + 6, 24 + ((y - 24)/2)); // display first 2 words of the wind string eg '4 mph' } - + if (settings.font == "Architect") g.setFontArchitect(); else if (settings.font == "GochiHand") @@ -253,7 +253,7 @@ function drawClock() { g.setFontSpecialElite(); else g.setFontLato(); - + g.setFontAlign(1,-1); // right aligned g.drawString(hh, x - 6, y); g.setFontAlign(-1,-1); // left aligned @@ -310,7 +310,7 @@ function BUTTON(name,x,y,w,h,c,f,tx) { // if pressed the callback BUTTON.prototype.check = function(x,y) { //console.log(this.name + ":check() x=" + x + " y=" + y +"\n"); - + if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { log_debug(this.name + ":callback\n"); this.callback(); @@ -366,7 +366,7 @@ function checkIdle() { warned = false; return; } - + let hour = (new Date()).getHours(); let active = (hour >= 9 && hour < 21); //let active = true; @@ -397,7 +397,7 @@ function buzzer(n) { if (n-- < 1) return; Bangle.buzz(250); - + if (buzzTimeout) clearTimeout(buzzTimeout); buzzTimeout = setTimeout(function() { buzzTimeout = undefined; diff --git a/apps/podadrem/ChangeLog b/apps/podadrem/ChangeLog index c26e40c0e..3c68f15ac 100644 --- a/apps/podadrem/ChangeLog +++ b/apps/podadrem/ChangeLog @@ -4,3 +4,6 @@ Addict. 0.04: New layout. 0.05: Add widget field, tweak layout. +0.06: Add compatibility with Fastload Utils. +0.07: Remove just the specific listeners to not interfere with Quick Launch +when fastloading. diff --git a/apps/podadrem/app.js b/apps/podadrem/app.js index b04d80b17..9c9ed8b04 100644 --- a/apps/podadrem/app.js +++ b/apps/podadrem/app.js @@ -1,77 +1,20 @@ +{ /* -Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}})); + Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}})); -Podcast Addict is developed by Xavier Guillemane and can be downloaded on Google Play Store: https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=en_US&gl=US - -Podcast Addict can be controlled through the sending of remote commands called 'Intents'. -Some 3rd parties apps specialized in task automation will then allow you to control Podcast Addict. For example, you will be able to wake up to the sound of your playlist or to start automatically playing when some NFC tag has been detected. -In Tasker, you just need to copy/paste one of the following intent in the task Action field ("Misc" action type then select "Send Itent") . -If you prefer Automate It, you can use the Podcast Addict plugin that will save you some configuration time (https://play.google.com/store/apps/details?id=com.smarterapps.podcastaddictplugin ) -Before using an intent make sure to set the following: -Package: com.bambuna.podcastaddict -Class (UPDATE intent only): com.bambuna.podcastaddict.receiver.PodcastAddictBroadcastReceiver -Class (every other intent): com.bambuna.podcastaddict.receiver.PodcastAddictPlayerReceiver -Here are the supported commands (Intents) : -com.bambuna.podcastaddict.service.player.toggle – Toggle the playlist -com.bambuna.podcastaddict.service.player.stop – Stop the player and release its resources -com.bambuna.podcastaddict.service.player.play – Start playing the playlist -com.bambuna.podcastaddict.service.player.pause – Pause the playlist -com.bambuna.podcastaddict.service.player.nexttrack – Start playing next track -com.bambuna.podcastaddict.service.player.previoustrack – Start playing previous track -com.bambuna.podcastaddict.service.player.jumpforward – Jump 30s forward -com.bambuna.podcastaddict.service.player.jumpbackward – Jump 15s backward -com.bambuna.podcastaddict.service.player.1xspeed - Disable the variable playback speed -com.bambuna.podcastaddict.service.player.1.5xspeed – Force the playback speed at 1.5x -com.bambuna.podcastaddict.service.player.2xspeed – Force the playback speed at 2.0x -com.bambuna.podcastaddict.service.player.stoptimer – Disable the timer -com.bambuna.podcastaddict.service.player.15mntimer – Set the timer at 15 minutes -com.bambuna.podcastaddict.service.player.30mntimer – Set the timer at 30 minutes -com.bambuna.podcastaddict.service.player.60mntimer – Set the timer at 1 hour -com.bambuna.podcastaddict.service.update – Trigger podcasts update -com.bambuna.podcastaddict.openmainscreen – Open the app on the Main screen -com.bambuna.podcastaddict.openplaylist – Open the app on the Playlist screen -com.bambuna.podcastaddict.openplayer – Open the app on the Player screen -com.bambuna.podcastaddict.opennewepisodes – Open the app on the New episodes screen -com.bambuna.podcastaddict.opendownloadedepisodes – Open the app on the Downloaded episodes screen -com.bambuna.podcastaddict.service.player.playfirstepisode – Start playing the first episode in the playlist -com.bambuna.podcastaddict.service.player.customspeed – Select playback speed -In order to use this intent you need to pass a float argument called "arg1". Valid values are within [0.1, 5.0] -com.bambuna.podcastaddict.service.player.customtimer – Start a custom timer -In order to use this intent you need to pass an int argument called "arg1" containing the number of minutes. Valid values are within [1, 1440] -com.bambuna.podcastaddict.service.player.deletecurrentskipnexttrack – Delete the current episode and skip to the next one. It behaves the same way as long pressing on the player >| button, but doesn't display any confirmation popup. -com.bambuna.podcastaddict.service.player.deletecurrentskipprevioustrack – Delete the current episode and skip to the previous one. It behaves the same way as long pressing on the player |< button, but doesn't display any confirmation popup. -com.bambuna.podcastaddict.service.player.boostVolume – Toggle the Volume Boost audio effect -You can pass a, optional boolean argument called "arg1" in order to create a ON or OFF button for the volume boost. Without this parameter the app will just toggle the current value -com.bambuna.podcastaddict.service.player.quickBookmark – Creates a bookmark at the current playback position so you can easily retrieve it later. -com.bambuna.podcastaddict.service.download.pause – Pause downloads -com.bambuna.podcastaddict.service.download.resume – Resume downloads -com.bambuna.podcastaddict.service. download.toggle – Toggle downloads -com.bambuna.podcastaddict.service.player.favorite – Mark the current episode playing as favorite. -com.bambuna.podcastaddict.openplaylist – Open the app on the Playlist screen -You can pass an optional string argument called "arg1" in order to select the playlist to open. Without this parameter the app will open the current playlist -Here's how it works: -##AUDIO## will open the Audio playlist screen -##VIDEO## will open the Video playlist screen -##RADIO## will open the Radio screen -Any other argument will be used as a CATEGORY name. The app will then open this category under the playlist CUSTOM tab -You can pass an optional boolean argument called "arg2" in order to select if the app UI should be opened. Without this parameter the playlist will be displayed -You can pass an optional boolean argument called "arg3" in order to select if the app should start playing the selected playlist. Without this parameter the playback won't start -Since v2020.3 -com.bambuna.podcastaddict.service.full_backup – Trigger a full backup of the app data (relies on the app automatic backup settings for the folder and the # of backup to keep) -This task takes a lot of resources and might take up to a minute to complete, so please avoid using the app at the same time -Since v2020.15 -com.bambuna.podcastaddict.service.player.toggletimer – This will toggle the Sleep Timer using the last duration and parameter used in the app. -Since v2020.16 -com.bambuna.podcastaddict.service.player.togglespeed – This will toggle the Playback speed for the episode currently playing (alternate between selected speed and 1.0x). + Podcast Addict is developed by Xavier Guillemane and can be downloaded on Google Play Store: https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=en_US&gl=US + + How to use intents to control Podcast Addict: https://podcastaddict.com/faq/130 */ -var R; -var backToMenu = false; -var dark = g.theme.dark; // bool +let R; +let widgetUtils = require("widget_utils"); +let backToMenu = false; +let dark = g.theme.dark; // bool // The main layout of the app -function gfx() { - //Bangle.drawWidgets(); +let gfx = function() { + widgetUtils.hide(); R = Bangle.appRect; marigin = 8; // g.drawString(str, x, y, solid) @@ -106,19 +49,19 @@ function gfx() { g.setFontAlign(1, 1, 0); g.drawString("Speed", R.x + R.w - 2*marigin, R.y + R.h - 2*marigin); -} +}; // Touch handler for main layout -function touchHandler(_, xy) { +let touchHandler = function(_, xy) { x = xy.x; y = xy.y; len = (R.wb-1 instead of a>b. + // doing ab-1 instead of a>=b. if ((R.x-1 { - if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup"); - } + {mode : "updown", + remove : ()=>{ + Bangle.removeListener("touch", touchHandler); + Bangle.removeListener("swipe", swipeHandler); + clearWatch(buttonHandler); + widgetUtils.show(); + } + }, + ud => { + if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup"); + } ); Bangle.on("touch", touchHandler); Bangle.on("swipe", swipeHandler); -} + let buttonHandler = setWatch(()=>{load();}, BTN, {edge:'falling'}); +}; /* The functions for interacting with Android and the Podcast Addict app */ -pkg = "com.bambuna.podcastaddict"; -standardCls = pkg + ".receiver.PodcastAddictPlayerReceiver"; -updateCls = pkg + ".receiver.PodcastAddictBroadcastReceiver"; -speed = 1.0; +let pkg = "com.bambuna.podcastaddict"; +let standardCls = pkg + ".receiver.PodcastAddictPlayerReceiver"; +let updateCls = pkg + ".receiver.PodcastAddictBroadcastReceiver"; +let speed = 1.0; -simpleSearch = ""; +let simpleSearch = ""; -function simpleSearchTerm() { // input a simple search term without tags, overrides search with tags (artist and track) +let simpleSearchTerm = function() { // input a simple search term without tags, overrides search with tags (artist and track) require("textinput").input({ text: simpleSearch }).then(result => { @@ -189,9 +140,9 @@ function simpleSearchTerm() { // input a simple search term without tags, overri }).then(() => { E.showMenu(searchMenu); }); -} +}; -function searchPlayWOTags() { //make a search and play using entered terms +let searchPlayWOTags = function() { //make a search and play using entered terms searchString = simpleSearch; Bluetooth.println(JSON.stringify({ t: "intent", @@ -203,9 +154,9 @@ function searchPlayWOTags() { //make a search and play using entered terms }, flags: ["FLAG_ACTIVITY_NEW_TASK"] })); -} +}; -function gadgetbridgeWake() { +let gadgetbridgeWake = function() { Bluetooth.println(JSON.stringify({ t: "intent", target: "activity", @@ -213,15 +164,15 @@ function gadgetbridgeWake() { package: "gadgetbridge", class: "nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity" })); -} +}; // For stringing together the action for Podcast Addict to perform -function actFn(actName, activOrServ) { +let actFn = function(actName, activOrServ) { return "com.bambuna.podcastaddict." + (activOrServ == "service" ? "service." : "") + actName; -} +}; // Send the intent message to Gadgetbridge -function btMsg(activOrServ, cls, actName, xtra) { +let btMsg = function(activOrServ, cls, actName, xtra) { Bluetooth.println(JSON.stringify({ t: "intent", @@ -231,22 +182,20 @@ function btMsg(activOrServ, cls, actName, xtra) { target: "broadcastreceiver", extra: xtra })); -} +}; // Get back to the main layout -function backToGfx() { +let backToGfx = function() { E.showMenu(); g.clear(); g.reset(); - Bangle.removeAllListeners("touch"); - Bangle.removeAllListeners("swipe"); setUI(); gfx(); backToMenu = false; -} +}; // Podcast Addict Menu -var paMenu = { +let paMenu = { "": { title: " ", back: backToGfx @@ -271,7 +220,7 @@ var paMenu = { }; -var controlMenu = { +let controlMenu = { "": { title: " ", back: () => {if (backToMenu) E.showMenu(paMenu); @@ -310,7 +259,7 @@ var controlMenu = { }, }; -var speedMenu = { +let speedMenu = { "": { title: " ", back: () => {if (backToMenu) E.showMenu(paMenu); @@ -333,7 +282,7 @@ var speedMenu = { //"Slower" : ()=>{speed-=0.1; speed=((speed<0.1)?0.1:speed); btMsg("service",standardCls,"player.customspeed",{arg1:speed});}, }; -var searchMenu = { +let searchMenu = { "": { title: " ", @@ -356,7 +305,7 @@ var searchMenu = { "Simpler search and play" : searchPlayWOTags, }; -var navigationMenu = { +let navigationMenu = { "": { title: " ", back: () => {if (backToMenu) E.showMenu(paMenu); @@ -372,4 +321,6 @@ var navigationMenu = { Bangle.loadWidgets(); setUI(); +widgetUtils.hide(); gfx(); +} diff --git a/apps/podadrem/metadata.json b/apps/podadrem/metadata.json index 929269762..c58b9241d 100644 --- a/apps/podadrem/metadata.json +++ b/apps/podadrem/metadata.json @@ -2,7 +2,7 @@ "id": "podadrem", "name": "Podcast Addict Remote", "shortName": "PA Remote", - "version": "0.05", + "version": "0.07", "description": "Control Podcast Addict on your android device.", "readme": "README.md", "type": "app", diff --git a/apps/presentor/metadata.json b/apps/presentor/metadata.json index e5b5e289f..2d0a22102 100644 --- a/apps/presentor/metadata.json +++ b/apps/presentor/metadata.json @@ -12,7 +12,8 @@ "allow_emulator": true, "storage": [ {"name":"presentor.app.js","url":"app.js"}, - {"name":"presentor.img","url":"app-icon.js","evaluate":true}, + {"name":"presentor.img","url":"app-icon.js","evaluate":true} + ], "data": [ {"name":"presentor.json","url":"settings.json"} ] } diff --git a/apps/primetimelato/ChangeLog b/apps/primetimelato/ChangeLog index 46690e360..7781e93a1 100644 --- a/apps/primetimelato/ChangeLog +++ b/apps/primetimelato/ChangeLog @@ -1,2 +1,4 @@ 0.01: first release 0.02: added option to buzz on prime, with settings +0.03: added option to debug settings and test fw 2.15.93 load speed ups +0.04: changed icon diff --git a/apps/primetimelato/README.md b/apps/primetimelato/README.md index 924a6fae6..0ffd5a3fa 100644 --- a/apps/primetimelato/README.md +++ b/apps/primetimelato/README.md @@ -1,4 +1,4 @@ -# Prime Time Lato (clock) +# Prime Lato (clock) A watchface that displays time and its prime factors in the Lato font. For example when the time is 21:05, the prime factors are 5,421. diff --git a/apps/primetimelato/app.js b/apps/primetimelato/app.js index 817da7cda..b4b9d5bb9 100644 --- a/apps/primetimelato/app.js +++ b/apps/primetimelato/app.js @@ -2,7 +2,8 @@ const h = g.getHeight(); const w = g.getWidth(); const SETTINGS_FILE = "primetimelato.json"; let settings; - +let setStr = 'U'; + Graphics.prototype.setFontLato = function(scale) { // Actual height 41 (43 - 3) this.setFontCustom( @@ -28,6 +29,22 @@ Graphics.prototype.setFontLatoSmall = function(scale) { function loadSettings() { settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; settings.buzz_on_prime = (settings.buzz_on_prime === undefined ? false : settings.buzz_on_prime); + settings.debug = (settings.debug === undefined ? false : settings.debug); + + switch(settings.buzz_on_prime) { + case true: + setStr = 'T'; + break; + + case false: + setStr = 'F'; + break; + + case undefined: + default: + setStr = 'U'; + break; + } } // creates a list of prime factors of n and outputs them as a string, if n is prime outputs "Prime Time!" @@ -69,9 +86,16 @@ function draw() { g.setColor(0,0,0); g.fillRect(Bangle.appRect); + g.setColor(100,100,100); + + if (settings.debug) { + g.setFontLatoSmall(); + g.setFontAlign(0, 0); + g.drawString(setStr, w/2, h/4); + } + g.setFontLato(); g.setFontAlign(0, 0); - g.setColor(100,100,100); g.drawString(timeStr, w/2, h/2); g.setFontLatoSmall(); diff --git a/apps/primetimelato/app.png b/apps/primetimelato/app.png index 2a84c62a0..e5762b97c 100644 Binary files a/apps/primetimelato/app.png and b/apps/primetimelato/app.png differ diff --git a/apps/primetimelato/icon.js b/apps/primetimelato/icon.js index 06f93e2ef..7c8d5380b 100644 --- a/apps/primetimelato/icon.js +++ b/apps/primetimelato/icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCEBAU/AQP4gfAj8AgPwAoMPwED8AFBg/AAYIBDA4ngg4TB4EBApkPKgJSBJQIFTMgIFCJIIFDKoIFEvgFBGoMAnw7DP4IFEh+BAoItBg+DNIQwBMIaeCKoKxCPoIzCEgKVHUIqtFXIrFFaIrdFdIwAV")) +require("heatshrink").decompress(atob("mEw4X/AAIHB8cYttrJf4AR1gKJgdYBZMCBZdcBZMNsALKuALJhNABZMFwALJmvAAwkOqvAmtAkwSF83+uEV4EMOIpZBznWII5NB7mXGo5BB7Z0HkpBB6x0HFYXVNA4rC6pcFAANXDQhSFqgaEBZGYaBLfIaAUBBZUJNQ4jCm+cHZPcBZFXgdwzELBg1W/PAy/rBY3VPAOVTY863kAnaPHAH4A/ADAA==")) diff --git a/apps/primetimelato/metadata.json b/apps/primetimelato/metadata.json index 6b292c380..400220b10 100644 --- a/apps/primetimelato/metadata.json +++ b/apps/primetimelato/metadata.json @@ -1,6 +1,6 @@ { "id": "primetimelato", - "name": "Prime Time Lato Clock", - "version": "0.02", + "name": "Prime Lato", + "version": "0.04", "type": "clock", "description": "A clock that tells you the primes of the time in the Lato font", "icon": "app.png", diff --git a/apps/primetimelato/settings.js b/apps/primetimelato/settings.js index 5550055eb..069c976c8 100644 --- a/apps/primetimelato/settings.js +++ b/apps/primetimelato/settings.js @@ -3,7 +3,8 @@ // initialize with default settings... let s = { - 'buzz_on_prime': true + 'buzz_on_prime': true, + 'debug': false } // ...and overwrite them with any saved values @@ -29,6 +30,16 @@ s.buzz_on_prime = v; save(); }, + }, + + 'Debug': { + value: !!s.debug, + onchange: v => { + s.debug = v; + save(); + }, } + + }) }) diff --git a/apps/rebble/ChangeLog b/apps/rebble/ChangeLog index c009a4ec1..78ba0c5da 100644 --- a/apps/rebble/ChangeLog +++ b/apps/rebble/ChangeLog @@ -9,7 +9,8 @@ 0.09: fix battery icon size 0.10: Tell clock widgets to hide. 0.11: fix issue https://github.com/espruino/BangleApps/issues/2128 (#2128) ( settings undefined ) -0.12: implemented widget_utils +0.12: implemented widget_utils 0.13: convert var/function into let -0.14: cleanup code and fix fastload issue -0.15: fix draw before widget hide \ No newline at end of file +0.14: cleanup code and fix fastload issue +0.15: fix draw before widget hide +0.16: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/rebble/metadata.json b/apps/rebble/metadata.json index 7042fcb95..c380204a4 100644 --- a/apps/rebble/metadata.json +++ b/apps/rebble/metadata.json @@ -2,7 +2,7 @@ "id": "rebble", "name": "Rebble Clock", "shortName": "Rebble", - "version": "0.15", + "version": "0.16", "description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion", "readme": "README.md", "icon": "rebble.png", @@ -14,7 +14,6 @@ "storage": [ {"name":"rebble.app.js","url":"rebble.app.js"}, {"name":"rebble.settings.js","url":"rebble.settings.js"}, - {"name":"rebble.img","url":"rebble.icon.js","evaluate":true}, - {"name":"suncalc","url":"suncalc.js"} + {"name":"rebble.img","url":"rebble.icon.js","evaluate":true} ] } diff --git a/apps/rebble/rebble.app.js b/apps/rebble/rebble.app.js index fd0c8f7f9..445c30125 100644 --- a/apps/rebble/rebble.app.js +++ b/apps/rebble/rebble.app.js @@ -11,7 +11,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) { { - let SunCalc = require("suncalc"); + let SunCalc = require("suncalc"); // from modules folder const SETTINGS_FILE = "rebble.json"; const LOCATION_FILE = "mylocation.json"; const GLOBAL_SETTINGS = "setting.json"; @@ -49,7 +49,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) { } if(settings.sideTap!=0) - sideBar=parseInt(settings.sideTap)-1; //tab to + sideBar=parseInt(settings.sideTap)-1; //tab to is12Hour = (require("Storage").readJSON(GLOBAL_SETTINGS, 1) || {})["12hour"] || false; } @@ -110,15 +110,15 @@ Graphics.prototype.setFontKdamThmor = function(scale) { let date = new Date(); let hh = date.getHours(); let mm = date.getMinutes(); - + hh = formatHours(hh); mm = zeroPad(mm,2); - + //const t = 6; if (drawCount % 60 == 0) updateSunRiseSunSet(location.lat, location.lon); - + g.reset(); g.setColor(g.theme.bg); g.fillRect(0, 0, w2, h); @@ -143,7 +143,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) { drawSideBar3(); break; } - + drawCount++; queueDraw(); } @@ -154,14 +154,14 @@ Graphics.prototype.setFontKdamThmor = function(scale) { let dd=date.getDate(); let mm=require("date_utils").month(date.getMonth()+1,1).toUpperCase(); - + drawBattery(w2 + (w-w2-wb)/2, h/10, wb, 17); setTextColor(); g.setFont('Vector', 20); g.setFontAlign(0, -1); g.drawString(E.getBattery() + '%', w3, (h/10) + 17 + 7); - + drawDateAndCalendar(w3, h/2, dy, dd, mm); } @@ -250,7 +250,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) { } } - + // format steps so they fit in the place let formatSteps=function() { let s = Bangle.getHealthStatus("day").steps; @@ -292,8 +292,8 @@ Graphics.prototype.setFontKdamThmor = function(scale) { - let chargingListener= function(charging) { - + let chargingListener= function(charging) { + //redraw the sidebar ( with the battery ) switch(sideBar) { case 0: @@ -304,7 +304,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) { break; } } - + let deleteAll=function() { // Called to unload all of the clock app @@ -320,7 +320,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) { log_debug("starting.."); loadSettings(); loadLocation(); - + if(settings.autoCycle || settings.sideTap==0) { Bangle.setUI({ @@ -332,7 +332,7 @@ Graphics.prototype.setFontKdamThmor = function(scale) { if (btn>0) nextSidebar(); draw(); }); - + } else{ Bangle.setUI({ @@ -354,4 +354,4 @@ Graphics.prototype.setFontKdamThmor = function(scale) { main(); -} \ No newline at end of file +} diff --git a/apps/rebble/suncalc.js b/apps/rebble/suncalc.js deleted file mode 100644 index d86f039c5..000000000 --- a/apps/rebble/suncalc.js +++ /dev/null @@ -1,143 +0,0 @@ -/* - (c) 2011-2015, Vladimir Agafonkin - SunCalc is a JavaScript library for calculating sun/moon position and light phases. - https://github.com/mourner/suncalc - - edit for banglejs -*/ - -(function () { 'use strict'; - -// shortcuts for easier to read formulas - -var PI = Math.PI, - sin = Math.sin, - cos = Math.cos, - tan = Math.tan, - asin = Math.asin, - atan = Math.atan2, - acos = Math.acos, - rad = PI / 180; - -// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas - - -// date/time constants and conversions - -var dayMs = 1000 * 60 * 60 * 24, - J1970 = 2440588, - J2000 = 2451545; - -function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } -function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } -function toDays(date) { return toJulian(date) - J2000; } - - -// general calculations for position - -var e = rad * 23.4397; // obliquity of the Earth - -function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); } - - -// general sun calculations - -function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); } - -function eclipticLongitude(M) { - - var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center - P = rad * 102.9372; // perihelion of the Earth - - return M + C + P + PI; -} - -var SunCalc = {}; - - -// sun times configuration (angle, morning name, evening name) - -var times = SunCalc.times = [ - [-0.833, 'sunrise', 'sunset' ], - [ -0.3, 'sunriseEnd', 'sunsetStart' ], - [ -6, 'dawn', 'dusk' ], - [ -12, 'nauticalDawn', 'nauticalDusk'], - [ -18, 'nightEnd', 'night' ], - [ 6, 'goldenHourEnd', 'goldenHour' ] -]; - - - -// calculations for sun times - -var J0 = 0.0009; - -function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } - -function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; } -function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); } - -function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); } -function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; } - -// returns set time for the given sun altitude -function getSetJ(h, lw, phi, dec, n, M, L) { - - var w = hourAngle(h, phi, dec), - a = approxTransit(w, lw, n); - return solarTransitJ(a, M, L); -} - - -// calculates sun times for a given date, latitude/longitude, and, optionally, -// the observer height (in meters) relative to the horizon - -SunCalc.getTimes = function (date, lat, lng, height) { - - height = height || 0; - - var lw = rad * -lng, - phi = rad * lat, - - dh = observerAngle(height), - - d = toDays(date), - n = julianCycle(d, lw), - ds = approxTransit(0, lw, n), - - M = solarMeanAnomaly(ds), - L = eclipticLongitude(M), - dec = declination(L, 0), - - Jnoon = solarTransitJ(ds, M, L), - - i, len, time, h0, Jset, Jrise; - - - var result = { - solarNoon: fromJulian(Jnoon), - nadir: fromJulian(Jnoon - 0.5) - }; - - for (i = 0, len = times.length; i < len; i += 1) { - time = times[i]; - h0 = (time[0] + dh) * rad; - - Jset = getSetJ(h0, lw, phi, dec, n, M, L); - Jrise = Jnoon - (Jset - Jnoon); - - result[time[1]] = fromJulian(Jrise); - result[time[2]] = fromJulian(Jset); - } - - return result; -}; - - -// export as Node module / AMD module / browser variable -if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc; -else if (typeof define === 'function' && define.amd) define(SunCalc); -else window.SunCalc = SunCalc; - - -}()); \ No newline at end of file diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 3cf5d8372..a4d0b7e88 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -23,3 +23,4 @@ 0.17: Use default Bangle formatter for booleans 0.18: Improve widget load speed, allow currently recording track to be plotted in openstmap 0.19: Fix track plotting code +0.20: Automatic translation of some more strings. diff --git a/apps/recorder/app.js b/apps/recorder/app.js index 9006d2236..8dcc4c3ed 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -61,7 +61,7 @@ function showMainMenu() { setTimeout(function() { E.showMenu(); WIDGETS["recorder"].setRecording(v).then(function() { - print("Complete"); + print(/*LANG*/"Complete"); loadSettings(); print(settings.recording); showMainMenu(); @@ -96,7 +96,7 @@ function showMainMenu() { }; var recorders = WIDGETS["recorder"].getRecorders(); Object.keys(recorders).forEach(id=>{ - mainmenu["Log "+recorders[id]().name] = menuRecord(id); + mainmenu[/*LANG*/"Log "+recorders[id]().name] = menuRecord(id); }); delete recorders; return E.showMenu(mainmenu); @@ -404,7 +404,7 @@ function viewTrack(filename, info) { title: title, miny: min, maxy: max, - xlabel : x=>Math.round(x*dur/(60*infn.length))+" min" // minutes + xlabel : x=>Math.round(x*dur/(60*infn.length))+/*LANG*/" min" // minutes }); g.setFont("6x8",2); g.setFontAlign(0,0,3); diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index bbc08e0ef..91ceaf86e 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder", "shortName": "Recorder", - "version": "0.19", + "version": "0.20", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog index 711326200..6d9d5caee 100644 --- a/apps/sched/ChangeLog +++ b/apps/sched/ChangeLog @@ -16,3 +16,4 @@ 0.13: Ask to delete a timer after stopping it 0.14: Added clkinfo for alarms and timers 0.15: Automatic translation of some string in clkinfo +0.16: Improve support for date timezone diff --git a/apps/sched/boot.js b/apps/sched/boot.js index 98bb0ff7d..c1bb1fc66 100644 --- a/apps/sched/boot.js +++ b/apps/sched/boot.js @@ -13,7 +13,7 @@ && (a.last != d) // not already fired today && (a.t + 60000 > currentTime) // is not in the past by >1 minute && (a.dow >> time.getDay() & 1) // is allowed on this day of the week - && (!a.date || a.date == time.toISOString().substr(0, 10)) // is allowed on this date + && (!a.date || a.date == time.toLocalISOString().substr(0, 10)) // is allowed on this date ); if (active.length) { active = active.sort((a,b)=>a.t-b.t); // sort by time diff --git a/apps/sched/lib.js b/apps/sched/lib.js index 74018dcde..c8961b9e3 100644 --- a/apps/sched/lib.js +++ b/apps/sched/lib.js @@ -21,7 +21,7 @@ exports.getActiveAlarms = function (alarms, time) { && (a.last != time.getDate()) // not already fired today && (a.t < currentTime) && (a.dow >> time.getDay() & 1) // is allowed on this day of the week - && (!a.date || a.date == time.toISOString().substr(0, 10)) // is allowed on this date + && (!a.date || a.date == time.toLocalISOString().substr(0, 10)) // is allowed on this date ) .sort((a, b) => a.t - b.t); } @@ -46,7 +46,7 @@ exports.getTimeToAlarm = function(alarm, time) { if (!alarm) return undefined; if (!time) time = new Date(); var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000); - var active = alarm.on && (alarm.dow>>((time.getDay()+(alarm.t>((time.getDay()+(alarm.t { + + g.reset().setFont('Vector',24).setBgColor(options.bg).setColor(options.fg); + + //use info.text.toString(), steps does not have length defined + var text_w = g.stringWidth(info.text.toString()); + // gap between image and text + var gap = 10; + // width of the image and text combined + var w = gap + (info.img ? 24 :0) + text_w; + // different fg color if we tapped on the menu + if (options.focus) g.setColor(options.hl); + + // clear the whole info line + g.clearRect(0, options.y -1, g.getWidth(), options.y+24); + + // draw the image if we have one + if (info.img) { + // image start + var x = (g.getWidth() / 2) - (w/2); + g.drawImage(info.img, x, options.y); + // draw the text to the side of the image (left/centre alignment) + g.setFontAlign(-1,0).drawString(info.text, x + 23 + gap, options.y+12); + } else { + // text only option, not tested yet + g.setFontAlign(0,0).drawString(info.text, g.getWidth() / 2, options.y+12); + } + + }; + + // clock_info_support + // retrieve all the clock_info modules that are installed + let clockInfoItems = require("clock_info").load(); + + // clock_info_support + // setup the way we wish to interact with the menu + // the hl property defines the color the of the info when the menu is selected after tapping on it + let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:64, y:132, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#0ff"} ); + + // timeout used to update every minute + var drawTimeout; + g.clear(); + + // Show launcher when middle button pressed, add updown button handlers + Bangle.setUI({ + mode : "clock", + remove : function() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + // remove info menu + clockInfoMenu.remove(); + delete clockInfoMenu; + } + }); + + // Load widgets + Bangle.loadWidgets(); + draw(); + setTimeout(Bangle.drawWidgets,0); +} // end of clock diff --git a/apps/simplestpp/app.png b/apps/simplestpp/app.png new file mode 100644 index 000000000..814306471 Binary files /dev/null and b/apps/simplestpp/app.png differ diff --git a/apps/simplestpp/icon.js b/apps/simplestpp/icon.js new file mode 100644 index 000000000..e4e40c82c --- /dev/null +++ b/apps/simplestpp/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4X/AAIHBqOM997IGkq1AKIltVqt4BQ0DBQIAB4ALFktVv/9qtYBYvVA4Ulq4KEgNVwAEBgVVoALDgtcAoc1qAFDitgAocJqguEGoowDgVWBYuVGoUBFwgwCHgUNGgUNuBCCAYY6CBYcJCYUlDYYLCgJxCmozCBYcCG4UVG4QLDgBgCBZeXBY/WBYcC1WtvWqGoILEVAIACJoILQgf/+tf/7jBBYg7JL66DLTZazLZZbjLfZcA6oLFq4EDio8CHQReCGgQwEmpCCHgVVFIUCVAQAD6plCkouEA4VVv/9qoPCAAcDZYa/BAAstBQN4BQwABlWoBRAAr")) diff --git a/apps/simplestpp/metadata.json b/apps/simplestpp/metadata.json new file mode 100644 index 000000000..10f756e32 --- /dev/null +++ b/apps/simplestpp/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "simplestpp", + "name": "Simplest++ Clock", + "version": "0.01", + "description": "The simplest working clock, with fast load and clock_info, acts as a tutorial piece", + "readme": "README.md", + "icon": "app.png", + "screenshots": [{"url":"screenshot3.png"}], + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"simplestpp.app.js","url":"app.js"}, + {"name":"simplestpp.img","url":"icon.js","evaluate":true} + ] +} diff --git a/apps/simplestpp/screenshot1.png b/apps/simplestpp/screenshot1.png new file mode 100644 index 000000000..94cb35cd6 Binary files /dev/null and b/apps/simplestpp/screenshot1.png differ diff --git a/apps/simplestpp/screenshot2.png b/apps/simplestpp/screenshot2.png new file mode 100644 index 000000000..d24d55b02 Binary files /dev/null and b/apps/simplestpp/screenshot2.png differ diff --git a/apps/simplestpp/screenshot3.png b/apps/simplestpp/screenshot3.png new file mode 100644 index 000000000..af7ae7249 Binary files /dev/null and b/apps/simplestpp/screenshot3.png differ diff --git a/apps/sleeplogalarm/ChangeLog b/apps/sleeplogalarm/ChangeLog new file mode 100644 index 000000000..80f8bd7e4 --- /dev/null +++ b/apps/sleeplogalarm/ChangeLog @@ -0,0 +1,4 @@ +0.01: New App! +0.02: Add "from Consec."-setting +0.03: Correct how to ignore last triggered alarm +0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings \ No newline at end of file diff --git a/apps/sleeplogalarm/README.md b/apps/sleeplogalarm/README.md new file mode 100644 index 000000000..005377fb1 --- /dev/null +++ b/apps/sleeplogalarm/README.md @@ -0,0 +1,56 @@ +# Sleep Log Alarm + +This widget searches for active alarms and raises an own alarm event up to the defined time earlier, if in light sleep or awake phase. Optional the earlier alarm will only be triggered if comming from or in consecutive sleep. The settings of the earlier alarm can be adjusted and it is possible to filter the targeting alarms by time and message. By default the time of the targeting alarm is displayed inside the widget which can be adjusted, too. + +_This widget does not detect sleep on its own and can not create alarms. It requires the [sleeplog](/apps/?id=sleeplog) app and any alarm app that uses [sched](/apps/?id=sched) to be installed._ + +--- +### Settings +--- + + - __earlier__ | duration to trigger alarm earlier + _10min_ / _20min_ / __30min__ / ... / _120min_ + - __from Consec.__ | only trigger if comming from consecutive sleep + _on_ / __off__ + - __vib pattern__ | vibration pattern for the earlier alarm + __..__ / ... + - __msg__ | customized message for the earlier alarm + __...__ / ... + - __msg as prefix__ | use the customized message as prefix to the original message or replace it comlpetely if disabled + __on__ / _off_ + - __disable alarm__ | if enabled the original alarm will be disabled + _on_ / __off__ + - __auto snooze__ | auto snooze option for the earlier alarm + __on__ / _off_ + - __Filter Alarm__ submenu + - __time from__ | exclude alarms before this time + _0:00_ / _0:15_ / ... / __3:00__ / ... / _24:00_ + - __time to__ | exclude alarms after this time + _0:00_ / _0:15_ / ... / __12:00__ / ... / _24:00_ + - __msg includes__ | include only alarms including this string in msg + __""__ / ... + - __Widget__ submenu + - __hide__ | completely hide the widget + _on_ / __off__ + - __show time__ | show the time of the targeting alarm + __on__ / _off_ + - __color__ | color of the widget + _red_ / __yellow__ / ... / _white_ + - __Enabled__ | completely en-/disables the background service + __on__ / _off_ + +--- +### Worth Mentioning +--- + +#### Requests, Bugs and Feedback +Please leave requests and bug reports by raising an issue at [github.com/storm64/BangleApps](https://github.com/storm64/BangleApps) (or send me a [mail](mailto:banglejs@storm64.de)). + +#### Creator +Storm64 ([Mail](mailto:banglejs@storm64.de), [github](https://github.com/storm64)) + +#### Attributions +The app icon is downloaded from [https://icons8.com](https://icons8.com). + +#### License +[MIT License](LICENSE) diff --git a/apps/sleeplogalarm/app.png b/apps/sleeplogalarm/app.png new file mode 100644 index 000000000..1a8e53865 Binary files /dev/null and b/apps/sleeplogalarm/app.png differ diff --git a/apps/sleeplogalarm/lib.js b/apps/sleeplogalarm/lib.js new file mode 100644 index 000000000..343e811af --- /dev/null +++ b/apps/sleeplogalarm/lib.js @@ -0,0 +1,141 @@ +// load library +var sched = require("sched"); + +// find next active alarm in range +function getNextAlarm(allAlarms, fo, withId) { + if (withId) allAlarms = allAlarms.map((a, idx) => { + a.idx = idx; + return a; + }); + // return next active alarms in range, filter for + // active && not timer && not own alarm && + // after from && before to && includes msg + var ret = allAlarms.filter( + a => a.on && !a.timer && a.id !== "sleeplog" && + a.t >= fo.from && a.t < fo.to && (!fo.msg || a.msg.includes(fo.msg)) + ).map(a => { // add time to alarm + a.tTo = sched.getTimeToAlarm(a); + return a; + }).filter(a => a.tTo // filter non active alarms + // sort to get next alarm first + ).sort((a, b) => a.tTo - b.tTo); + // prevent triggering for an already triggered alarm again if available + if (fo.lastDate) { + var toLast = fo.lastDate - new Date().valueOf() + 1000; + if (toLast > 0) ret = ret.filter(a => a.tTo > toLast); + } + // return first entry + return ret[0] || {}; +} + +exports = { + // function to read settings with defaults + getSettings: function() { + return Object.assign({ + enabled: true, + earlier: 30, + fromConsec: false, + vibrate: "..", + msg: "...\n", + msgAsPrefix: true, + disableOnAlarm: false, // !!! not available if alarm is at the next day + as: true, + filter: { + from: 3 * 36E5, + to: 12 * 36E5, + msg: "" + }, + wid: { + hide: false, + time: true, + color: g.theme.dark ? 65504 : 31 // yellow or blue + } + }, require("Storage").readJSON("sleeplogalarm.settings.json", true) || {}); + }, + + // widget reload function + widReload: function() { + // abort if trigger object is not available + if (typeof (global.sleeplog || {}).trigger !== "object") return; + + // read settings to calculate alarm range + var settings = exports.getSettings(); + + // set the alarm time + this.time = getNextAlarm(sched.getAlarms(), settings.filter).t; + + // abort if no alarm time could be found inside range + if (!this.time) return; + + // set widget width if not hidden + if (!this.hidden) this.width = 8; + + // insert sleeplogalarm conditions and function + sleeplog.trigger.sleeplogalarm = { + from: this.time - settings.earlier * 6E4, + to: this.time - 1, + fn: function (data) { + // execute trigger function if on light sleep or awake + // and if set if comming from consecutive + if ((data.status === 3 || data.status === 2) && !settings.fromConsec || + data.consecutive === 3 || data.prevConsecutive === 3) + require("sleeplogalarm").trigger(); + } + }; + }, + + // trigger function + trigger: function() { + // read settings + var settings = exports.getSettings(); + + // read all alarms + var allAlarms = sched.getAlarms(); + + // find first active alarm + var alarm = getNextAlarm(sched.getAlarms(), settings.filter, settings.disableOnAlarm); + + // return if no alarm is found + if (!alarm) return; + + // get now + var now = new Date(); + + // get date of the alarm + var aDate = new Date(now + alarm.tTo); + + // disable earlier triggered alarm if set + if (settings.disableOnAlarm) { + // set alarms last to the day it would trigger + allAlarms[alarm.idx].last = aDate.getDate(); + // remove added indexes + allAlarms = allAlarms.map(a => { + delete a.idx; + return a; + }); + } + + // add new alarm for now with data from found alarm + allAlarms.push({ + id: "sleeplog", + appid: "sleeplog", + on: true, + t: ((now.getHours() * 60 + now.getMinutes()) * 60 + now.getSeconds()) * 1000, + dow: 127, + msg: settings.msg + (settings.msgAsPrefix ? alarm.msg || "" : ""), + vibrate: settings.vibrate || alarm.vibrate, + as: settings.as, + del: true + }); + + // save date of the alarm to prevent triggering for the same alarm again + settings.filter.lastDate = aDate.valueOf(); + require("Storage").writeJSON("sleeplogalarm.settings.json", settings); + + // write changes + sched.setAlarms(allAlarms); + + // trigger sched.js + load("sched.js"); + } +}; \ No newline at end of file diff --git a/apps/sleeplogalarm/metadata.json b/apps/sleeplogalarm/metadata.json new file mode 100644 index 000000000..fd85507e6 --- /dev/null +++ b/apps/sleeplogalarm/metadata.json @@ -0,0 +1,21 @@ +{ + "id":"sleeplogalarm", + "name":"Sleep Log Alarm", + "shortName": "SleepLogAlarm", + "version": "0.04", + "description": "Enhance your morning and let your alarms wake you up when you are in light sleep.", + "icon": "app.png", + "type": "widget", + "tags": "tool,widget", + "supports": ["BANGLEJS2"], + "dependencies": { + "scheduler": "type", + "sleeplog": "app" + }, + "readme": "README.md", + "storage": [ + {"name": "sleeplogalarm", "url": "lib.js"}, + {"name": "sleeplogalarm.settings.js", "url": "settings.js"}, + {"name": "sleeplogalarm.wid.js", "url": "widget.js"} + ] +} diff --git a/apps/sleeplogalarm/settings.js b/apps/sleeplogalarm/settings.js new file mode 100644 index 000000000..1f3a13272 --- /dev/null +++ b/apps/sleeplogalarm/settings.js @@ -0,0 +1,192 @@ +(function(back) { + // read settings + var settings = require("sleeplogalarm").getSettings(); + + // write change to storage + function writeSetting() { + require("Storage").writeJSON("sleeplogalarm.settings.json", settings); + } + + // read input from keyboard + function readInput(v, cb) { + // setTimeout required to load after menu refresh + setTimeout((v, cb) => { + if (require("Storage").read("textinput")) { + g.clear(); + require("textinput").input({text: v}).then(v => cb(v)); + } else { + E.showAlert(/*LANG*/"No keyboard app installed").then(() => cb()); + } + }, 0, v, cb); + } + + // show widget menu + function showFilterMenu() { + // set menu + var filterMenu = { + "": { + title: "Filter Alarm" + }, + /*LANG*/"< Back": () => showMain(8), + /*LANG*/"time from": { + value: settings.filter.from / 6E4, + step: 10, + min: 0, + max: 1440, + wrap: true, + noList: true, + format: v => (0|v/60) + ":" + ("" + (v%60)).padStart(2, "0"), + onchange: v => { + settings.filter.from = v * 6E4; + writeSetting(); + } + }, + /*LANG*/"time to": { + value: settings.filter.to / 6E4, + step: 10, + min: 0, + max: 1440, + wrap: true, + noList: true, + format: v => (0|v/60) + ":" + ("" + (v%60)).padStart(2, "0"), + onchange: v => { + settings.filter.to = v * 6E4; + writeSetting(); + } + }, + /*LANG*/"msg includes": { + value: settings.filter.msg, + format: v => !v ? "" : v.length > 6 ? v.substring(0, 6)+"..." : v, + onchange: v => readInput(v, v => { + settings.filter.msg = v; + writeSetting(); + showFilterMenu(3); + }) + } + }; + var menu = E.showMenu(filterMenu); + } + + // show widget menu + function showWidMenu() { + // define color values and names + var colName = ["red", "yellow", "green", "cyan", "blue", "magenta", "black", "white"]; + var colVal = [63488, 65504, 2016, 2047, 31, 63519, 0, 65535]; + + // set menu + var widgetMenu = { + "": { + title: "Widget Settings" + }, + /*LANG*/"< Back": () => showMain(9), + /*LANG*/"hide": { + value: settings.wid.hide, + onchange: v => { + settings.wid.hide = v; + writeSetting(); + } + }, + /*LANG*/"show time": { + value: settings.wid.time, + onchange: v => { + settings.wid.time = v; + writeSetting(); + } + }, + /*LANG*/"color": { + value: colVal.indexOf(settings.wid.color), + min: 0, + max: colVal.length -1, + wrap: true, + format: v => colName[v], + onchange: v => { + settings.wid.color = colVal[v]; + writeSetting(); + } + } + }; + var menu = E.showMenu(widgetMenu); + } + + // show main menu + function showMain(selected) { + // set menu + var mainMenu = { + "": { + title: "Sleep Log Alarm", + selected: selected + }, + /*LANG*/"< Back": () => back(), + /*LANG*/"erlier": { + value: settings.earlier, + step: 10, + min: 10, + max: 120, + wrap: true, + noList: true, + format: v => v + /*LANG*/"min", + onchange: v => { + settings.earlier = v; + writeSetting(); + } + }, + /*LANG*/"from Consec.": { + value: settings.fromConsec, + onchange: v => { + settings.fromConsec = v; + writeSetting(); + } + }, + /*LANG*/"vib pattern": require("buzz_menu").pattern( + settings.vibrate, + v => { + settings.vibrate = v; + writeSetting(); + } + ), + /*LANG*/"msg": { + value: settings.msg, + format: v => !v ? "" : v.length > 6 ? v.substring(0, 6)+"..." : v, + onchange: v => readInput(v, v => { + settings.msg = v; + writeSetting(); + showMenu(4); + }) + }, + /*LANG*/"msg as prefix": { + value: settings.msgAsPrefix, + onchange: v => { + settings.msgAsPrefix = v; + writeSetting(); + } + }, + /*LANG*/"disable alarm": { + value: settings.disableOnAlarm, + onchange: v => { + settings.disableOnAlarm = v; + writeSetting(); + } + }, + /*LANG*/"auto snooze": { + value: settings.as, + onchange: v => { + settings.as = v; + writeSetting(); + } + }, + /*LANG*/"Filter Alarm": () => showFilterMenu(), + /*LANG*/"Widget": () => showWidMenu(), + /*LANG*/"Enabled": { + value: settings.enabled, + onchange: v => { + settings.enabled = v; + writeSetting(); + } + } + }; + var menu = E.showMenu(mainMenu); + } + + // draw main menu + showMain(); +}) diff --git a/apps/sleeplogalarm/widget.js b/apps/sleeplogalarm/widget.js new file mode 100644 index 000000000..e3171751f --- /dev/null +++ b/apps/sleeplogalarm/widget.js @@ -0,0 +1,32 @@ +// check if enabled in settings +if ((require("Storage").readJSON("sleeplogalarm.settings.json", true) || {enabled: true}).enabled) { + // read settings + settings = require("sleeplogalarm").getSettings(); // is undefined if used with var + + // insert neccessary settings into widget + WIDGETS.sleeplogalarm = { + area: "tl", + width: 0, + time: 0, + earlier: settings.earlier, + draw: function () { + // draw zzz + g.reset().setColor(settings.wid.color).drawImage(atob("BwoBD8SSSP4EEEDg"), this.x + 1, this.y); + // call function to draw the time of alarm if a alarm is found + if (this.time) this.drawTime(this.time + 1); + }, + drawTime: () => {}, + reload: require("sleeplogalarm").widReload + }; + + // add function to draw the time of alarm if enabled + if (settings.wid.time) WIDGETS.sleeplogalarm.drawTime = function(time) { + // directly include Font4x5Numeric + g.setFontCustom(atob("CAZMA/H4PgvXoK1+DhPg7W4P1uCEPg/X4O1+AA=="), 46, atob("AgQEAgQEBAQEBAQE"), 5).setFontAlign(1, 1); + g.drawString(0|(time / 36E5), this.x + this.width + 1, this.y + 17); + g.drawString(0|((time / 36E5)%1 * 60), this.x + this.width + 1, this.y + 23); + }; + + // load widget + WIDGETS.sleeplogalarm.reload(); +} \ No newline at end of file diff --git a/apps/sleepphasealarm/ChangeLog b/apps/sleepphasealarm/ChangeLog index 80b2e554b..795c62fa2 100644 --- a/apps/sleepphasealarm/ChangeLog +++ b/apps/sleepphasealarm/ChangeLog @@ -12,3 +12,4 @@ Add setting to disable scheduler alarm 0.10: Fix: Do not wake when falling asleep 0.11: Minor tweaks +0.12: Support javascript command to execute as defined in scheduler 'js' configuration diff --git a/apps/sleepphasealarm/app.js b/apps/sleepphasealarm/app.js index a5193b244..ba8bff9b2 100644 --- a/apps/sleepphasealarm/app.js +++ b/apps/sleepphasealarm/app.js @@ -173,12 +173,16 @@ if (nextAlarmDate !== undefined) { setTimeout(load, 1000); } else if (measure && now >= minAlarm && swest === false) { addLog(now, "alarm"); - buzz(); measure = false; - if (config.settings.disableAlarm) { - // disable alarm for scheduler - nextAlarmConfig.last = now.getDate(); - require("Storage").writeJSON("sched.json", alarms); + if (nextAlarmConfig.js) { + eval(nextAlarmConfig.js); // run nextAlarmConfig.js if set + } else { + buzz(); + if (config.settings.disableAlarm) { + // disable alarm for scheduler + nextAlarmConfig.last = now.getDate(); + require('Storage').writeJSON('sched.json', alarms); + } } } }); diff --git a/apps/sleepphasealarm/metadata.json b/apps/sleepphasealarm/metadata.json index fd3366812..ced99062f 100644 --- a/apps/sleepphasealarm/metadata.json +++ b/apps/sleepphasealarm/metadata.json @@ -2,7 +2,7 @@ "id": "sleepphasealarm", "name": "SleepPhaseAlarm", "shortName": "SleepPhaseAlarm", - "version": "0.11", + "version": "0.12", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", "icon": "app.png", "tags": "alarm", diff --git a/apps/slopeclockpp/ChangeLog b/apps/slopeclockpp/ChangeLog index 9ae4ccc96..eef1840ea 100644 --- a/apps/slopeclockpp/ChangeLog +++ b/apps/slopeclockpp/ChangeLog @@ -6,3 +6,4 @@ Allowed black/white background (as that can look nice too) 0.05: Images in clkinfo are optional now 0.06: Added support for locale based time +0.07: README file update as UI interaction was not easy to understand diff --git a/apps/slopeclockpp/README.md b/apps/slopeclockpp/README.md index 9352db685..c9e9d523d 100644 --- a/apps/slopeclockpp/README.md +++ b/apps/slopeclockpp/README.md @@ -10,6 +10,8 @@ to change (top right or bottom left). It should change color showing it is selected. * Swipe up or down to cycle through the info screens that can be displayed + when you have finished tap again towards the centre of the screen to unselect. + * Swipe left or right to change the type of info screens displayed (by default there is only one type of data so this will have no effect) diff --git a/apps/slopeclockpp/metadata.json b/apps/slopeclockpp/metadata.json index bebed4b71..3243d389a 100644 --- a/apps/slopeclockpp/metadata.json +++ b/apps/slopeclockpp/metadata.json @@ -1,6 +1,6 @@ { "id": "slopeclockpp", "name": "Slope Clock ++", - "version":"0.06", + "version":"0.07", "description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows extra information and allows the colors to be selected.", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/smpltmr/ChangeLog b/apps/smpltmr/ChangeLog index b09100f50..805e8546f 100644 --- a/apps/smpltmr/ChangeLog +++ b/apps/smpltmr/ChangeLog @@ -1,4 +1,5 @@ 0.01: Release 0.02: Rewrite with new interface 0.03: Added clock infos to expose timer functionality to clocks. -0.04: Improvements of clock infos. \ No newline at end of file +0.04: Improvements of clock infos. +0.05: Updated clkinfo icon. \ No newline at end of file diff --git a/apps/smpltmr/clkinfo.js b/apps/smpltmr/clkinfo.js index 1a63a9b7e..c16e6127e 100644 --- a/apps/smpltmr/clkinfo.js +++ b/apps/smpltmr/clkinfo.js @@ -63,10 +63,9 @@ } catch(ex){ } } - var img = atob("GBiBAeAAB+AAB/v/3/v/3/v/3/v/3/v/n/n/H/z+P/48//85//+b//+b//8p//4E//yCP/kBH/oAn/oAX/oAX/oAX/oAX+AAB+AABw==") var smpltmrItems = { name: "Timer", - img: img, + img: atob("GBiBAAB+AAB+AAAYAAAYAAB+AA3/sA+B8A4AcAwMMBgPGBgPmDAPjDAPzDAPzDP/zDP/zDH/jBn/mBj/GAw8MA4AcAeB4AH/gAB+AA=="), items: [ { name: null, diff --git a/apps/smpltmr/metadata.json b/apps/smpltmr/metadata.json index ce526d1ba..2191902de 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.04", + "version": "0.05", "description": "A very simple app to start a timer.", "icon": "app.png", "tags": "tool,alarm,timer,clkinfo", diff --git a/apps/spotrem/ChangeLog b/apps/spotrem/ChangeLog index 8e3d8b652..a92ed3de2 100644 --- a/apps/spotrem/ChangeLog +++ b/apps/spotrem/ChangeLog @@ -3,3 +3,6 @@ 0.03: change handling of intent extras. 0.04: New layout. 0.05: Add widgets field. Tweak layout. +0.06: Make compatible with Fastload Utils app. +0.07: Remove just the specific listeners to not interfere with Quick Launch +when fastloading. diff --git a/apps/spotrem/app.js b/apps/spotrem/app.js index 7e76d84bc..f9046c4a6 100644 --- a/apps/spotrem/app.js +++ b/apps/spotrem/app.js @@ -1,21 +1,23 @@ +{ /* Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}})); */ -var R; -var backToMenu = false; -var isPaused = true; -var dark = g.theme.dark; // bool +let R; +let widgetUtils = require("widget_utils"); +let backToMenu = false; +let isPaused = true; +let dark = g.theme.dark; // bool // The main layout of the app -function gfx() { - //Bangle.drawWidgets(); +let gfx = function() { + widgetUtils.hide(); R = Bangle.appRect; marigin = 8; // g.drawString(str, x, y, solid) g.clearRect(R); g.reset(); - + if (dark) {g.setColor(0x07E0);} else {g.setColor(0x03E0);} // Green on dark theme, DarkGreen on light theme. g.setFont("4x6:2"); g.setFontAlign(1, 0, 0); @@ -44,10 +46,10 @@ function gfx() { g.setFontAlign(1, 1, 0); g.drawString("Saved", R.x + R.w - 2*marigin, R.y + R.h - 2*marigin); -} +}; // Touch handler for main layout -function touchHandler(_, xy) { +let touchHandler = function(_, xy) { x = xy.x; y = xy.y; len = (R.wb-1 instead of a>b. if ((R.x-1 { - if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup"); - } + {mode : "updown", + remove : ()=>{ + Bangle.removeListener("touch", touchHandler); + Bangle.removeListener("swipe", swipeHandler); + clearWatch(buttonHandler); + widgetUtils.show(); + } + }, + ud => { + if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup"); + } ); Bangle.on("touch", touchHandler); Bangle.on("swipe", swipeHandler); -} - - + let buttonHandler = setWatch(()=>{load();}, BTN, {edge:'falling'}); +}; // Get back to the main layout -function backToGfx() { +let backToGfx = function() { E.showMenu(); g.clear(); g.reset(); - Bangle.removeAllListeners("touch"); - Bangle.removeAllListeners("swipe"); setUI(); gfx(); backToMenu = false; -} +}; /* The functions for interacting with Android and the Spotify app */ simpleSearch = ""; -function simpleSearchTerm() { // input a simple search term without tags, overrides search with tags (artist and track) +let simpleSearchTerm = function() { // input a simple search term without tags, overrides search with tags (artist and track) require("textinput").input({text:simpleSearch}).then(result => {simpleSearch = result;}).then(() => {E.showMenu(searchMenu);}); -} +}; artist = ""; -function artistSearchTerm() { // input artist to search for +let artistSearchTerm = function() { // input artist to search for require("textinput").input({text:artist}).then(result => {artist = result;}).then(() => {E.showMenu(searchMenu);}); -} +}; track = ""; -function trackSearchTerm() { // input track to search for +let trackSearchTerm = function() { // input track to search for require("textinput").input({text:track}).then(result => {track = result;}).then(() => {E.showMenu(searchMenu);}); -} +}; album = ""; -function albumSearchTerm() { // input album to search for +let albumSearchTerm = function() { // input album to search for require("textinput").input({text:album}).then(result => {album = result;}).then(() => {E.showMenu(searchMenu);}); -} +}; -function searchPlayWOTags() {//make a spotify search and play using entered terms +let searchPlayWOTags = function() {//make a spotify search and play using entered terms searchString = simpleSearch; Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); -} +}; -function searchPlayWTags() {//make a spotify search and play using entered terms +let searchPlayWTags = function() {//make a spotify search and play using entered terms searchString = (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\"")); Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); -} +}; -function playVreden() {//Play the track "Vreden" by Sara Parkman via spotify uri-link +let playVreden = function() {//Play the track "Vreden" by Sara Parkman via spotify uri-link Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function playVredenAlternate() {//Play the track "Vreden" by Sara Parkman via spotify uri-link +let playVredenAlternate = function() {//Play the track "Vreden" by Sara Parkman via spotify uri-link Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]})); -} +}; -function searchPlayVreden() {//Play the track "Vreden" by Sara Parkman via search and play +let searchPlayVreden = function() {//Play the track "Vreden" by Sara Parkman via search and play Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); -} +}; -function openAlbum() {//Play EP "The Blue Room" by Coldplay +let openAlbum = function() {//Play EP "The Blue Room" by Coldplay Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]})); -} +}; -function searchPlayAlbum() {//Play EP "The Blue Room" by Coldplay via search and play +let searchPlayAlbum = function() {//Play EP "The Blue Room" by Coldplay via search and play Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); -} +}; -function spotifyWidget(action) { +let spotifyWidget = function(action) { Bluetooth.println(JSON.stringify({t:"intent", action:("com.spotify.mobile.android.ui.widget."+action), package:"com.spotify.music", target:"broadcastreceiver"})); -} +}; -function gadgetbridgeWake() { +let gadgetbridgeWake = function() { Bluetooth.println(JSON.stringify({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"})); -} +}; -function spotifyPlaylistDW() { +let spotifyPlaylistDW = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function spotifyPlaylistDM1() { +let spotifyPlaylistDM1 = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E365VyzxE0mxF:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function spotifyPlaylistDM2() { +let spotifyPlaylistDM2 = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E38LZHLFnrM61:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function spotifyPlaylistDM3() { +let spotifyPlaylistDM3 = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36RU87qzgBFP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function spotifyPlaylistDM4() { +let spotifyPlaylistDM4 = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E396gGyCXEBFh:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function spotifyPlaylistDM5() { +let spotifyPlaylistDM5 = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E37a0Tt6CKJLP:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function spotifyPlaylistDM6() { +let spotifyPlaylistDM6 = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1E36UIQLQK79od:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function spotifyPlaylistDD() { +let spotifyPlaylistDD = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZF1EfWFiI7QfIAKq:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; -function spotifyPlaylistRR() { +let spotifyPlaylistRR = function() { Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXbs0XkE2V8sMO:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); -} +}; // Spotify Remote Menu -var spotifyMenu = { - "" : { title : " ", +let spotifyMenu = { + "" : { title : " Menu ", back: backToGfx }, "Controls" : ()=>{E.showMenu(controlMenu);}, "Search and play" : ()=>{E.showMenu(searchMenu);}, @@ -234,8 +240,8 @@ var spotifyMenu = { }; -var controlMenu = { - "" : { title : " ", +let controlMenu = { + "" : { title : " Controls ", back: () => {if (backToMenu) E.showMenu(spotifyMenu); if (!backToMenu) backToGfx();} }, "Play" : ()=>{Bangle.musicControl("play");}, @@ -246,8 +252,8 @@ var controlMenu = { "Messages Music Controls" : ()=>{load("messagesmusic.app.js");}, }; -var searchMenu = { - "" : { title : " ", +let searchMenu = { + "" : { title : " Search ", back: () => {if (backToMenu) E.showMenu(spotifyMenu); if (!backToMenu) backToGfx();} }, "Search term w/o tags" : ()=>{simpleSearchTerm();}, @@ -258,8 +264,8 @@ var searchMenu = { "Execute search and play with tags" : ()=>{searchPlayWTags();}, }; -var savedMenu = { - "" : { title : " ", +let savedMenu = { + "" : { title : " Saved ", back: () => {if (backToMenu) E.showMenu(spotifyMenu); if (!backToMenu) backToGfx();} }, "Play Discover Weekly" : ()=>{spotifyPlaylistDW();}, @@ -279,3 +285,4 @@ var savedMenu = { Bangle.loadWidgets(); setUI(); gfx(); +} diff --git a/apps/spotrem/metadata.json b/apps/spotrem/metadata.json index a0261ba13..5818d9aee 100644 --- a/apps/spotrem/metadata.json +++ b/apps/spotrem/metadata.json @@ -1,7 +1,7 @@ { "id": "spotrem", "name": "Remote for Spotify", - "version": "0.05", + "version": "0.07", "description": "Control spotify on your android device.", "readme": "README.md", "type": "app", diff --git a/apps/sunclock/ChangeLog b/apps/sunclock/ChangeLog new file mode 100644 index 000000000..d63f1567e --- /dev/null +++ b/apps/sunclock/ChangeLog @@ -0,0 +1,2 @@ +0.01: First commit +0.02: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/sunclock/app.js b/apps/sunclock/app.js index 4609565a2..1685bc218 100644 --- a/apps/sunclock/app.js +++ b/apps/sunclock/app.js @@ -9,8 +9,8 @@ SunCalc = require("suncalc.js"); loc = require('locale'); const LOCATION_FILE = "mylocation.json"; const xyCenter = g.getWidth() / 2 + 3; -const yposTime = 60; -const yposDate = 100; +const yposTime = 60; +const yposDate = 100; const yposRS = 135; const yposPos = 160; var rise = "07:00"; @@ -19,13 +19,21 @@ var pos = {altitude: 20, azimuth: 135}; var noonpos = {altitude: 37, azimuth: 180}; let idTimeout = null; + + function updatePos() { + function radToDeg(pos) { + return { // instead of mofidying suncalc + azimuth: Math.round((pos.azimuth / rad + 180) % 360), + altitude: Math.round( pos.altitude / rad) + }; + } coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":53.3,"lon":10.1,"location":"Pattensen"}; - pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon); + pos = radToDeg(SunCalc.getPosition(Date.now(), coord.lat, coord.lon)); times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon); rise = times.sunrise.toString().split(" ")[4].substr(0,5); set = times.sunset.toString().split(" ")[4].substr(0,5); - noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon); + noonpos = radToDeg(SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon)); } function drawSimpleClock() { @@ -38,7 +46,7 @@ function drawSimpleClock() { var time = da[4].substr(0, 5); // draw time - g.setFont("Vector",60); + g.setFont("Vector",60); g.drawString(time, xyCenter, yposTime, true); var date = [loc.dow(new Date(),1), loc.date(d,1)].join(" "); // draw day of week, date @@ -51,7 +59,7 @@ function drawSimpleClock() { g.setFont("Vector",21); g.drawString(`H${pos.altitude}/${noonpos.altitude} Az${pos.azimuth}`, xyCenter, yposPos, true); // draw sun pos - + let t = d.getSeconds()*1000 + d.getMilliseconds(); idTimeout = setTimeout(drawSimpleClock, 60000 - t); // time till next minute } diff --git a/apps/sunclock/metadata.json b/apps/sunclock/metadata.json index 617d76821..a9155f6f6 100644 --- a/apps/sunclock/metadata.json +++ b/apps/sunclock/metadata.json @@ -1,7 +1,7 @@ { "id": "sunclock", "name": "Sun Clock", - "version": "0.01", + "version": "0.02", "description": "A clock with sunset/sunrise, sun height/azimuth", "icon": "app.png", "type": "clock", @@ -11,7 +11,6 @@ "allow_emulator": true, "storage": [ {"name":"sunclock.app.js","url":"app.js"}, - {"name":"sunclock.img","url":"app-icon.js","evaluate":true}, - {"name":"suncalc.js","url":"suncalc.js"} + {"name":"sunclock.img","url":"app-icon.js","evaluate":true} ] } diff --git a/apps/swscroll/ChangeLog b/apps/swscroll/ChangeLog index c650baf72..c5fc9dcb4 100644 --- a/apps/swscroll/ChangeLog +++ b/apps/swscroll/ChangeLog @@ -1 +1,2 @@ 0.01: Inital release. +0.02: Rebasing on latest changes to showScroller_Q3 (https://github.com/espruino/Espruino/commit/2d3c34ef7c2b9fe2118e816aacd2e096adb99596). diff --git a/apps/swscroll/boot.js b/apps/swscroll/boot.js index fc5650cad..2b1b00de3 100644 --- a/apps/swscroll/boot.js +++ b/apps/swscroll/boot.js @@ -1,13 +1,14 @@ -E.showScroller = (function(options) { +E.showScroller = (function(options) { /* options = { h = height c = # of items scroll = initial scroll position scrollMin = minimum scroll amount (can be negative) draw = function(idx, rect) - select = function(idx) + remove = function() + select = function(idx, touch) } - + returns { draw = draw all drawItem(idx) = draw specific item @@ -15,46 +16,13 @@ E.showScroller = (function(options) { */ if (!options) return Bangle.setUI(); // remove existing handlers -var menuShowing = false; -var R = Bangle.appRect; -var Y = Bangle.appRect.y; -var n = Math.ceil(R.h/options.h); -var menuScrollMin = 0|options.scrollMin; -var menuScrollMax = options.h*options.c - R.h; -if (menuScrollMax { - g.reset().clearRect(R.x,R.y,R.x2,R.y2); - g.setClipRect(R.x,R.y,R.x2,R.y2); - var a = YtoIdx(R.y); - var b = Math.min(YtoIdx(R.y2),options.c-1); - for (var i=a;i<=b;i++) - options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h}); - g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); -}, drawItem : i => { - var y = idxToY(i); - g.reset().setClipRect(R.x,y,R.x2,y+options.h); - options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); - g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); -}}; -var rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither) -s.draw(); // draw the full scroller -g.flip(); // force an update now to make this snappier Bangle.setUI({ mode : "custom", back : options.back, - swipe : (hor,ver)=>{ + remove : options.remove, + swipe : (_,UD)=>{ pixels = 120; - var dy = ver*pixels; + var dy = UD*pixels; if (s.scroll - dy > menuScrollMax) dy = s.scroll - menuScrollMax-8; // Makes it so the last 'page' has the same position as previous pages. This should be done dynamically (change the static 8 to be a variable) so the offset is correct even when no widget field or title field is present. if (s.scroll - dy < menuScrollMin) @@ -66,7 +34,7 @@ Bangle.setUI({ if (!dy || options.c<=3) return; //options.c<=3 should maybe be dynamic, so 3 would be replaced by a variable dependent on R=Bangle.appRect. It's here so we don't try to scroll if all entries fit in the app rectangle. g.reset().setClipRect(R.x,R.y,R.x2,R.y2); g.scroll(0,dy); - var d = ver*pixels; + var d = UD*pixels; if (d < 0) { g.setClipRect(R.x,R.y2-(1-d),R.x2,R.y2); let i = YtoIdx(R.y2-(1-d)); @@ -92,9 +60,47 @@ Bangle.setUI({ }, touch : (_,e)=>{ if (e.y=0) && i=0) && i { + g.reset().clearRect(R.x,R.y,R.x2,R.y2); + g.setClipRect(R.x,R.y,R.x2,R.y2); + var a = YtoIdx(R.y); + var b = Math.min(YtoIdx(R.y2),options.c-1); + for (var i=a;i<=b;i++) + options.draw(i, {x:R.x,y:idxToY(i),w:R.w,h:options.h}); + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); +}, drawItem : i => { + var y = idxToY(i); + g.reset().setClipRect(R.x,y,R.x2,y+options.h); + options.draw(i, {x:R.x,y:y,w:R.w,h:options.h}); + g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); +}}; +var rScroll = s.scroll&~1; // rendered menu scroll (we only shift by 2 because of dither) +s.draw(); // draw the full scroller +g.flip(); // force an update now to make this snappier return s; -}); +}) diff --git a/apps/swscroll/metadata.json b/apps/swscroll/metadata.json index cb345054e..4edbfa2ba 100644 --- a/apps/swscroll/metadata.json +++ b/apps/swscroll/metadata.json @@ -1,7 +1,7 @@ { "id": "swscroll", "name": "Swipe menus", - "version": "0.01", + "version": "0.02", "description": "Replace built in E.showScroller to act on swipe instead of drag. Navigate menus in discrete steps instead of a continuous motion.", "readme": "README.md", "icon": "app.png", diff --git a/apps/timerclk/ChangeLog b/apps/timerclk/ChangeLog index 7a357b1aa..5a954d58c 100644 --- a/apps/timerclk/ChangeLog +++ b/apps/timerclk/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Add sunrise/sunset. Fix timer bugs. 0.03: Use default Bangle formatter for booleans +0.04: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps diff --git a/apps/timerclk/app.js b/apps/timerclk/app.js index c750fcfde..ee30b059a 100644 --- a/apps/timerclk/app.js +++ b/apps/timerclk/app.js @@ -3,7 +3,7 @@ Graphics.prototype.setFontAnton = function(scale) { g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78+(scale<<8)+(1<<16)); }; -var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); +var SunCalc = require("suncalc"); // from modules folder const LOCATION_FILE = "mylocation.json"; let location; var sunRise = "--:--"; @@ -72,7 +72,7 @@ function drawSpecial() { g.setFontAlign(0,0).setFont(settings.specialFont, settings.specialFontSize); var y = Bangle.appRect.y + g.stringMetrics("00:00").height/2; g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y+g.stringMetrics("00:00").height); - + if (stopwatches.length) { time = timerclk.getTime(stopwatches[stopwatch]); g.drawString(timerclk.formatTime(time, true), x, y); @@ -111,7 +111,7 @@ function draw() { var dateStr = require("locale").date(date,settings.shortDate).toUpperCase(); var dowStr = require("locale").dow(date).toUpperCase(); var srssStr = sunRise + sunIcons + sunSet; - + // draw time if (settings.timeFont == "Anton") { g.setFontAlign(0,0).setFont("Anton"); diff --git a/apps/timerclk/metadata.json b/apps/timerclk/metadata.json index 899eda59d..5bd6bee24 100644 --- a/apps/timerclk/metadata.json +++ b/apps/timerclk/metadata.json @@ -1,8 +1,8 @@ -{ +{ "id": "timerclk", "name": "Timer Clock", "shortName":"Timer Clock", - "version":"0.03", + "version":"0.04", "description": "A clock with stopwatches, timers and alarms build in.", "icon": "app-icon.png", "type": "clock", diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index aefb903b9..f1d001c81 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -17,3 +17,5 @@ 0.18: Added hasRange to clkinfo. 0.19: Added weather condition to clkinfo. 0.20: Added weather condition with temperature to clkinfo. +0.21: Updated clkinfo icon. +0.22: Automatic translation of strings, some left untranslated. diff --git a/apps/weather/app.js b/apps/weather/app.js index f63b226b9..8988c5002 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -16,10 +16,10 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ {type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"}, ]}, {filly: 1}, - {type: "txt", font: "6x8", pad: 2, halign: 1, label: "Humidity"}, + {type: "txt", font: "6x8", pad: 2, halign: 1, label: /*LANG*/"Humidity"}, {type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"}, {filly: 1}, - {type: "txt", font: "6x8", pad: 2, halign: -1, label: "Wind"}, + {type: "txt", font: "6x8", pad: 2, halign: -1, label: /*LANG*/"Wind"}, {type: "h", halign: -1, c: [ {type: "txt", font: "9%", pad: 2, id: "wind", label: "00"}, {type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"}, @@ -27,22 +27,22 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ ]}, ]}, {filly: 1}, - {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"}, + {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: /*LANG*/"Weather condition"}, {filly: 1}, {type: "h", c: [ {type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"}, {fillx: 1}, - {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"}, + {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: /*LANG*/"15 minutes ago"}, ]}, {filly: 1}, ]}, {lazy: true}); function formatDuration(millis) { let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); - if (millis < 60000) return "< 1 minute"; - if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); - if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); - return pluralize(Math.floor(millis/86400000), "day"); + if (millis < 60000) return /*LANG*/"< 1 minute"; + if (millis < 3600000) return pluralize(Math.floor(millis/60000), /*LANG*/"minute"); + if (millis < 86400000) return pluralize(Math.floor(millis/3600000), /*LANG*/"hour"); + return pluralize(Math.floor(millis/86400000), /*LANG*/"day"); } function draw() { @@ -57,7 +57,7 @@ function draw() { layout.windUnit.label = wind[2] + " " + (current.wrose||'').toUpperCase(); layout.cond.label = current.txt.charAt(0).toUpperCase()+(current.txt||'').slice(1); layout.loc.label = current.loc; - layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; + layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; // How to autotranslate this and similar? layout.update(); layout.render(); } @@ -77,9 +77,9 @@ function update() { } else { layout.forgetLazyState(); if (NRF.getSecurityStatus().connected) { - E.showMessage("Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?"); + E.showMessage(/*LANG*/"Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?"); } else { - E.showMessage("Weather\nunknown\n\nGadgetbridge\nnot connected"); + E.showMessage(/*LANG*/"Weather\nunknown\n\nGadgetbridge\nnot connected"); NRF.on("connect", update); } } diff --git a/apps/weather/clkinfo.js b/apps/weather/clkinfo.js index 339ff39c3..3cdd31c59 100644 --- a/apps/weather/clkinfo.js +++ b/apps/weather/clkinfo.js @@ -28,7 +28,7 @@ //FIXME ranges are somehow arbitrary var weatherItems = { name: "Weather", - img: atob("GBiBAf+///u5//n7//8f/9wHP8gDf/gB//AB/7AH/5AcP/AQH/DwD/uAD84AD/4AA/wAAfAAAfAAAfAAAfgAA/////+bP/+zf/+zfw=="), + img: atob("GBiBAABAAARGAAYEAADgACP4wDf8gAf+AA/+AE/4AG/jwA/v4A8P8AR/8DH/8AH//AP//g///g///g///gf//AAAAABkwABMgABMgA=="), items: [ { name: "conditionWithTemperature", diff --git a/apps/weather/metadata.json b/apps/weather/metadata.json index dd8b6c293..7fefb7685 100644 --- a/apps/weather/metadata.json +++ b/apps/weather/metadata.json @@ -1,7 +1,7 @@ { "id": "weather", "name": "Weather", - "version": "0.20", + "version": "0.22", "description": "Show Gadgetbridge weather report", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/widalarm/ChangeLog b/apps/widalarm/ChangeLog new file mode 100644 index 000000000..63568a9bd --- /dev/null +++ b/apps/widalarm/ChangeLog @@ -0,0 +1 @@ +0.01: Moved out of 'alarm' app diff --git a/apps/widalarm/app.png b/apps/widalarm/app.png new file mode 100644 index 000000000..a859dd2ef Binary files /dev/null and b/apps/widalarm/app.png differ diff --git a/apps/widalarm/metadata.json b/apps/widalarm/metadata.json new file mode 100644 index 000000000..b91457138 --- /dev/null +++ b/apps/widalarm/metadata.json @@ -0,0 +1,15 @@ +{ + "id": "widalarm", + "name": "Alarms Widget", + "version": "0.01", + "description": "Displays an alarm icon in the widgets bar if any alarm is active", + "icon": "app.png", + "type": "widget", + "tags": "tool,alarm,widget", + "supports": [ "BANGLEJS", "BANGLEJS2" ], + "provides_widgets" : ["alarm"], + "default" : true, + "storage": [ + { "name": "widalarm.wid.js", "url": "widget.js" } + ] +} diff --git a/apps/alarm/widget.js b/apps/widalarm/widget.js similarity index 100% rename from apps/alarm/widget.js rename to apps/widalarm/widget.js diff --git a/apps/widalarmeta/metadata.json b/apps/widalarmeta/metadata.json index b6d8bd62b..ef9f55ba8 100644 --- a/apps/widalarmeta/metadata.json +++ b/apps/widalarmeta/metadata.json @@ -8,6 +8,7 @@ "type": "widget", "tags": "widget", "supports": ["BANGLEJS","BANGLEJS2"], + "provides_widgets" : ["alarm"], "screenshots" : [ { "url":"screenshot.png" } ], "storage": [ {"name":"widalarmeta.wid.js","url":"widget.js"} diff --git a/apps/widbat/metadata.json b/apps/widbat/metadata.json index 0f040396f..993310eb2 100644 --- a/apps/widbat/metadata.json +++ b/apps/widbat/metadata.json @@ -6,6 +6,8 @@ "icon": "widget.png", "type": "widget", "tags": "widget,battery", + "provides_widgets" : ["battery"], + "default" : true, "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbat.wid.js","url":"widget.js"} diff --git a/apps/widbata/metadata.json b/apps/widbata/metadata.json index 26968a7d7..ddc901e80 100644 --- a/apps/widbata/metadata.json +++ b/apps/widbata/metadata.json @@ -10,6 +10,7 @@ "readme": "README.md", "description": "Shows the current battery level status in the top right using the clocks colour theme", "tags": "widget,battery", + "provides_widgets" : ["battery"], "storage": [ {"name":"widbata.wid.js","url":"widbata.wid.js"} ] diff --git a/apps/widbatpc/metadata.json b/apps/widbatpc/metadata.json index 7da4e3e0c..953f8d345 100644 --- a/apps/widbatpc/metadata.json +++ b/apps/widbatpc/metadata.json @@ -7,6 +7,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,battery", + "provides_widgets" : ["battery"], "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "screenshots": [{"url":"widbatpc.full.jpg"},{"url":"widbatpc.part.jpg"}], diff --git a/apps/widbatv/metadata.json b/apps/widbatv/metadata.json index 37cf6197b..d4ef28315 100644 --- a/apps/widbatv/metadata.json +++ b/apps/widbatv/metadata.json @@ -6,6 +6,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,battery", + "provides_widgets" : ["battery"], "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbatv.wid.js","url":"widget.js"} diff --git a/apps/widbt/metadata.json b/apps/widbt/metadata.json index e2d5082a5..1623db7a1 100644 --- a/apps/widbt/metadata.json +++ b/apps/widbt/metadata.json @@ -6,6 +6,8 @@ "icon": "widget.png", "type": "widget", "tags": "widget,bluetooth", + "provides_widgets" : ["bluetooth"], + "default" : true, "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbt.wid.js","url":"widget.js"} diff --git a/apps/widbt_notify/metadata.json b/apps/widbt_notify/metadata.json index 36905a340..5e3f15af2 100644 --- a/apps/widbt_notify/metadata.json +++ b/apps/widbt_notify/metadata.json @@ -6,6 +6,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,bluetooth", + "provides_widgets" : ["bluetooth"], "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbt_notify.wid.js","url":"widget.js"}, @@ -13,5 +14,5 @@ ], "data": [ {"name":"widbt_notify.json"} - ] + ] } diff --git a/apps/widbthide/metadata.json b/apps/widbthide/metadata.json index 59b13adb4..e3ac5cd54 100644 --- a/apps/widbthide/metadata.json +++ b/apps/widbthide/metadata.json @@ -6,6 +6,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,bluetooth", + "provides_widgets" : ["bluetooth"], "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbthide.wid.js","url":"widget.js"} diff --git a/apps/widmessages/ChangeLog b/apps/widmessages/ChangeLog index 3a41005e9..598068920 100644 --- a/apps/widmessages/ChangeLog +++ b/apps/widmessages/ChangeLog @@ -1 +1,3 @@ -0.01: Moved messages widget into standalone widget app \ No newline at end of file +0.01: Moved messages widget into standalone widget app +0.02: Fix 'srcs' being defined in global scope + Remove library stub diff --git a/apps/widmessages/lib.js b/apps/widmessages/lib.js deleted file mode 100644 index 897611ad1..000000000 --- a/apps/widmessages/lib.js +++ /dev/null @@ -1,8 +0,0 @@ -exports.hide = function() { - if (!global.WIDGETS||!WIDGETS["messages"]) return; - WIDGETS["messages"].hide(); -} -exports.show = function() { - if (!global.WIDGETS||!WIDGETS["messages"]) return; - WIDGETS["messages"].show(); -} \ No newline at end of file diff --git a/apps/widmessages/metadata.json b/apps/widmessages/metadata.json index 0c3ac7e05..a8a23df19 100644 --- a/apps/widmessages/metadata.json +++ b/apps/widmessages/metadata.json @@ -1,7 +1,7 @@ { "id": "widmessages", "name": "Message Widget", - "version": "0.01", + "version": "0.02", "description": "Widget showing new messages", "icon": "app.png", "type": "widget", @@ -9,10 +9,10 @@ "supports": ["BANGLEJS","BANGLEJS2"], "screenshots": [{"url": "screenshot.gif"}], "dependencies" : { "messageicons":"module" }, - "provides_modules" : ["messagewidget"], + "provides_widgets" : ["message"], + "default" : true, "readme": "README.md", "storage": [ - {"name":"messagewidget","url":"lib.js"}, {"name":"widmessages.wid.js","url":"widget.js"} ] } diff --git a/apps/widmessages/widget.js b/apps/widmessages/widget.js index 2ee11b690..316957e29 100644 --- a/apps/widmessages/widget.js +++ b/apps/widmessages/widget.js @@ -7,6 +7,9 @@ .filter((msg, i, arr) => arr.findIndex(nmsg => msg.src == nmsg.src) == i); } + // NOTE when adding a custom "essages" widget: + // the name still needs to be "messages": the library calls WIDGETS["messages'].hide()/show() + // see e.g. widmsggrid WIDGETS["messages"] = { area: "tl", width: 0, srcs: [], draw: function(recall) { // If we had a setTimeout queued from the last time we were called, remove it @@ -18,7 +21,7 @@ if (!this.width) return; let settings = Object.assign({flash: true, maxMessages: 3}, require("Storage").readJSON("messages.settings.json", true) || {}); if (recall!==true || settings.flash) { - const msgsShown = E.clip(this.srcs.length, 0, settings.maxMessages), + const msgsShown = E.clip(this.srcs.length, 0, settings.maxMessages); srcs = Object.keys(this.srcs); g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23); for(let i = 0; iw.x+w.width || c.yw.y+24) return; require("messages").openGUI(); - }, hide() { + }, + // hide() and show() are required by the "message" library! + hide() { this.hidden=true; if (this.width) { // hide widget @@ -68,6 +73,5 @@ }; Bangle.on("message", WIDGETS["messages"].onMsg); - this.srcs = {}; WIDGETS["messages"].onMsg("init", {}); // abuse type="init" to prevent Bangle.drawWidgets(); })(); diff --git a/apps/widmsggrid/ChangeLog b/apps/widmsggrid/ChangeLog index 9612da729..9be40817a 100644 --- a/apps/widmsggrid/ChangeLog +++ b/apps/widmsggrid/ChangeLog @@ -1,3 +1,4 @@ 0.01: New widget! 0.02: Adjust to message icons moving to messageicons lib -0.03: Use new message library \ No newline at end of file +0.03: Use new message library +0.04: Remove library stub \ No newline at end of file diff --git a/apps/widmsggrid/README.md b/apps/widmsggrid/README.md index 274858d66..36aad20e2 100644 --- a/apps/widmsggrid/README.md +++ b/apps/widmsggrid/README.md @@ -12,12 +12,7 @@ Example: one SMS, one Signal, and two WhatsApp messages: ![screenshot](screenshot.png) ## Installation -This widget needs the [`messages`](/?id=messages) app to handle notifications. - -You probably want to disable the default widget, to do so: -1. Open `Settings` -2. Navigate to `Apps`>`Messages` -3. Scroll down to the `Widget messages` entry, and change it to `Hide` +There can only be one messages widget, so you should uninstall the default "Message Widget". ## Settings You can change settings by going to the global `Settings` app, then `App Settings` diff --git a/apps/widmsggrid/lib.js b/apps/widmsggrid/lib.js deleted file mode 100644 index 430577209..000000000 --- a/apps/widmsggrid/lib.js +++ /dev/null @@ -1,8 +0,0 @@ -exports.hide = function() { - if (!global.WIDGETS||!WIDGETS["msggrid"]) return; - WIDGETS["msggrid"].hide(); -} -exports.show = function() { - if (!global.WIDGETS||!WIDGETS["msggrid"]) return; - WIDGETS["msggrid"].show(); -} \ No newline at end of file diff --git a/apps/widmsggrid/metadata.json b/apps/widmsggrid/metadata.json index 68c2c3771..17d3573ad 100644 --- a/apps/widmsggrid/metadata.json +++ b/apps/widmsggrid/metadata.json @@ -1,17 +1,16 @@ { "id": "widmsggrid", "name": "Messages Grid Widget", - "version": "0.03", + "version": "0.04", "description": "Widget that displays notification icons in a grid", "icon": "widget.png", "type": "widget", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], "dependencies" : { "messages":"module" }, - "provides_modules" : ["messagewidget"], + "provides_widgets" : ["message"], "readme": "README.md", "storage": [ - {"name":"messagewidget","url":"lib.js"}, {"name":"widmsggrid.wid.js","url":"widget.js"} ], "screenshots": [{"url":"screenshot.png"}] diff --git a/apps/widmsggrid/widget.js b/apps/widmsggrid/widget.js index 786f590b5..6a5b175ac 100644 --- a/apps/widmsggrid/widget.js +++ b/apps/widmsggrid/widget.js @@ -6,7 +6,8 @@ showRead: !!settings.showRead, }; delete settings; - WIDGETS["msggrid"] = { + // widget name needs to be "messages": the library calls WIDGETS["messages'].hide()/show() + WIDGETS["messages"] = { area: "tl", width: 0, flash: s.flash, showRead: s.showRead, @@ -57,7 +58,9 @@ .drawString(w.total, w.x + w.width - 1, w.y + 24, w.total > 9); } if (w.flash && w.status === "new") w.t = setTimeout(w.draw, 1000); // schedule redraw while blinking - }, show: function (m) { + }, + // show() and hide() are required by the "message" library! + show: function (m) { delete w.hidden; w.width = 24; w.srcs = require("messages").getMessages(m) @@ -94,6 +97,6 @@ } }; delete s; - const w = WIDGETS["msggrid"]; + const w = WIDGETS["messages"]; Bangle.on("message", w.listener); })(); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js old mode 100755 new mode 100644 index 0a4765d9c..6537f4389 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -76,7 +76,7 @@ const APP_KEYS = [ 'id', 'name', 'shortName', 'version', 'icon', 'screenshots', 'description', 'tags', 'type', 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'supports', 'allow_emulator', - 'dependencies', 'provides_modules' + 'dependencies', 'provides_modules', 'provides_widgets', "default" ]; const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports', 'noOverwrite']; const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate']; @@ -93,7 +93,7 @@ const INTERNAL_FILES_IN_APP_TYPE = { // list of app types and files they SHOULD /* These are warnings we know about but don't want in our output */ var KNOWN_WARNINGS = [ "App gpsrec data file wildcard .gpsrc? does not include app ID", -"App widmessages storage file messagewidget is also listed as storage file for app widmsggrid", +"App owmweather data file weather.json is also listed as data file for app weather", ]; function globToRegex(pattern) { @@ -168,8 +168,8 @@ apps.forEach((app,appIdx) => { if (app.dependencies) { if (("object"==typeof app.dependencies) && !Array.isArray(app.dependencies)) { Object.keys(app.dependencies).forEach(dependency => { - if (!["type","app","module"].includes(app.dependencies[dependency])) - ERROR(`App ${app.id} 'dependencies' must all be tagged 'type/app/module' right now`, {file:metadataFile}); + if (!["type","app","module","widget"].includes(app.dependencies[dependency])) + ERROR(`App ${app.id} 'dependencies' must all be tagged 'type/app/module/widget' right now`, {file:metadataFile}); if (app.dependencies[dependency]=="type" && !METADATA_TYPES.includes(dependency)) ERROR(`App ${app.id} 'type' dependency must be one of `+METADATA_TYPES, {file:metadataFile}); }); diff --git a/core b/core index 3a953179b..2a89ea64f 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 3a953179b7bb9f574d4e77d5f34b6b7deee1e884 +Subproject commit 2a89ea64f7874b9264572f68836fe8ecd0a6b191 diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json index 04bd44504..f96f81f60 100644 --- a/defaultapps_banglejs2.json +++ b/defaultapps_banglejs2.json @@ -1 +1 @@ -["boot","launch","antonclk","health","setting","about","widbat","widbt","widlock","widid"] +["boot","launch","antonclk","health","setting","about","alarm","widbat","widbt","widlock","widid"] diff --git a/lang/de_DE.json b/lang/de_DE.json index ef22b588f..84f29a6c2 100644 --- a/lang/de_DE.json +++ b/lang/de_DE.json @@ -20,7 +20,7 @@ "On": "Ein", "Off": "Aus", "Ok": "OK", - "New Timer": "Neuer Kurzzeitwecker", + "New Timer": "Neuer Timer", "(repeat)": "(Wiederholung)", "music": "Musik", "Keep Msgs": "Nachrichten behalten", @@ -148,7 +148,7 @@ "Whitelist": "Whitelist", "Select Clock": "Uhr auswählen", "Disable": "Deaktivieren", - "Timer": "Kurzzeitwecker", + "Timer": "Timer", "Error in settings": "Fehler in den Einstellungen", "Set Time": "Zeit einstellen", "ALARM": "ALARM", diff --git a/lang/sv_SE.json b/lang/sv_SE.json index 6e0f30fa4..2c2ff5acf 100644 --- a/lang/sv_SE.json +++ b/lang/sv_SE.json @@ -6,34 +6,34 @@ "Hours": "Timmar", "Minutes": "Minuter", "Enabled": "Aktiverad", - "New Alarm": "Ny alarm", + "New Alarm": "Nytt larm", "Save": "Spara", "Back": "Tillbaka", "Repeat": "Upprepning", "Delete": "Radera", - "ALARM!": "ALURH!", + "ALARM!": "LARM!", "Sleep": "Sömn", "circle 3": "cirkel 3", "circle 1": "cirkel 1", "music": "musik", "week": "vecka", "Keep Msgs": "Behåll meddelanden", - "Auto snooze": "Automatisk snooze", + "Auto snooze": "Auto-snooza", "step length": "steglängd", "Circle": "Cirkel", "data": "uppgifter", - "colorize icon": "färglägga ikonen", - "min. confidence": "Min. förtroende", - "show widgets": "visa widgets", + "colorize icon": "färglägg ikon", + "min. confidence": "min. konfidens", + "show widgets": "visa widgetar", "valid period": "giltig period", "Heartrate": "Hjärtfrekvens", - "distance goal": "mål för distans", + "distance goal": "distansmål", "circle 4": "cirkel 4", "circle count": "antal cirklar", "minimum": "minimum", "maximum": "maximal", "New Timer": "Ny timer", - "battery warn": "batteri varning", + "battery warn": "batterivarning", "heartrate": "hjärtfrekvens", "circle 2": "cirkel 2", "(repeat)": "(upprepning)", @@ -42,30 +42,30 @@ "No Messages": "Inga meddelanden", "Show clocks": "Visa klockor", "STEPS": "STEG", - "TAP right top/bottom": "TAP höger upp/ner", + "TAP right top/bottom": "KNACKA höger upp/ner", "View Message": "Visa meddelande", "Mark Unread": "Markera oläst", - "Are you sure": "Är du säker på att", + "Are you sure": "Är du säker", "Delete all messages": "Radera alla meddelanden", - "Record Run": "Rekordkörning", + "Record Run": "Spåra löprunda", "Unread timer": "Oläst timer", "Vibration": "Vibrationer", - "Utils": "Användaruppgifter", + "Utils": "Verktyg", "Quiet Mode": "Tyst läge", "Passkey BETA": "Passkey BETA", "Dark BW": "Mörk BW", - "BTNs 1:startlap 2:exit 3:reset": "BTN 1:startlap 2:exit 3:reset", - "start&lap/reset, BTN1: EXIT": "start&lap/återställning, BTN1: EXIT", + "BTNs 1:startlap 2:exit 3:reset": "BTN 1:starta varv 2:lämna 3:återställ", + "start&lap/reset, BTN1: EXIT": "starta&varv/återställ, BTN1: LÄMNA", "BLE": "BLE", "Programmable": "Programmerbar", - "Launcher Settings": "Inställningar för lanseringen", - "Vector font size": "Vektor teckensnittsstorlek", + "Launcher Settings": "Inställningar för launchern", + "Vector font size": "Storlek vektortypsnitt", "Font": "Typsnitt", "Yes\ndefinitely": "Ja\ndefinitivt", "App Source\nNot found": "App-källa\nEj funnen", - "Make Connectable": "Gör det möjligt att ansluta", + "Make Connectable": "Gör anslutningsbar", "HID": "HID", - "Bluetooth": "Bluetooth", + "Bluetooth": "Blåtand", "Apps": "Appar", "Piezo": "Piezo", "LCD": "LCD", @@ -73,46 +73,46 @@ "Light BW": "Ljus BW", "Background": "Bakgrund", "Remove": "Ta bort", - "Highlight BG": "Markera BG", + "Highlight BG": "Markering BG", "Customize": "Anpassa", - "Highlight FG": "Highlight FG", + "Highlight FG": "Markering FG", "Background 2": "Bakgrund 2", "LCD Brightness": "Ljusstyrka på LCD-skärmen", "Add Device": "Lägg till enhet", - "Wake on BTN1": "Vakna på BTN1", - "Wake on BTN2": "Vakna på BTN2", - "Twist Timeout": "Twist Timeout", + "Wake on BTN1": "Vakna av BTN1", + "Wake on BTN2": "Vakna av BTN2", + "Twist Timeout": "Vridning Timeout", "Wake on Touch": "Vakna vid beröring", "LCD Timeout": "LCD Timeout", "Foreground": "Förgrund", - "Connect device\nto add to\nwhitelist": "Anslut enhet\nför att lägga till\nvitlista", - "Wake on FaceUp": "Vakna på FaceUp", + "Connect device\nto add to\nwhitelist": "Anslut enhet\nför att lägga till\ni vitlistan", + "Wake on FaceUp": "Skärm upp väcker", "Twist Threshold": "Tröskelvärde för vridning", - "Wake on BTN3": "Wake på BTN3", + "Wake on BTN3": "Vakna av BTN3", "Clock Style": "Klockstil", "Time Zone": "Tidszon", "Twist Max Y": "Vridning Max Y", - "Stay Connectable": "Håll dig tillgänglig", - "This will remove everything": "Detta kommer att ta bort allt", + "Stay Connectable": "Håll anslutningsbar", + "This will remove everything": "Detta kommer ta bort allt", "Turn Off": "Stäng av", "Connectable": "Anslutningsbar", - "Flattening battery - this can take hours.\nLong-press button to cancel": "Platta batterier - detta kan ta flera timmar.\nTryck länge på knappen för att avbryta", - "Reset to Defaults": "Återställ till standardvärden", + "Flattening battery - this can take hours.\nLong-press button to cancel": "Töm batteri - detta kan ta flera timmar.\nTryck länge på knappen för att avbryta", + "Reset to Defaults": "Återställ standardvärden", "Utilities": "Verktyg", - "Flatten Battery": "Platta batterier", - "Debug Info": "Info om felsökning", - "Reset Settings": "Återställa inställningar", - "Wake on Twist": "Väckning på Twist", - "Compact Storage": "Kompakt förvaring", + "Flatten Battery": "Töm batteri", + "Debug Info": "Felsökningsinfo", + "Reset Settings": "Återställ inställningar", + "Wake on Twist": "Vakna av vridning", + "Compact Storage": "Komprimera lagring", "Log": "Logg", "Rewrite Settings": "Omskrivning av inställningar", - "Compacting...\nTakes approx\n1 minute": "Komprimering...\nTar ca.\n1 minut", + "Compacting...\nTakes approx\n1 minute": "Komprimerar...\nTar ca.\n1 minut", "Storage": "Lagring", "Second": "Andra", "App Settings": "App-inställningar", "Invalid settings": "Ogiltiga inställningar", - "Minute": "Protokoll", - "Sleep Phase Alarm": "Larm om sömnfas", + "Minute": "Minut", + "Sleep Phase Alarm": "Sömnfaslarm", "No app has settings": "Ingen app har inställningar", "Hour": "Timme", "No Clocks Found": "Inga klockor hittades", @@ -124,16 +124,16 @@ "TIMER": "TIMER", "on": "på", "OFF": "OFF", - "Side": "Sidan", + "Side": "Sida", "Sort Order": "Sortering", "Left": "Vänster", "Right": "Höger", "Reset All": "Återställ alla", - "Widgets": "Widgets", + "Widgets": "Widgetar", "goal": "mål", "Vibrate": "Vibrera", "Message": "Meddelande", - "Beep": "Piper", + "Beep": "Pip", "Disable": "Inaktivera", "Select Clock": "Välj klocka", "Locale": "Lokalisering", @@ -145,14 +145,14 @@ "Timer": "Timer", "BACK": "TILLBAKA", "Error in settings": "Fel i inställningarna", - "Whitelist": "Whitelist", - "ALARM": "ALARM", + "Whitelist": "Vitlista", + "ALARM": "LARM", "Hide": "Dölj", "Connected": "Ansluten", "Show": "Visa", "On": "På", "Ok": "Ok", - "No": "Ingen", + "No": "Nej", "Settings": "Inställningar", "steps": "steg", "back": "tillbaka", @@ -162,7 +162,7 @@ "Loading": "Laddar", "Music": "Musik", "color": "färg", - "off": "off", + "off": "av", "Off": "Av", "Theme": "Tema", "one": "ett", @@ -197,4 +197,4 @@ "ten to *$2": "tio i *$2", "five to *$2": "fem i *$2" } -} \ No newline at end of file +} diff --git a/modules/README.md b/modules/README.md index 3f8e90b06..fcb403bd5 100644 --- a/modules/README.md +++ b/modules/README.md @@ -18,28 +18,6 @@ so you may see the error "Module not found" in the IDE when sendin To fix this you have three options: -### Host your own App Loader and upload from that - -This is reasonably easy to set up, but it's more difficult to make changes and upload: - -* Follow the steps here to set up your own App Loader: https://www.espruino.com/Bangle.js+App+Loader -* Make changes to that repository -* Refresh and upload your app from the app loader (you can have the IDE connected - at the same time so you can see any error messages) - -### Upload the module to the Bangle's internal storage - -This allows you to develop both the app and module very quickly, but the app is -uploaded in a slightly different way to what you'd get when you use the App Loader -or the method below: - -* Load the module's source file in the Web IDE -* Click the down-arrow below the upload button, then `Storage` -* Click `New File`, type `your_module_name` as the name (with no `.js` extension), click `Ok` -* Now Click the `Upload` icon. - -You can now upload the app direct from the IDE. You can even leave a second Web IDE window open -(one for the app, one for the module) to allow you to change the module. ### Change the Web IDE search path to include Bangle.js modules @@ -56,3 +34,30 @@ The next time you upload your app, the module will automatically be included. **Note:** You can optionally use `https://raw.githubusercontent.com/espruino/BangleApps/master/modules|https://www.espruino.com/modules` as the module URL to pull in modules direct from the development app loader (which could be slightly newer than the ones on https://banglejs.com/apps) + + +### Host your own App Loader and upload from that + +This is reasonably easy to set up, but it's more difficult to make changes and upload: + +* Follow the steps here to set up your own App Loader: https://www.espruino.com/Bangle.js+App+Loader +* Make changes to that repository +* Refresh and upload your app from the app loader (you can have the IDE connected + at the same time so you can see any error messages) + + +### Upload the module to the Bangle's internal storage + +This allows you to develop both the app and module very quickly, but the app is +uploaded in a slightly different way to what you'd get when you use the App Loader +or the method below: + +* Load the module's source file in the Web IDE +* Click the down-arrow below the upload button, then `Storage` +* Click `New File`, type `your_module_name` as the name (with no `.js` extension), click `Ok` +* Now Click the `Upload` icon. + +You can now upload the app direct from the IDE. You can even leave a second Web IDE window open +(one for the app, one for the module) to allow you to change the module. + + diff --git a/modules/clock_info.js b/modules/clock_info.js index 238888b1c..50968311e 100644 --- a/modules/clock_info.js +++ b/modules/clock_info.js @@ -77,7 +77,7 @@ exports.load = function() { // actual menu var menu = [{ name: "Bangle", - img: atob("GBiBAf8B//4B//4B//4B//4A//x4//n+f/P/P+fPn+fPn+fP3+/Px+/Px+fn3+fzn+f/n/P/P/n+f/x4//4A//4B//4B//4B//8B/w=="), + img: atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA=="), items: [ { name : "Battery", hasRange : true, diff --git a/apps/sunclock/suncalc.js b/modules/suncalc.js similarity index 76% rename from apps/sunclock/suncalc.js rename to modules/suncalc.js index b1af0a0d9..0c22c6cb2 100644 --- a/apps/sunclock/suncalc.js +++ b/modules/suncalc.js @@ -1,17 +1,34 @@ -/* Module suncalc.js +/* (c) 2011-2015, Vladimir Agafonkin SunCalc is a JavaScript library for calculating sun/moon position and light phases. https://github.com/mourner/suncalc -PB: Usage: -E.setTimeZone(2); // 1 = MEZ, 2 = MESZ -SunCalc = require("suncalc.js"); -pos = SunCalc.getPosition(Date.now(), 53.3, 10.1); -times = SunCalc.getTimes(Date.now(), 53.3, 10.1); -rise = times.sunrise; // Date object -rise_str = rise.getHours() + ':' + rise.getMinutes(); //hh:mm +Copyright (c) 2014, Vladimir Agafonkin +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ -var exports={}; + +(function () { 'use strict'; // shortcuts for easier to read formulas @@ -26,6 +43,7 @@ var PI = Math.PI, // sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas + // date/time constants and conversions var dayMs = 1000 * 60 * 60 * 24, @@ -33,7 +51,7 @@ var dayMs = 1000 * 60 * 60 * 24, J2000 = 2451545; function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; } -function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } // PB: onece removed + 0.5; included it again 4 Jan 2021 +function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } function toDays(date) { return toJulian(date) - J2000; } @@ -81,9 +99,13 @@ function sunCoords(d) { }; } + +var SunCalc = {}; + + // calculates sun position for a given date and latitude/longitude -exports.getPosition = function (date, lat, lng) { +SunCalc.getPosition = function (date, lat, lng) { var lw = rad * -lng, phi = rad * lat, @@ -93,19 +115,32 @@ exports.getPosition = function (date, lat, lng) { H = siderealTime(d, lw) - c.ra; return { - azimuth: Math.round((azimuth(H, phi, c.dec) / rad + 180) % 360), // PB: converted to deg - altitude: Math.round( altitude(H, phi, c.dec) / rad) // PB: converted to deg + azimuth: azimuth(H, phi, c.dec), + altitude: altitude(H, phi, c.dec) }; }; // sun times configuration (angle, morning name, evening name) -var times = [ - [-0.833, 'sunrise', 'sunset' ] +var times = SunCalc.times = [ + [-0.833, 'sunrise', 'sunset' ], + [ -0.3, 'sunriseEnd', 'sunsetStart' ], + [ -6, 'dawn', 'dusk' ], + [ -12, 'nauticalDawn', 'nauticalDusk'], + [ -18, 'nightEnd', 'night' ], + [ 6, 'goldenHourEnd', 'goldenHour' ] ]; +// adds a custom time to the times config + +SunCalc.addTime = function (angle, riseName, setName) { + times.push([angle, riseName, setName]); +}; + + // calculations for sun times + var J0 = 0.0009; function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); } @@ -128,7 +163,7 @@ function getSetJ(h, lw, phi, dec, n, M, L) { // calculates sun times for a given date, latitude/longitude, and, optionally, // the observer height (in meters) relative to the horizon -exports.getTimes = function (date, lat, lng, height) { +SunCalc.getTimes = function (date, lat, lng, height) { height = height || 0; @@ -189,7 +224,7 @@ function moonCoords(d) { // geocentric ecliptic coordinates of the moon }; } -getMoonPosition = function (date, lat, lng) { +SunCalc.getMoonPosition = function (date, lat, lng) { var lw = rad * -lng, phi = rad * lat, @@ -216,7 +251,7 @@ getMoonPosition = function (date, lat, lng) { // based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and // Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. -getMoonIllumination = function (date) { +SunCalc.getMoonIllumination = function (date) { var d = toDays(date || new Date()), s = sunCoords(d), @@ -243,7 +278,7 @@ function hoursLater(date, h) { // calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article -getMoonTimes = function (date, lat, lng, inUTC) { +SunCalc.getMoonTimes = function (date, lat, lng, inUTC) { var t = new Date(date); if (inUTC) t.setUTCHours(0, 0, 0, 0); else t.setHours(0, 0, 0, 0); @@ -295,4 +330,12 @@ getMoonTimes = function (date, lat, lng, inUTC) { if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true; return result; -}; \ No newline at end of file +}; + + +// export as Node module / AMD module / browser variable +if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc; +else if (typeof define === 'function' && define.amd) define(SunCalc); +else window.SunCalc = SunCalc; + +}()); diff --git a/modules/widget_utils.js b/modules/widget_utils.js index 3440a01d2..33fd303f9 100644 --- a/modules/widget_utils.js +++ b/modules/widget_utils.js @@ -29,6 +29,7 @@ exports.show = function() { /// Remove any intervals/handlers/etc that we might have added. Does NOT re-show widgets that were hidden exports.cleanup = function() { + delete exports.autohide; delete Bangle.appRect; if (exports.swipeHandler) { Bangle.removeListener("swipe", exports.swipeHandler); @@ -50,11 +51,13 @@ exports.cleanup = function() { /** Put widgets offscreen, and allow them to be swiped back onscreen with a downwards swipe. Use .show to undo. +First parameter controls automatic hiding time, 0 equals not hiding at all. +Default value is 2000ms until hiding. Bangle.js 2 only at the moment. */ -exports.swipeOn = function() { +exports.swipeOn = function(autohide) { exports.cleanup(); if (!global.WIDGETS) return; - + exports.autohide=autohide===undefined?2000:autohide; /* TODO: maybe when widgets are offscreen we don't even store them in an offscreen buffer? */ @@ -125,11 +128,13 @@ exports.swipeOn = function() { clearTimeout(exports.hideTimeout); delete exports.hideTimeout; } - if (ud>0 && offset<0) anim(4, function() { + let cb; + if (exports.autohide > 0) cb = function() { exports.hideTimeout = setTimeout(function() { anim(-4); - }, 2000); - }); + }, exports.autohide); + } + if (ud>0 && offset<0) anim(4, cb); if (ud<0 && offset>-24) anim(-4); };