diff --git a/apps.json b/apps.json index 783884523..2c3c77985 100644 --- a/apps.json +++ b/apps.json @@ -384,7 +384,7 @@ { "id": "weather", "name": "Weather", "icon": "icon.png", - "version":"0.01", + "version":"0.02", "description": "Show Gadgetbridge weather report", "readme": "readme.md", "tags": "widget,outdoors", @@ -392,7 +392,8 @@ {"name":"weather.app.js","url":"app.js"}, {"name":"weather.wid.js","url":"widget.js"}, {"name":"weather","url":"lib.js"}, - {"name":"weather.img","url":"icon.js","evaluate":true} + {"name":"weather.img","url":"icon.js","evaluate":true}, + {"name":"weather.settings.js","url":"settings.js"} ], "data": [ {"name": "weather.json"} @@ -887,7 +888,7 @@ { "id": "berlinc", "name": "Berlin Clock", "icon": "berlin-clock.png", - "version":"0.02", + "version":"0.03", "description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)", "tags": "clock", "type":"clock", @@ -1726,7 +1727,7 @@ { "id": "rndmclk", "name": "Random Clock Loader", "icon": "rndmclk.png", - "version":"0.02", + "version":"0.03", "description": "Load a different clock whenever the LCD is switched on.", "readme": "README.md", "tags": "widget,clock", @@ -1773,5 +1774,19 @@ "storage": [ {"name":"widviz.wid.js","url":"widget.js"} ] + }, + { + "id": "pizzatimer", + "name": "Pizza Timer", + "shortName":"Pizza Timer", + "icon": "pizza.png", + "version":"0.01", + "description": "A timer app for when you cook Pizza. Some say it can also time other things", + "tags": "timer,tool,pizza", + "readme": "README.md", + "storage": [ + {"name":"pizzatimer.app.js","url":"app.js"}, + {"name":"pizzatimer.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/berlinc/ChangeLog b/apps/berlinc/ChangeLog index 7819dbe2a..a33332bc4 100644 --- a/apps/berlinc/ChangeLog +++ b/apps/berlinc/ChangeLog @@ -1 +1,2 @@ 0.02: Modified for use with new bootloader and firmware +0.03: Shrinked size to avoid cut-off edges on the physical device. BTN3: show date. BTN1: show time in decimal. diff --git a/apps/berlinc/README.md b/apps/berlinc/README.md new file mode 100644 index 000000000..1f86ad73e --- /dev/null +++ b/apps/berlinc/README.md @@ -0,0 +1,10 @@ +# Berlin Clock Watch Face + +This is a clock-face analogous to the [Berlin Clock](https://en.wikipedia.org/wiki/Mengenlehreuhr). + +## Usage + +* BTN1: toggle displaying the time in decimal figures (24 hour format) in the minutes fields. The first two fields are used for the hour and the last two fields for the minute. This might be a help when you're still familarizig yourself with this new way to express the time. +* BTN2: start the launcher +* BTN3: toggle displaying the current date (in ISO 8601 format) below the actual clock-face. + diff --git a/apps/berlinc/berlin-clock.js b/apps/berlinc/berlin-clock.js index 0c8e2d076..0398ffd2f 100644 --- a/apps/berlinc/berlin-clock.js +++ b/apps/berlinc/berlin-clock.js @@ -1,48 +1,86 @@ -// place your const, vars, functions or classes here -fields = [ 4 , 4 , 11 , 4 ]; -width = g.getWidth(); -height = g.getHeight(); -rowHeight = height/4; +// Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr +// https://github.com/eska-muc/BangleApps +const fields = [4, 4, 11, 4]; +const offset = 20; +const width = g.getWidth() - 2 * offset; +const height = g.getHeight() - 2 * offset; +const rowHeight = height / 4; + +var show_date = false; +var show_time = false; +var yy = 0; + rowlights = []; +time_digit = []; function drawBerlinClock() { - var now = new Date(); - rowlights[0] = Math.floor(now.getHours() / 5); - rowlights[1] = now.getHours() % 5; - rowlights[2] = Math.floor(now.getMinutes() / 5); - rowlights[3] = now.getMinutes() % 5; + g.clear(); + var now = new Date(); + + // show date below the clock + if (show_date) { + var yr = now.getFullYear(); + var month = now.getMonth() + 1; + var day = now.getDate(); + var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`; + var strWidth = g.stringWidth(dateString); + g.setColor(1, 1, 1); + g.setFontAlign(-1,-1); + g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4); + } + + rowlights[0] = Math.floor(now.getHours() / 5); + rowlights[1] = now.getHours() % 5; + rowlights[2] = Math.floor(now.getMinutes() / 5); + rowlights[3] = now.getMinutes() % 5; - g.clear(); + time_digit[0] = Math.floor(now.getHours() / 10); + time_digit[1] = now.getHours() % 10; + time_digit[2] = Math.floor(now.getMinutes() / 10); + time_digit[3] = now.getMinutes() % 10; - g.drawRect(0,0,width,height); - for (row = 0 ; row < 4 ; row++) { - nfields = fields[row]; - boxWidth = width/nfields; + g.drawRect(offset, offset, width + offset, height + offset); + for (row = 0; row < 4; row++) { + nfields = fields[row]; + boxWidth = width / nfields; - for (col = 0 ; col < nfields ; col++) { - x1 = col*boxWidth; - y1 = row*rowHeight; - x2 = (col+1)*boxWidth; - y2 = (row+1)*rowHeight; + for (col = 0; col < nfields; col++) { + x1 = col * boxWidth + offset; + y1 = row * rowHeight + offset; + x2 = (col + 1) * boxWidth + offset; + y2 = (row + 1) * rowHeight + offset; - g.setColor(1,1,1); - g.drawRect(x1,y1,x2,y2); - if (col new Promise(resolve => setTimeout(resolve, 200))) + .then(() => Bangle.buzz()); + setTimeout(alertCountdownFinished, 2000); +} + +function unsetDrawInterval() { + clearInterval(drawInterval); + drawInterval = undefined; +} + +function decrementCountdownTime() { + const allZero = countDownFinished(); + + if(allZero) { + return; + } + + if (countdownTime[SEC_INDEX] !== 0) { + countdownTime[SEC_INDEX] = countdownTime[SEC_INDEX] - 1; + return; + } + + countdownTime[SEC_INDEX] = 59; + + if (countdownTime[MIN_INDEX] !== 0) { + countdownTime[MIN_INDEX] = countdownTime[MIN_INDEX] - 1; + return; + } + + countdownTime[MIN_INDEX] = 59; + + if (countdownTime[HOUR_INDEX] !== 0) { + countdownTime[HOUR_INDEX] = countdownTime[HOUR_INDEX] - 1; + return; + } +} + +function toggleShow(timeIndex) { + show[timeIndex] = !show[timeIndex]; +} + +function twoPadded(i) { + return i.length < 2 ? "0" + i : i; +} + +function getTimeString(t) { + let hour = t[HOUR_INDEX].toString(); + let min = t[MIN_INDEX].toString(); + let sec = t[SEC_INDEX].toString(); + + hour = show[HOUR_INDEX] ? twoPadded(hour) : " "; + min = show[MIN_INDEX] ? twoPadded(min) : " "; + sec = show[SEC_INDEX] ? twoPadded(sec) : " "; + + return hour + ":" + min + ":" + sec; +} + +/* drawing */ + +/* + shamelessly stollen from Bluetooth Music Controls + https://github.com/espruino/BangleApps/blob/6b09377414e02d575b8335bb051c831ecc9da9d9/apps/hidmsic/hid-music.js#L42 +*/ +function drawArrows() { + function c(a) { + return { + width: 8, + height: a.length, + bpp: 1, + buffer: (new Uint8Array(a)).buffer + }; + } + const d = g.getWidth() - 18; + g.drawImage(c([16,56,124,254,16,16,16,16]),d,40); + g.drawImage(c([16,16,16,16,254,124,56,16]),d,194); + g.drawImage(c([0,8,12,14,255,14,12,8]),d,116); +} + +function drawTime(input) { + g.clear(); + g.setFontAlign(0,0); + g.setFont("4x6",5); + g.drawString(input, g.getWidth() / 2, g.getHeight() / 2); +} + +function drawMenu() { + const timeString = getTimeString(menuTime); + drawTime(timeString); + drawArrows(); +} + +function drawCountdown() { + const timeString = getTimeString(countdownTime); + drawTime(timeString); +} + +/* button callbacks */ + +function getMaxSelectableTime() { + return flashIndex === HOUR_INDEX ? 23 : 59; +} + +/* btn1 */ + +function incrementMenuTime() { + const maxTime = getMaxSelectableTime(); + const currentTime = menuTime[flashIndex]; + const newTime = currentTime < maxTime ? currentTime + 1 : 0; + menuTime[flashIndex] = newTime; +} + +/* btn2 */ + +function incrementScene() { + currentScene++; +} + +function incrementFlashIndex() { + flashIndex++; +} + +function showAll() { + for(var i = 0; i < show.length; i++) { + show[i] = true; + } +} + +function showFlashIndex() { + show[flashIndex] = true; +} + +function hideFlashIndex() { + show[flashIndex] = false; +} + +function next() { + incrementScene(); + + if (currentScene === COUNTDOWN_SCENE) { + showAll(); + startCountdownScene(); + } else { + showFlashIndex(); + incrementFlashIndex(); + hideFlashIndex(); + } +} + +/* btn3 */ + +function decrementMenuTime() { + const maxTime = getMaxSelectableTime(); + const currentTime = menuTime[flashIndex]; + const newTime = currentTime > 0 ? currentTime - 1 : maxTime; + menuTime[flashIndex] = newTime; +} + +/* watches */ + +function setupMenuWatches() { + clearWatch(); + btn1Watch = setWatch(incrementMenuTime, BTN1, {repeat: true}); + btn2Watch = setWatch(next, BTN2, {repeat: true}); + btn3Watch = setWatch(decrementMenuTime, BTN3, {repeat: true}); +} + +function setupCountdownWatches() { + clearWatch(); + btn2Watch = setWatch(main, BTN2, {repeat: true}); +} + +/* scenes */ + +function menu() { + drawMenu(); + toggleShow(flashIndex); +} + +function countdown() { + decrementCountdownTime(); + drawCountdown(); + + if (countDownFinished()) { + unsetDrawInterval(); + alertCountdownFinished(); + } +} + +/* init */ + +function unsetDrawInterval() { + if (!drawInterval) return; + clearInterval(drawInterval); + drawInterval = undefined; +} + +function startMenuScene() { + setupMenuWatches(); + unsetDrawInterval(); + menu(); + drawInterval = setInterval(menu, 500); +} + +function startCountdownScene() { + setupCountdownWatches(); + unsetDrawInterval(); + setCountdownTime(); + showAll(); + drawCountdown(); + drawInterval = setInterval(countdown, 1000); +} + +/* main */ + +function reset() { + currentScene = 0; + flashIndex = HOUR_INDEX; + hideFlashIndex(); +} + +function main() { + reset(); + startMenuScene(); +} + +main(); diff --git a/apps/pizzatimer/pizza.png b/apps/pizzatimer/pizza.png new file mode 100644 index 000000000..d5a083633 Binary files /dev/null and b/apps/pizzatimer/pizza.png differ diff --git a/apps/rndmclk/ChangeLog b/apps/rndmclk/ChangeLog index 2d387a04b..1f53ea4ae 100644 --- a/apps/rndmclk/ChangeLog +++ b/apps/rndmclk/ChangeLog @@ -1,2 +1,3 @@ 0.01: New widget 0.02: Less invasive, change default clock setting instead of directly loading the new clock (no longer breaks Gadgetbridge notifications) +0.03: Only changes when the widget id reloaded (no longer uses LCD turning off) diff --git a/apps/rndmclk/README.md b/apps/rndmclk/README.md index 86138e0e7..d75a53343 100644 --- a/apps/rndmclk/README.md +++ b/apps/rndmclk/README.md @@ -1,6 +1,6 @@ # Summary -Random Clock is a widget that will randomly show one of the installed watch faces each time the LCD is turned on. +Random Clock is a widget that will randomly show one of the installed watch faces each time after the widget is (re-)loaded. # How it works -Everytime the LCD is turned off, the widget randomly changes the clock. When you long press BTN 3 the next time, +Everytime the widget is reloaded, it randomly changes the clock. When you long press BTN 3 the next time, you might (or might not, it's random after all) see another watch face. \ No newline at end of file diff --git a/apps/rndmclk/widget.js b/apps/rndmclk/widget.js index 566d8eed5..479d8b2c3 100644 --- a/apps/rndmclk/widget.js +++ b/apps/rndmclk/widget.js @@ -16,20 +16,18 @@ if (clockApps && clockApps.length > 0) { var clockIndex = getRandomInt(clockApps.length); - // Only update the file if the clock really change to be nice to the FLASH mem + // Only update the file if the clock really changed to be nice to the FLASH mem if (clockApps[clockIndex].src != currentClock) { currentClock = clockApps[clockIndex].src; settings = require("Storage").readJSON('setting.json', 1); settings.clock = clockApps[clockIndex].src; require("Storage").write('setting.json', settings); + + console.log("RandomClockWidget set the clock to '" + clockApps[clockIndex].name + "'"); } } } - Bangle.on('lcdPower', (on) => { - if (!on) { - loadRandomClock(); - } - }); + loadRandomClock(); })(); \ No newline at end of file diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog new file mode 100644 index 000000000..f0eae0198 --- /dev/null +++ b/apps/weather/ChangeLog @@ -0,0 +1 @@ +0.02: Make minor adjustments to widget, and discard stale weather data after a configurable period. diff --git a/apps/weather/app.js b/apps/weather/app.js index 8493144f7..b43a52517 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -1,9 +1,20 @@ (() => { - function draw(w) { + const weather = require('weather'); + + function formatDuration(millis) { + let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); + if (millis < 60000) return pluralize(Math.floor(millis/1000), "second"); + 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"); + } + + function draw() { + let w = weather.current; g.reset(); g.setColor(0).fillRect(0, 24, 239, 239); - require('weather').drawIcon(w.txt, 65, 90, 55); + weather.drawIcon(w.txt, 65, 90, 55); const locale = require("locale"); g.setColor(-1); @@ -30,25 +41,47 @@ g.setFont("6x8", 1).setFontAlign(0, 0, 0); g.drawString(w.txt.charAt(0).toUpperCase()+w.txt.slice(1), 120, 190); + drawUpdateTime(w); + g.flip(); } - const _GB = global.GB; - global.GB = (event) => { - if (event.t==="weather") draw(event); - if (_GB) setTimeout(_GB, 0, event); - }; + function drawUpdateTime() { + if (!weather.current || !weather.current.time) return; + let text = `Last update received ${formatDuration(Date.now() - weather.current.time)} ago`; + g.reset(); + g.setColor(0).fillRect(0, 202, 239, 210); + g.setColor(-1).setFont("6x8", 1).setFontAlign(0, 0, 0); + g.drawString(text, 120, 206); + } + + function update() { + if (weather.current) { + draw(); + } else { + E.showMessage('Weather unknown\n\nIs Gadgetbridge\nconnected?'); + } + } + + let interval = setInterval(drawUpdateTime, 1000); + Bangle.on('lcdPower', (on) => { + if (interval) { + clearInterval(interval); + interval = undefined; + } + if (on) { + drawUpdateTime(); + interval = setInterval(drawUpdateTime, 1000); + } + }); + + weather.on("update", update); + + update(weather.current); + + // Show launcher when middle button pressed + setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: 'falling'}); Bangle.loadWidgets(); Bangle.drawWidgets(); - - const weather = require('weather').load(); - if (weather) { - draw(weather); - } else { - E.showMessage('Weather unknown\n\nIs Gadgetbridge\nconnected?'); - } - - // Show launcher when middle button pressed - setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: 'falling'}) })() diff --git a/apps/weather/lib.js b/apps/weather/lib.js index f87984fe5..8eda2dea9 100644 --- a/apps/weather/lib.js +++ b/apps/weather/lib.js @@ -1,176 +1,214 @@ -exports = { - save: weather => { - let json = require('Storage').readJSON('weather.json')||{} - json.weather = Object.assign({}, weather) // don't mutate GB events - delete json.weather.t // don't save the event type (if present) - require('Storage').write('weather.json', json) - }, - load: () => { - let json = require('Storage').readJSON('weather.json')||{} - return json.weather - }, - drawIcon: (cond, x, y, r) => { - function drawSun(x, y, r) { +const storage = require('Storage'); + +let expiryTimeout = undefined; +function scheduleExpiry(json) { + if (expiryTimeout) { + clearTimeout(expiryTimeout); + expiryTimeout = undefined; + } + if (json.weather && json.weather.time && json.expiry) { + let t = json.weather.time + json.expiry - Date.now(); + expiryTimeout = setTimeout(() => { + expiryTimeout = undefined; + + let json = storage.readJSON('weather.json')||{}; + delete json.weather; + storage.write('weather.json', json); + + exports.current = undefined; + exports.emit("update"); + }, t); + } +} + +function setCurrentWeather(json) { + scheduleExpiry(json); + exports.current = json.weather; +} + +function update(weatherEvent) { + let weather = Object.assign({}, weatherEvent); + weather.time = Date.now(); + delete weather.t; + + let json = storage.readJSON('weather.json')||{}; + json.weather = weather; + storage.write('weather.json', json); + + setCurrentWeather(json); + + exports.emit("update"); +} + +const _GB = global.GB; +global.GB = (event) => { + if (event.t==="weather") update(event); + if (_GB) setTimeout(_GB, 0, event); +}; + +setCurrentWeather(storage.readJSON('weather.json')||{}); + +exports.drawIcon = function(cond, x, y, r) { + function drawSun(x, y, r) { + g.setColor("#FF7700"); + g.fillCircle(x, y, r); + } + + function drawCloud(x, y, r, c) { + const u = r/12; + if (c==null) c = "#EEEEEE"; + g.setColor(c); + g.fillCircle(x-8*u, y+3*u, 4*u); + g.fillCircle(x-4*u, y-2*u, 5*u); + g.fillCircle(x+4*u, y+0*u, 4*u); + g.fillCircle(x+9*u, y+4*u, 3*u); + g.fillPoly([ + x-8*u, y+7*u, + x-8*u, y+3*u, + x-4*u, y-2*u, + x+4*u, y+0*u, + x+9*u, y+4*u, + x+9*u, y+7*u, + ]); + } + + function drawBrokenClouds(x, y, r) { + drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777777"); + drawCloud(x-1/8*r, y+1/8*r, 7/8*r); + } + + function drawFewClouds(x, y, r) { + drawSun(x+3/8*r, y-1/8*r, 5/8*r); + drawCloud(x-1/8*r, y+1/8*r, 7/8*r); + } + + function drawRainLines(x, y, r) { + g.setColor("#FFFFFF"); + const y1 = y+1/2*r; + const y2 = y+1*r; + g.fillPoly([ + x-6/12*r+1, y1, + x-8/12*r+1, y2, + x-7/12*r, y2, + x-5/12*r, y1, + ]); + g.fillPoly([ + x-2/12*r+1, y1, + x-4/12*r+1, y2, + x-3/12*r, y2, + x-1/12*r, y1, + ]); + g.fillPoly([ + x+2/12*r+1, y1, + x+0/12*r+1, y2, + x+1/12*r, y2, + x+3/12*r, y1, + ]); + } + + function drawShowerRain(x, y, r) { + drawFewClouds(x, y-1/3*r, r); + drawRainLines(x, y, r); + } + + function drawRain(x, y, r) { + drawBrokenClouds(x, y-1/3*r, r); + drawRainLines(x, y, r); + } + + function drawThunderstorm(x, y, r) { + function drawLightning(x, y, r) { g.setColor("#FF7700"); - g.fillCircle(x, y, r); - } - - function drawCloud(x, y, r, c) { - const u = r/12; - if (c==null) c = "#EEEEEE"; - g.setColor(c); - g.fillCircle(x-8*u, y+3*u, 4*u); - g.fillCircle(x-4*u, y-2*u, 5*u); - g.fillCircle(x+4*u, y+0*u, 4*u); - g.fillCircle(x+9*u, y+4*u, 3*u); g.fillPoly([ - x-8*u, y+7*u, - x-8*u, y+3*u, - x-4*u, y-2*u, - x+4*u, y+0*u, - x+9*u, y+4*u, - x+9*u, y+7*u, + x-2/6*r, y-r, + x-4/6*r, y+1/6*r, + x-1/6*r, y+1/6*r, + x-3/6*r, y+1*r, + x+3/6*r, y-1/6*r, + x+0/6*r, y-1/6*r, + x+3/6*r, y-r, ]); } - function drawBrokenClouds(x, y, r) { - drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777777"); - drawCloud(x-1/8*r, y+1/8*r, 7/8*r); - } + drawBrokenClouds(x, y-1/3*r, r); + drawLightning(x-1/12*r, y+1/2*r, 1/2*r); + } - function drawFewClouds(x, y, r) { - drawSun(x+3/8*r, y-1/8*r, 5/8*r); - drawCloud(x-1/8*r, y+1/8*r, 7/8*r); - } - - function drawRainLines(x, y, r) { - g.setColor("#FFFFFF"); - const y1 = y+1/2*r; - const y2 = y+1*r; - g.fillPoly([ - x-6/12*r+1, y1, - x-8/12*r+1, y2, - x-7/12*r, y2, - x-5/12*r, y1, - ]); - g.fillPoly([ - x-2/12*r+1, y1, - x-4/12*r+1, y2, - x-3/12*r, y2, - x-1/12*r, y1, - ]); - g.fillPoly([ - x+2/12*r+1, y1, - x+0/12*r+1, y2, - x+1/12*r, y2, - x+3/12*r, y1, - ]); - } - - function drawShowerRain(x, y, r) { - drawFewClouds(x, y-1/3*r, r); - drawRainLines(x, y, r); - } - - function drawRain(x, y, r) { - drawBrokenClouds(x, y-1/3*r, r); - drawRainLines(x, y, r); - } - - function drawThunderstorm(x, y, r) { - function drawLightning(x, y, r) { - g.setColor("#FF7700"); - g.fillPoly([ - x-2/6*r, y-r, - x-4/6*r, y+1/6*r, - x-1/6*r, y+1/6*r, - x-3/6*r, y+1*r, - x+3/6*r, y-1/6*r, - x+0/6*r, y-1/6*r, - x+3/6*r, y-r, - ]); + function drawSnow(x, y, r) { + function rotatePoints(points, pivotX, pivotY, angle) { + for(let i = 0; i {}; - condition = condition.toLowerCase(); - if (condition.includes("thunderstorm")) return drawThunderstorm; - if (condition.includes("freezing")||condition.includes("snow")|| - condition.includes("sleet")) { - return drawSnow; - } - if (condition.includes("drizzle")|| - condition.includes("shower")) { - return drawRain; - } - if (condition.includes("rain")) return drawShowerRain; - if (condition.includes("clear")) return drawSun; - if (condition.includes("few clouds")) return drawFewClouds; - if (condition.includes("scattered clouds")) return drawCloud; - if (condition.includes("clouds")) return drawBrokenClouds; - return drawMist; + function chooseIcon(condition) { + if (!condition) return () => {}; + condition = condition.toLowerCase(); + if (condition.includes("thunderstorm")) return drawThunderstorm; + if (condition.includes("freezing")||condition.includes("snow")|| + condition.includes("sleet")) { + return drawSnow; } + if (condition.includes("drizzle")|| + condition.includes("shower")) { + return drawRain; + } + if (condition.includes("rain")) return drawShowerRain; + if (condition.includes("clear")) return drawSun; + if (condition.includes("few clouds")) return drawFewClouds; + if (condition.includes("scattered clouds")) return drawCloud; + if (condition.includes("clouds")) return drawBrokenClouds; + return drawMist; + } - chooseIcon(cond)(x, y, r) - }, -} + chooseIcon(cond)(x, y, r); +}; diff --git a/apps/weather/settings.js b/apps/weather/settings.js new file mode 100644 index 000000000..1cc097e3a --- /dev/null +++ b/apps/weather/settings.js @@ -0,0 +1,24 @@ +(function(back) { + const storage = require('Storage'); + let settings = storage.readJSON('weather.json', 1) || {}; + function save(key, value) { + settings[key] = value; + storage.write('weather.json', settings); + } + E.showMenu({ + '': { 'title': 'Weather' }, + 'Expiry': { + value: "expiry" in settings ? settings["expiry"] : 2*3600000, + min: 0, + max : 24*3600000, + step: 15*60000, + format: x => { + if (x == 0) return "none"; + if (x < 3600000) return Math.floor(x/60000) + "m"; + if (x < 86400000) return Math.floor(x/36000)/100 + "h"; + }, + onchange: x => save('expiry', x), + }, + '< Back': back, + }); +}) diff --git a/apps/weather/widget.js b/apps/weather/widget.js index e02591543..eb5ead949 100644 --- a/apps/weather/widget.js +++ b/apps/weather/widget.js @@ -1,40 +1,57 @@ (() => { + const weather = require('weather'); + function draw() { - const w = require('weather').load() + const w = weather.current; if (!w) return; g.reset(); - g.setColor(0).fillRect(this.x, this.y, this.x+this.width, this.y+24) + g.setColor(0).fillRect(this.x, this.y, this.x+this.width-1, this.y+23); if (w.txt) { - require('weather').drawIcon(w.txt, this.x+10, this.y+8, 8); + weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5); } if (w.temp) { let t = require('locale').temp(w.temp-273.15); // applies conversion - t = t.substr(0, t.length-2); // but we have no room for units + t = t.match(/[\d\-]*/)[0]; // but we have no room for units g.setFontAlign(0, 1); // center horizontally at bottom of widget g.setFont('6x8', 1); - g.setColor(-1) - g.drawString(t, this.x+10, this.y+24) + g.setColor(-1); + g.drawString(t, this.x+10, this.y+24); } } - function update(weather) { - require('weather').save(weather); + var dirty = false; + + function update() { if (!WIDGETS["weather"].width) { - WIDGETS["weather"].width = 20 - Bangle.drawWidgets() + WIDGETS["weather"].width = 20; + Bangle.drawWidgets(); } else if (Bangle.isLCDOn()) { - WIDGETS["weather"].draw() + WIDGETS["weather"].draw(); + } else { + dirty = true; } } - const _GB = global.GB; - global.GB = (event) => { - if (event.t==="weather") update(event); - if (_GB) setTimeout(_GB, 0, event); - }; - - WIDGETS["weather"] = {area: "tl", width: 20, draw: draw}; - if (!require('weather').load()) { - WIDGETS["weather"].width = 0 + function hide() { + WIDGETS["weather"].width = 0; + Bangle.drawWidgets(); } + + weather.on("update", () => { + if (weather.current) update(); + else hide(); + }); + + Bangle.on('lcdPower', on => { + if (on && dirty) { + WIDGETS["weather"].draw(); + dirty = false; + } + }); + + WIDGETS["weather"] = { + area: "tl", + width: weather.current ? 20 : 0, + draw: draw, + }; })(); diff --git a/js/appinfo.js b/js/appinfo.js index ad2611d19..54cc1b7af 100644 --- a/js/appinfo.js +++ b/js/appinfo.js @@ -1,5 +1,9 @@ -if (typeof btoa==="undefined") - function btoa(d) { return Buffer.from(d).toString('base64'); } +if (typeof btoa==="undefined") { + // Don't define btoa as a function here because Apple's + // iOS browser defines the function even though it's in + // an IF statement that is never executed! + btoa = function(d) { return BufferA.from(d).toString('base64'); } +} // Converts a string into most efficient way to send to Espruino (either json, base64, or compressed base64) function toJS(txt) { diff --git a/js/index.js b/js/index.js index 031391f52..24aa2e4cb 100644 --- a/js/index.js +++ b/js/index.js @@ -24,8 +24,8 @@ httpGet("appdates.csv").then(csv=>{ document.querySelector(".sort-nav").classList.remove("hidden"); csv.split("\n").forEach(line=>{ var l = line.split(","); - appSortInfo[l[0]] = { - created : Date.parse(l[1]), + appSortInfo[l[0]] = { + created : Date.parse(l[1]), modified : Date.parse(l[2]) }; }); @@ -196,7 +196,8 @@ function showTab(tabname) { // =========================================== Library -var chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip => chip.attributes.filterid.value); +// Can't use chip.attributes.filterid.value here because Safari/Apple's WebView doesn't handle it +var chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip => chip.getAttribute("filterid")); var hash = window.location.hash ? window.location.hash.slice(1) : ''; var activeFilter = !!~chips.indexOf(hash) ? hash : ''; @@ -457,15 +458,20 @@ function getAppsToUpdate() { function refreshMyApps() { var panelbody = document.querySelector("#myappscontainer .panel-body"); panelbody.innerHTML = appsInstalled.map(appInstalled => { -var app = appNameToApp(appInstalled.id); -var version = getVersionInfo(app, appInstalled); -return `
+ var app = appNameToApp(appInstalled.id); + var version = getVersionInfo(app, appInstalled); + var username = "espruino"; + var githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); + if(githubMatch) username = githubMatch[1]; + var url = `https://github.com/${username}/BangleApps/tree/master/apps/${app.id}`; + return `
${escapeHtml(app.name)}

${escapeHtml(app.name)} (${version.text})

${escapeHtml(app.description)}

+ See the code on GitHub