diff --git a/apps/bikespeedo/ChangeLog b/apps/bikespeedo/ChangeLog index 7330eb937..e63622217 100644 --- a/apps/bikespeedo/ChangeLog +++ b/apps/bikespeedo/ChangeLog @@ -1,7 +1,8 @@ -0.01: New App! -0.02: Barometer altitude adjustment setting -0.03: Use default Bangle formatter for booleans -0.04: Add options for units in locale and recording GPS -0.05: Allow toggling of "max" values (screen tap) and recording (button press) -0.06: Fix local unit setting +0.01: New App! +0.02: Barometer altitude adjustment setting +0.03: Use default Bangle formatter for booleans +0.04: Add options for units in locale and recording GPS +0.05: Allow toggling of "max" values (screen tap) and recording (button press) +0.06: Fix local unit setting 0.07: Minor code improvements +0.08: Ensure graphics state is reset at the start of a draw (such as if a widget has altered state) diff --git a/apps/bikespeedo/app.js b/apps/bikespeedo/app.js index a1f0b53ce..7d0c65ab1 100644 --- a/apps/bikespeedo/app.js +++ b/apps/bikespeedo/app.js @@ -196,7 +196,7 @@ var emulator = (process.env.BOARD=="EMSCRIPTEN" || process.env.BOARD=="EMSCRIPTE var SATinView = 0; function drawFix(dat) { - g.clearRect(0,screenYstart,screenW,screenH); + g.reset().clearRect(0,screenYstart,screenW,screenH); var v = ''; var u=''; @@ -234,7 +234,6 @@ function drawFix(dat) { drawSats('View:' + SATinView); } } - g.reset(); } @@ -404,12 +403,12 @@ function onGPS(fix) { } function updateClock() { - drawTime(); g.reset(); + drawTime(); if ( emulator ) { max.spd++; max.alt++; - const d=new Date(); + const d=new Date(); sec=d.getSeconds(); onGPS(lf); } @@ -499,7 +498,7 @@ function nextMode() { function start() { Bangle.setBarometerPower(1); // needs some time... - g.clearRect(0,screenYstart,screenW,screenH); + g.reset().clearRect(0,screenYstart,screenW,screenH); onGPS(lf); Bangle.setGPSPower(1); Bangle.on('GPS', onGPS); diff --git a/apps/bikespeedo/metadata.json b/apps/bikespeedo/metadata.json index 20c7d4f53..2df77ec17 100644 --- a/apps/bikespeedo/metadata.json +++ b/apps/bikespeedo/metadata.json @@ -2,7 +2,7 @@ "id": "bikespeedo", "name": "Bike Speedometer (beta)", "shortName": "Bike Speedometer", - "version": "0.07", + "version": "0.08", "description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources", "icon": "app.png", "screenshots": [{"url":"Screenshot.png"}], diff --git a/apps/jsonclock/ChangeLog b/apps/jsonclock/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/jsonclock/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/jsonclock/README.md b/apps/jsonclock/README.md new file mode 100644 index 000000000..740024e68 --- /dev/null +++ b/apps/jsonclock/README.md @@ -0,0 +1,20 @@ +# JSON Clock ![](app.png) + + *JSON view of the time.* + +Written by: [David Volovskiy](https://github.com/voloved) and fully +inspired by [This FitBit face](https://github.com/Sharkgrammer/clockface.json) + +* Displays a JSON that shows the date, time, step count, sunrise/set and battery. +* If the Bangle's theme is dark, then it'll show in dark view (`Settings>System>Theme>Dark BW`) +* It'll show 12 hrs if it's set so in `Settings>System>Locale>Time Format` +* Along with the physical button, you can leave the clock by pressing the `X` next to the `clockface.json` in the tab. +* If the steps cannot be gotten, they won't display +* If the location isn't set, then the JSON array will ignore the sun info and instead display the time as a struct. + +## Screenshots +![](dark.png) +![](white.png) +![](no_steps.png) +![](no_location.png) +![](dark-emulator.png) \ No newline at end of file diff --git a/apps/jsonclock/app-icon.js b/apps/jsonclock/app-icon.js new file mode 100644 index 000000000..dcac31aa3 --- /dev/null +++ b/apps/jsonclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwcCBhEcuPHAQsDwE48AQDnEDx048YCEDQ/AEZAxEnADCEZAKBg5HQCIxiJuARPAEZHBgEBwQGBwE04VNmBHHXYXDhlAg0YsIbBEY8AZQmDhBHcFwcg4AOE4YpCI4swI4NgAQUAAQRHDg0IkAdDkGSAoRBNiBZWjgDCI4UBLAqPHKQMgCJQWFQIIROI6BzD4DXBw0YI5EAhEhwwIBoEwdQwAEWYIDBEYJIYa4REBSQKBBI5kJkmCgECpkwQIprEgcMmOAEZCPVJQYANhwRQaiIjRABqPCgLPEkilBNYxoCmHDa4L+BDAqzFgUIkGCjEgHhwxGAA4uEGYxHImPAahBHDgxEByEAAIIAqI4T+DABXgEaICEADoA=")) diff --git a/apps/jsonclock/app.js b/apps/jsonclock/app.js new file mode 100644 index 000000000..f42a9313e --- /dev/null +++ b/apps/jsonclock/app.js @@ -0,0 +1,259 @@ +{ +const SunCalc = require("suncalc"); // from modules folder +const storage = require('Storage'); +const widget_utils = require('widget_utils'); +const global_settings = storage.readJSON("setting.json", true) || {}; +const LOCATION_FILE = "mylocation.json"; +const h = g.getHeight(); +const w = g.getWidth(); +let location; +let cachedSunTimes = null; +let lastSunCalcDate = null; +var prevVals = {}; +let drawTimeout; + +var settings = { + hr_12: global_settings["12hour"] !== undefined ? global_settings["12hour"] : false, + dark_mode: g.theme.dark +}; + +let clrs = { + tab: "#505050", // grey + keys: settings.dark_mode ? "#4287f5" : "#0000FF", // blue + strings: settings.dark_mode ? "#F0A000" : "#FF0000", // orange or red + ints: settings.dark_mode ? "#00FF00" : "#005F00", // green + bg: g.theme.bg, + brackets: g.theme.fg, +}; + + +let jsonText; +let lines = []; +let fontSize = 13; +const lineHeight = 16; + +const buttonHeight = 12; +const buttonX = 78; +const buttonY = 3; + +let valuePositions = []; +const headerHeight = 16; +const usableHeight = h - headerHeight; +const maxLines = Math.floor(usableHeight / lineHeight); +var numWidth = 0; + +// requires the myLocation app +let loadLocation = function() { + location = require("Storage").readJSON(LOCATION_FILE, 1) || {}; + location.lat = location.lat || 0; + location.lon = location.lon || 0; + location.location = location.location || null; +}; + +let getHr = function(h) { + var amPm = ""; + if (settings.hr_12) { + amPm = h < 12 ? "AM" : "PM"; + h = h % 12; + if (h == 0) h = 12; + } + return [h, amPm]; +}; + +let extractTime = function(d) { + const out = getHr(d.getHours()); + const h = out[0]; + const amPm = out[1]; + const m = d.getMinutes(); + return `${h}:${("0"+m).substr(-2)}${amPm}`; +}; + +let extractDate = function(d) { + const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + ]; + + const weekday = weekdays[d.getDay()]; + const month = months[d.getMonth()]; + const day = d.getDate(); + + return `${weekday} ${month} ${day}`; +}; + +let getSteps = function() { + try { + return Bangle.getHealthStatus("day").steps; + } catch (e) { + if (WIDGETS.wpedom !== undefined) + return WIDGETS.wpedom.getSteps(); + else + return null; + } +}; + +let getVal = function(now, loc) { + const vals = {}; + const currentDateStr = extractDate(now); + if (loc.location) { + if (lastSunCalcDate !== currentDateStr) { + cachedSunTimes = SunCalc.getTimes(now, location.lat, location.lon); + lastSunCalcDate = currentDateStr; + } + vals.rise = extractTime(cachedSunTimes.sunrise); + vals.set = extractTime(cachedSunTimes.sunset); + } + vals.time = extractTime(now); + vals.date = currentDateStr; + vals.batt_pct = E.getBattery(); + vals.steps = getSteps(); + return vals; +}; + +let loadJson = function() { + const now = new Date(); + const vals = getVal(now, location); + //vals.steps = null; // For testing; uncomment to see the steps not appear + //location.location = null; // For testing, if null, the time becomes an struct to take up sun's struct + let raw; + + if (location.location !== null) { + raw = { + time: vals.time, + dt: vals.date, + sun: { + rise: vals.rise, + set: vals.set, + }, + "batt_%": vals.batt_pct, + }; + } else { + raw = { + time: { + hr: getHr(now.getHours())[0], + min: now.getMinutes(), + }, + dt: vals.date, + "batt_%": vals.batt_pct, + }; + } + + if (vals.steps != null) raw.steps = vals.steps; + + jsonText = JSON.stringify(raw, null, 2); + lines = jsonText.split("\n"); +}; + +let draw = function() { + g.clear(); + g.setFontAlign(-1, -1); + g.setFont("Vector", 10); + valuePositions = []; + + g.setColor(clrs.tab); + + g.fillRect(90, 0, w, headerHeight); + g.setColor(clrs.brackets); + g.drawString("clockface.json", 3, 3); + + g.setFont("Vector", buttonHeight); + g.drawString("X", buttonX, buttonY); + g.setFont("Vector", fontSize); + + for (let i = 0; i < maxLines; i++) { + const y = headerHeight + i * lineHeight; + const lineNumberStr = (i + 1).toString().padStart(2, " ") + " "; + g.drawString(lineNumberStr, 0, y); + numWidth = Math.max(numWidth, g.stringWidth(lineNumberStr)); + } + + redrawValues(); +}; + +let redraw = function() { + for (let i = 0; i < maxLines; i++) { + const lineIndex = i; + const line = lines[lineIndex]; + if (!line) continue; + const y = headerHeight + i * lineHeight; + + const indentMatch = line.match(/^(\s*)/); + const indent = indentMatch ? indentMatch[1] : ""; + + const kvMatch = line.trim().match(/^"([^"]+)":\s*(.+)$/); + if (kvMatch) { + const key = kvMatch[1]; + let value = kvMatch[2]; + + if (prevVals.key == value) continue; + prevVals.key = value; + + // Key + g.setColor(clrs.keys); + g.drawString(indent + `"${key}"`, numWidth, y); + const keyWidth = g.stringWidth(indent + `"${key}"`); + const valueX = numWidth + keyWidth; + const valueText = ": " + value; + + // Value color + if (value.startsWith('"')) { + g.setColor(clrs.strings); + } else if (value.startsWith('{') || value.startsWith('}')) { + g.setColor(clrs.brackets); + } else { + g.setColor(clrs.ints); + } + g.drawString(valueText, valueX, y); + + valuePositions.push({ + key, + x: valueX, + y, + text: value + }); + } else { + g.setColor(clrs.brackets); + g.drawString(line, numWidth, y); + } + } +}; + +let clearVals = function() { + g.setFont("Vector", fontSize); + g.setFontAlign(-1, -1); + valuePositions.forEach(pos => { + g.setColor(clrs.bg); + g.fillRect(pos.x, pos.y, w, pos.y + lineHeight); + }); +}; + +let redrawValues = function() { + loadJson(); + clearVals(); + redraw(); + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + redrawValues(); + }, 60000 - (Date.now() % 60000)); +}; + +Bangle.on('touch', (zone, e) => { + if (e.x >= (buttonY - buttonHeight) && e.x <= (buttonX + buttonHeight) && + (e.y >= (buttonY - buttonHeight) && e.y <= (buttonY + buttonHeight))) { + Bangle.showLauncher(); // Exit app + } +}); + +Bangle.on('backlight', function(on) { + if (on) { + redrawValues(); + } +}); + +Bangle.setUI("clock"); +loadLocation(); +Bangle.loadWidgets(); +widget_utils.hide(); +draw(); +} \ No newline at end of file diff --git a/apps/jsonclock/app.png b/apps/jsonclock/app.png new file mode 100644 index 000000000..ab6c821f4 Binary files /dev/null and b/apps/jsonclock/app.png differ diff --git a/apps/jsonclock/dark-emulator.png b/apps/jsonclock/dark-emulator.png new file mode 100644 index 000000000..88e06c4ef Binary files /dev/null and b/apps/jsonclock/dark-emulator.png differ diff --git a/apps/jsonclock/dark.png b/apps/jsonclock/dark.png new file mode 100644 index 000000000..fcbc3e6ff Binary files /dev/null and b/apps/jsonclock/dark.png differ diff --git a/apps/jsonclock/light.png b/apps/jsonclock/light.png new file mode 100644 index 000000000..b5ecef0d6 Binary files /dev/null and b/apps/jsonclock/light.png differ diff --git a/apps/jsonclock/metadata.json b/apps/jsonclock/metadata.json new file mode 100644 index 000000000..215da7e58 --- /dev/null +++ b/apps/jsonclock/metadata.json @@ -0,0 +1,15 @@ +{ "id": "jsonclock", + "name": "JsonClock", + "version": "0.01", + "description": "JSON view of the time, date, steps, battery, and sunrise and sunset times", + "icon": "app.png", + "screenshots": [{"url":"dark-emulator.png"}], + "readme": "README.md", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"jsonclock.app.js","url":"app.js"}, + {"name":"jsonclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/jsonclock/no_location.png b/apps/jsonclock/no_location.png new file mode 100644 index 000000000..5699ba7e3 Binary files /dev/null and b/apps/jsonclock/no_location.png differ diff --git a/apps/jsonclock/no_steps.png b/apps/jsonclock/no_steps.png new file mode 100644 index 000000000..5919aa0f2 Binary files /dev/null and b/apps/jsonclock/no_steps.png differ diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog index 2bd62b83c..c3dfd9ffe 100644 --- a/apps/runplus/ChangeLog +++ b/apps/runplus/ChangeLog @@ -28,5 +28,6 @@ Write to correct settings file, fixing settings not working. 0.25: Fix step count bug when runs are resumed after a long time 0.26: Add ability to zoom in on a single stat by tapping it 0.27: Allow setting to alway resume an activity -0.28: Change the "time" stat to show active time (duration) rather than +0.28: Add vibration feedback on start/stop +0.29: Change the "time" stat to show active time (duration) rather than elapsed time (fix #3802) diff --git a/apps/runplus/app.js b/apps/runplus/app.js index e83112219..219bdd14b 100644 --- a/apps/runplus/app.js +++ b/apps/runplus/app.js @@ -27,6 +27,7 @@ let settings = Object.assign({ B6: "caden", paceLength: 1000, alwaysResume: false, + vibrate: false, notify: { dist: { value: 0, @@ -59,6 +60,8 @@ function setStatus(running) { // Called to start/stop running function onStartStop() { + if (settings.vibrate) Bangle.buzz(250); + if (screen === "karvonen") { // start/stop on the karvonen screen reverts us to the main screen setScreen("main"); diff --git a/apps/runplus/metadata.json b/apps/runplus/metadata.json index dab94890b..4c405b922 100644 --- a/apps/runplus/metadata.json +++ b/apps/runplus/metadata.json @@ -1,7 +1,7 @@ { "id": "runplus", "name": "Run+", - "version": "0.28", + "version": "0.29", "description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screens for heart rate interval training and individual stat focus.", "icon": "app.png", "tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen", diff --git a/apps/runplus/settings.js b/apps/runplus/settings.js index 77343d6b3..d18833e1e 100644 --- a/apps/runplus/settings.js +++ b/apps/runplus/settings.js @@ -18,6 +18,7 @@ B6: "caden", paceLength: 1000, // TODO: Default to either 1km or 1mi based on locale alwaysResume: false, + vibrate: false, notify: { dist: { increment: 0, @@ -80,6 +81,13 @@ saveSettings(); }, }; + menu[/*LANG*/"Start/stop vibrate"] = { + value : settings.vibrate, + onchange : v => { + settings.vibrate = v; + saveSettings(); + }, + }; var notificationsMenu = { '< Back': function() { E.showMenu(menu) }, } diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index d7cdd07be..0f88be9dd 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -84,4 +84,5 @@ of 'Select Clock' 0.73: Fix `const` bug / work with fastload 0.74: Add extra layer of checks before allowing a factory reset (fix #3476) 0.75: Restore previous menu's scroll positions -0.76: Add altitude calibration menu (and update README after menu changed) \ No newline at end of file +0.76: Add altitude calibration menu (and update README after menu changed) +0.77: Save altitude calibration when user exits via reset diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index 8191598f9..b37534577 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.76", + "version": "0.77", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 6cde55696..8303c8c25 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -1036,8 +1036,14 @@ function showAltitude() { menuAltitude.value = Math.round(pressure.altitude); m.draw(); } + function altitudeDone() { + settings.seaLevelPressure = seaLevelPressure; + updateSettings(); + } + Bangle.setBarometerPower(1,"settings"); Bangle.on("pressure",onPressure); + E.on("kill", altitudeDone); var seaLevelPressure = Bangle.getOptions().seaLevelPressure; if (!isFinite(seaLevelPressure)) seaLevelPressure=1013.25; var menuPressure = {value:"-"}; @@ -1045,8 +1051,8 @@ function showAltitude() { var m = E.showMenu({ "" : {title:/*LANG*/"Altitude",back:() => { Bangle.setBarometerPower(0,"settings"); Bangle.removeListener("pressure",onPressure); - settings.seaLevelPressure = seaLevelPressure; - updateSettings(); + E.removeListener("kill",altitudeDone); + altitudeDone(); popMenu(systemMenu()); }}, /*LANG*/"Pressure (hPa)" : menuPressure, @@ -1054,17 +1060,17 @@ function showAltitude() { /*LANG*/"Adjust up" : function() { Bangle.buzz(80); seaLevelPressure++; - Bangle.setOptions({seaLevelPressure:seaLevelPressure}); + Bangle.setOptions({seaLevelPressure}); }, /*LANG*/"Adjust down" : function() { Bangle.buzz(80); seaLevelPressure--; - Bangle.setOptions({seaLevelPressure:seaLevelPressure}); + Bangle.setOptions({seaLevelPressure}); }, /*LANG*/"Set Default" : function() { Bangle.buzz(); seaLevelPressure=1013.25; - Bangle.setOptions({seaLevelPressure:seaLevelPressure}); + Bangle.setOptions({seaLevelPressure}); } }); } diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index 56cc64be1..65d1a8d9b 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -7,3 +7,4 @@ 0.07: Use default Bangle formatter for booleans 0.08: Better formula for the moon's phase 0.09: Fix variable declaration +0.10: Added ability to hide widget diff --git a/apps/widmp/metadata.json b/apps/widmp/metadata.json index a334ec27e..bd4c182d0 100644 --- a/apps/widmp/metadata.json +++ b/apps/widmp/metadata.json @@ -1,7 +1,7 @@ { "id": "widmp", "name": "Moon Phase", - "version": "0.09", + "version": "0.10", "description": "Display the current moon phase in blueish (in light mode) or white (in dark mode) for both hemispheres. In the southern hemisphere the 'My Location' app is needed.", "icon": "widget.png", "type": "widget", diff --git a/apps/widmp/settings.js b/apps/widmp/settings.js index e671d9900..734e90a5b 100644 --- a/apps/widmp/settings.js +++ b/apps/widmp/settings.js @@ -2,6 +2,7 @@ var settings = Object.assign({ default_colour: true, + hide: false, red: 0, green: 0, blue: 0, @@ -30,6 +31,13 @@ writeSettings(); } }, + "Hide Widget": { + value: settings.hide, + onchange: () => { + settings.hide = !settings.hide; + writeSettings(); + } + }, "Custom...": () => E.showMenu(custommenu) }; diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index 89c072ca1..d94c8697a 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -3,6 +3,7 @@ var lastCalculated = 0; // When we last calculated the phase var phase = 0; // The last phase we calculated var southernHemisphere = false; // when in southern hemisphere -- use the "My Location" App + var settings; // https://github.com/deirdreobyrne/LunarPhase function moonPhase(sec) { @@ -39,14 +40,18 @@ g.drawLine(CenterX-leftFactor*y,CenterY+x, CenterX+rightFactor*y,CenterY+x); } } - - function setMoonColour(g) { - var settings = Object.assign({ + + function reloadSettings() { + settings = Object.assign({ default_colour: true, + hide: false, red: 0, green: 0, blue: 0, }, require('Storage').readJSON("widmp.json", true) || {}); + } + + function setMoonColour(g) { if (settings.default_colour) { if (g.theme.dark) { g.setColor(0xffff); // white @@ -62,6 +67,7 @@ function draw() { + if (settings.hide) return; const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11; let leftFactor, rightFactor; @@ -90,9 +96,11 @@ drawMoonPhase(CenterX,CenterY, Radius, leftFactor,rightFactor); } + reloadSettings(); + var wid = settings.hide ? 0 : 24; WIDGETS["widmp"] = { area: "tr", - width: 24, + width: wid, draw: draw }; diff --git a/core b/core index bf8d62eae..49bab4e3c 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit bf8d62eae968525b0964952ce84ad9bfe020e1d4 +Subproject commit 49bab4e3c2483109e013fa3049dc6debf82b9a78