diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog index 967062223..5ff1eb90e 100644 --- a/apps/bwclk/ChangeLog +++ b/apps/bwclk/ChangeLog @@ -14,4 +14,6 @@ 0.14: Adds humidity to weather data. 0.15: Added option for a dynamic mode to show widgets only if unlocked. 0.16: You can now show your agenda if your calendar is synced with Gadgetbridge. -0.17: Fix - Step count was no more shown in the menu. \ No newline at end of file +0.17: Fix - Step count was no more shown in the menu. +0.18: Set timer for an agenda entry by simply clicking in the middle of the screen. Only one timer can be set. +0.19: Fix - Compatibility with "Digital clock widget" diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md index 3f177d6fa..dfb9bf515 100644 --- a/apps/bwclk/README.md +++ b/apps/bwclk/README.md @@ -7,7 +7,7 @@ A very minimalistic clock to mainly show date and time. The BW clock provides many features and also 3rd party integrations: - Bangle data such as steps, heart rate, battery or charging state. - A timer can be set directly. *Requirement: Scheduler library* -- Show agenda entries. *Requirement: Gadgetbridge calendar sync enabled* +- Show agenda entries. A timer for an agenda entry can also be set by simply clicking in the middle of the screen. This can be used to not forget a meeting etc. Note that only one agenda-timer can be set at a time. *Requirement: Gadgetbridge calendar sync enabled* - Weather temperature as well as the wind speed can be shown. *Requirement: Weather app* - HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app* diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js index 577014130..a23df6e0d 100644 --- a/apps/bwclk/app.js +++ b/apps/bwclk/app.js @@ -8,10 +8,16 @@ const storage = require('Storage'); * Statics */ const SETTINGS_FILE = "bwclk.setting.json"; -const TIMER_IDX = "bwclk"; +const TIMER_IDX = "bwclk_timer"; +const TIMER_AGENDA_IDX = "bwclk_agenda"; const W = g.getWidth(); const H = g.getHeight(); +/************ + * Global data + */ +var pressureData; + /************ * Settings */ @@ -181,6 +187,14 @@ function imgAgenda() { } } +function imgMountain() { + return { + width : 24, height : 24, bpp : 1, + transparent : 1, + buffer : atob("//////////////////////3///n///D//uZ//E8//A+/+Z+f8//P5//n7//3z//zn//5AAAAAAAA////////////////////") + } +} + /************ * 2D MENU with entries of: * [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]] @@ -195,6 +209,7 @@ var menu = [ function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] }, function(){ return [ getSteps(), imgSteps() ] }, function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] }, + function(){ return [ getAltitude(), imgMountain() ]}, ] ] @@ -205,8 +220,8 @@ try{ require('sched'); menu.push([ function(){ - var text = isAlarmEnabled() ? getAlarmMinutes() + " min." : "Timer"; - return [text, imgTimer(), () => decreaseAlarm(), () => increaseAlarm(), null ] + var text = isAlarmEnabled(TIMER_IDX) ? getAlarmMinutes(TIMER_IDX) + " min." : "Timer"; + return [text, imgTimer(), () => decreaseAlarm(TIMER_IDX), () => increaseAlarm(TIMER_IDX), null ] }, ]); } catch(ex) { @@ -219,6 +234,7 @@ try{ * Note that we handle the agenda differently in order to hide old entries... */ var agendaIdx = 0; +var agendaTimerIdx = 0; if(storage.readJSON("android.calendar.json") !== undefined){ function nextAgendaEntry(){ agendaIdx += 1; @@ -246,9 +262,43 @@ if(storage.readJSON("android.calendar.json") !== undefined){ var title = entry.title.slice(0,14); var date = new Date(entry.timestamp*1000); var dateStr = locale.date(date).replace(/\d\d\d\d/,""); - dateStr += entry.durationInSeconds < 86400 ? " / " + locale.time(date,1) : ""; + dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; - return [title + "\n" + dateStr, imgAgenda(), () => nextAgendaEntry(), () => previousAgendaEntry(), null] + function dynImgAgenda(){ + if(isAlarmEnabled(TIMER_AGENDA_IDX) && agendaTimerIdx == agendaIdx){ + return imgTimer(); + } else { + return imgAgenda(); + } + } + + return [title + "\n" + dateStr, dynImgAgenda(), () => nextAgendaEntry(), () => previousAgendaEntry(), function(){ + try{ + var alarm = require('sched') + + // If other time, we disable the old one and enable this one. + if(agendaIdx != agendaTimerIdx){ + agendaTimerIdx = -1; + alarm.setAlarm(TIMER_AGENDA_IDX, undefined); + } + + // Disable alarm if enabled + if(isAlarmEnabled(TIMER_AGENDA_IDX)){ + agendaTimerIdx = -1; + alarm.setAlarm(TIMER_AGENDA_IDX, undefined); + alarm.reload(); + return + } + + // Otherwise, set alarm for given event + agendaTimerIdx = agendaIdx; + alarm.setAlarm(TIMER_AGENDA_IDX, { + msg: title, + timer : parseInt((date - now)), + }); + alarm.reload(); + } catch(ex){ } + }] }, ]); } @@ -330,6 +380,14 @@ function getSteps() { } +function getAltitude(){ + if(pressureData && pressureData.altitude){ + return Math.round(pressureData.altitude) + "m"; + } + return "???"; +} + + function getWeather(){ var weatherJson; @@ -364,10 +422,10 @@ function getWeather(){ } -function isAlarmEnabled(){ +function isAlarmEnabled(idx){ try{ var alarm = require('sched'); - var alarmObj = alarm.getAlarm(TIMER_IDX); + var alarmObj = alarm.getAlarm(idx); if(alarmObj===undefined || !alarmObj.on){ return false; } @@ -379,22 +437,22 @@ function isAlarmEnabled(){ } -function getAlarmMinutes(){ - if(!isAlarmEnabled()){ +function getAlarmMinutes(idx){ + if(!isAlarmEnabled(idx)){ return -1; } var alarm = require('sched'); - var alarmObj = alarm.getAlarm(TIMER_IDX); + var alarmObj = alarm.getAlarm(idx); return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000)); } -function increaseAlarm(){ +function increaseAlarm(idx){ try{ - var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0; - var alarm = require('sched') - alarm.setAlarm(TIMER_IDX, { + var minutes = isAlarmEnabled(idx) ? getAlarmMinutes(idx) : 0; + var alarm = require('sched'); + alarm.setAlarm(idx, { timer : (minutes+5)*60*1000, }); alarm.reload(); @@ -402,16 +460,16 @@ function increaseAlarm(){ } -function decreaseAlarm(){ +function decreaseAlarm(idx){ try{ - var minutes = getAlarmMinutes(); + var minutes = getAlarmMinutes(idx); minutes -= 5; var alarm = require('sched') - alarm.setAlarm(TIMER_IDX, undefined); + alarm.setAlarm(idx, undefined); if(minutes > 0){ - alarm.setAlarm(TIMER_IDX, { + alarm.setAlarm(idx, { timer : minutes*60*1000, }); } @@ -421,6 +479,19 @@ function decreaseAlarm(){ } +function handleAsyncData(){ + try{ + + if (settings.menuPosX == 1){ + Bangle.getPressure().then(data=>{ + pressureData = data + }); + } + + }catch(ex){ } +} + + /************ * DRAW */ @@ -428,6 +499,9 @@ function draw() { // Queue draw again queueDraw(); + // Now lets measure some data.. + handleAsyncData(); + // Draw clock drawDate(); drawTime(); @@ -670,6 +744,7 @@ Bangle.on('touch', function(btn, e){ menuEntry[4](); setTimeout(()=>{ Bangle.buzz(80, 0.6); + drawTime(); }, 250); } catch(ex){ // In case it fails, we simply ignore it. @@ -698,6 +773,9 @@ E.on("kill", function(){ // dark/light theme as well as the colors. g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear(); +// Show launcher when middle button pressed +Bangle.setUI("clock"); + // Load widgets and draw clock the first time Bangle.loadWidgets(); @@ -707,6 +785,3 @@ for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;} // Draw first time draw(); - -// Show launcher when middle button pressed -Bangle.setUI("clock"); diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json index 1eb1fde72..bc2bb37b6 100644 --- a/apps/bwclk/metadata.json +++ b/apps/bwclk/metadata.json @@ -1,7 +1,7 @@ { "id": "bwclk", "name": "BW Clock", - "version": "0.17", + "version": "0.19", "description": "A very minimalistic clock to mainly show date and time.", "readme": "README.md", "icon": "app.png", diff --git a/apps/bwclk/screenshot_3.png b/apps/bwclk/screenshot_3.png index f9a9a7d3f..8d982cac4 100644 Binary files a/apps/bwclk/screenshot_3.png and b/apps/bwclk/screenshot_3.png differ diff --git a/apps/chimer/ChangeLog b/apps/chimer/ChangeLog new file mode 100644 index 000000000..01bd00a0a --- /dev/null +++ b/apps/chimer/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial Creation +0.02: Fixed some sleep bugs. Added a sleep mode toggle \ No newline at end of file diff --git a/apps/chimer/metadata.json b/apps/chimer/metadata.json index fab277f15..d5bc04950 100644 --- a/apps/chimer/metadata.json +++ b/apps/chimer/metadata.json @@ -1,12 +1,13 @@ { "id": "chimer", "name": "Chimer", - "version": "0.01", + "version": "0.02", "description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Reapeat Chime up to 3 times \n - Set hours to disable chime", "icon": "widget.png", "type": "widget", "tags": "widget", "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.MD", "storage": [ { "name": "chimer.wid.js", "url": "widget.js" }, { "name": "chimer.settings.js", "url": "settings.js" } diff --git a/apps/chimer/settings.js b/apps/chimer/settings.js index 1174246bd..55160c9be 100644 --- a/apps/chimer/settings.js +++ b/apps/chimer/settings.js @@ -36,14 +36,21 @@ Repetition: { value: settings.repeat, min: 1, - max: 3, + max: 5, format: (v) => v, onchange: (v) => { settings.repeat = v; writeSettings(settings); }, }, - "Start Hour": { + "Sleep Mode": { + value: !!settings.sleep, + onchange: (v) => { + settings.sleep = v; + writeSettings(settings); + }, + }, + "Sleep Start": { value: settings.start, min: 0, max: 23, @@ -53,7 +60,7 @@ writeSettings(settings); }, }, - "End Hour": { + "Sleep End": { value: settings.end, min: 0, max: 23, @@ -71,6 +78,7 @@ type: 1, freq: 0, repeat: 1, + sleep: true, start: 6, end: 22, }; diff --git a/apps/chimer/widget.js b/apps/chimer/widget.js index 9029511f8..18358df9e 100644 --- a/apps/chimer/widget.js +++ b/apps/chimer/widget.js @@ -7,6 +7,7 @@ type: 1, freq: 0, repeat: 1, + sleep: true, start: 6, end: 22, }; @@ -32,7 +33,7 @@ } else { return; } - sleep(100); + sleep(150); } } @@ -44,7 +45,11 @@ m = now.getMinutes(), s = now.getSeconds(), ms = now.getMilliseconds(); - if (h > settings.end || h < settings.start) { + if ( + (settings.sleep && h > settings.end) || + (settings.sleep && h >= settings.end && m !== 0) || + (settings.sleep && h < settings.start) + ) { var mLeft = 60 - m, sLeft = mLeft * 60 - s, msLeft = sLeft * 1000 - ms; @@ -52,7 +57,8 @@ return; } if (settings.freq === 1) { - if ((m !== lastMinute && m === 0) || m === 30) chime(); + if ((m !== lastMinute && m === 0) || (m !== lastMinute && m === 30)) + chime(); lastHour = h; lastMinute = m; // check again in 30 minutes @@ -70,7 +76,12 @@ } setTimeout(check, msLeft); } else if (settings.freq === 2) { - if ((m !== lastMinute && m === 0) || m === 15 || m === 30 || m === 45) + if ( + (m !== lastMinute && m === 0) || + (m !== lastMinute && m === 15) || + (m !== lastMinute && m === 30) || + (m !== lastMinute && m === 45) + ) chime(); lastHour = h; lastMinute = m; diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog index 096f85787..b530843e7 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -5,4 +5,5 @@ 0.05: Don't poll for GPS status, override setGPSPower handler (fix #1456) 0.06: Periodically update so the always on display does show current GPS fix 0.07: Alternative marker icon (configurable via settings) -0.08: Add ability to hide the icon when GPS is off, for a cleaner appearance. \ No newline at end of file +0.08: Add ability to hide the icon when GPS is off, for a cleaner appearance. +0.09: Do not take widget space if icon is hidden diff --git a/apps/widgps/metadata.json b/apps/widgps/metadata.json index 14cdb81d4..cfd35f5bb 100644 --- a/apps/widgps/metadata.json +++ b/apps/widgps/metadata.json @@ -1,7 +1,7 @@ { "id": "widgps", "name": "GPS Widget", - "version": "0.08", + "version": "0.09", "description": "Tiny widget to show the power and fix status of the GPS/GNSS", "icon": "widget.png", "type": "widget", diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js index ee34d174c..73351eaa6 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -11,13 +11,14 @@ var interval; var oldSetGPSPower = Bangle.setGPSPower; Bangle.setGPSPower = function(on, id) { var isGPSon = oldSetGPSPower(on, id); - WIDGETS.gps.draw(); + WIDGETS.gps.width = !isGPSon && settings.hideWhenGpsOff ? 0 : 24; + Bangle.drawWidgets(); return isGPSon; }; WIDGETS.gps = { area : "tr", - width : 24, + width : !Bangle.isGPSOn() && settings.hideWhenGpsOff ? 0 : 24, draw : function() { g.reset(); diff --git a/apps/widmeda/ChangeLog b/apps/widmeda/ChangeLog new file mode 100644 index 000000000..7415150e6 --- /dev/null +++ b/apps/widmeda/ChangeLog @@ -0,0 +1 @@ +0.01: Initial Medical Alert Widget! diff --git a/apps/widmeda/README.md b/apps/widmeda/README.md new file mode 100644 index 000000000..0bbfc4dc3 --- /dev/null +++ b/apps/widmeda/README.md @@ -0,0 +1,23 @@ +# Medical Alert Widget + +Shows a medical alert logo in the top right widget area, and a medical alert message in the bottom widget area. + +**Note:** this is not a replacement for a medical alert band but hopefully a useful addition. + +## Features + +Implemented: + +- Basic medical alert logo and message +- Only display bottom widget on clocks +- High contrast colours depending on theme + +Future: + +- Configure when to show bottom widget (always/never/clocks) +- Configure medical alert text +- Show details when touched + +## Creator + +James Taylor ([jt-nti](https://github.com/jt-nti)) diff --git a/apps/widmeda/metadata.json b/apps/widmeda/metadata.json new file mode 100644 index 000000000..346306b8e --- /dev/null +++ b/apps/widmeda/metadata.json @@ -0,0 +1,15 @@ +{ "id": "widmeda", + "name": "Medical Alert Widget", + "shortName":"Medical Alert", + "version":"0.01", + "description": "Display a medical alert in the bottom widget section.", + "icon": "widget.png", + "type": "widget", + "tags": "health,medical,tools,widget", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [{"url":"screenshot_light.png"}], + "storage": [ + {"name":"widmeda.wid.js","url":"widget.js"} + ] +} diff --git a/apps/widmeda/screenshot_light.png b/apps/widmeda/screenshot_light.png new file mode 100644 index 000000000..bf92d753d Binary files /dev/null and b/apps/widmeda/screenshot_light.png differ diff --git a/apps/widmeda/widget.js b/apps/widmeda/widget.js new file mode 100644 index 000000000..a2d109596 --- /dev/null +++ b/apps/widmeda/widget.js @@ -0,0 +1,30 @@ +(() => { + // Top right star of life logo + WIDGETS["widmedatr"]={ + area: "tr", + width: 24, + draw: function() { + g.reset(); + g.setColor("#f00"); + g.drawImage(atob("FhYBAAAAA/AAD8AAPwAc/OD/P8P8/x/z/n+/+P5/wP58A/nwP5/x/v/n/P+P8/w/z/Bz84APwAA/AAD8AAAAAA=="), this.x + 1, this.y + 1); + } + }; + + // Bottom medical alert message + WIDGETS["widmedabl"]={ + area: "bl", + width: Bangle.CLOCK?Bangle.appRect.w:0, + draw: function() { + // Only show the widget on clocks + if (!Bangle.CLOCK) return; + + g.reset(); + g.setBgColor(g.theme.dark ? "#fff" : "#f00"); + g.setColor(g.theme.dark ? "#f00" : "#fff"); + g.setFont("Vector",18); + g.setFontAlign(0,0); + g.clearRect(this.x, this.y, this.x + this.width - 1, this.y + 23); + g.drawString("MEDICAL ALERT", this.width / 2, this.y + ( 23 / 2 )); + } + }; +})(); diff --git a/apps/widmeda/widget.png b/apps/widmeda/widget.png new file mode 100644 index 000000000..249bf15bf Binary files /dev/null and b/apps/widmeda/widget.png differ diff --git a/apps/widtwenties/ChangeLog b/apps/widtwenties/ChangeLog new file mode 100644 index 000000000..87935d810 --- /dev/null +++ b/apps/widtwenties/ChangeLog @@ -0,0 +1,2 @@ +0.01: New Widget! +0.02: Fix calling null on draw \ No newline at end of file diff --git a/apps/widtwenties/README.md b/apps/widtwenties/README.md new file mode 100644 index 000000000..1dea18b8e --- /dev/null +++ b/apps/widtwenties/README.md @@ -0,0 +1,15 @@ +# Twenties + +Follow the [20-20-20 rule](https://www.aoa.org/AOA/Images/Patients/Eye%20Conditions/20-20-20-rule.pdf) with discrete reminders. Your BangleJS will buzz every 20 minutes for you to look away from your screen, and then buzz 20 seconds later to look back. Additionally, alternate between standing and sitting every 20 minutes to be standing for [more than 30 minutes](https://uwaterloo.ca/kinesiology-health-sciences/how-long-should-you-stand-rather-sit-your-work-station) per hour. + +## Usage + +Download this widget and, as long as your watch-face supports widgets, it will automatically run in the background. + +## Features + +Vibrate to remind you to stand up and look away for healthy living. + +## Creator + +[@splch](https://github.com/splch/) diff --git a/apps/widtwenties/metadata.json b/apps/widtwenties/metadata.json new file mode 100644 index 000000000..2e51457ac --- /dev/null +++ b/apps/widtwenties/metadata.json @@ -0,0 +1,13 @@ +{ + "id": "widtwenties", + "name": "Twenties", + "shortName": "twenties", + "version": "0.02", + "description": "Buzzes every 20m to stand / sit and look 20ft away for 20s.", + "icon": "widget.png", + "type": "widget", + "tags": "widget,tools", + "supports": ["BANGLEJS", "BANGLEJS2"], + "readme": "README.md", + "storage": [{ "name": "widtwenties.wid.js", "url": "widget.js" }] +} diff --git a/apps/widtwenties/widget.js b/apps/widtwenties/widget.js new file mode 100644 index 000000000..58bc622eb --- /dev/null +++ b/apps/widtwenties/widget.js @@ -0,0 +1,24 @@ +// WIDGETS = {}; // <-- for development only + +/* run widgets in their own function scope so +they don't interfere with currently-running apps */ +(() => { + const move = 20 * 60 * 1000; // 20 minutes + const look = 20 * 1000; // 20 seconds + + buzz = _ => { + Bangle.buzz().then(_ => { + setTimeout(Bangle.buzz, look); + }); + }; + + // add widget + WIDGETS.twenties = { + buzz: buzz, + draw: _ => { return null; }, + }; + + setInterval(WIDGETS.twenties.buzz, move); // buzz to stand / sit +})(); + +// Bangle.drawWidgets(); // <-- for development only \ No newline at end of file diff --git a/apps/widtwenties/widget.png b/apps/widtwenties/widget.png new file mode 100644 index 000000000..7c6b02055 Binary files /dev/null and b/apps/widtwenties/widget.png differ diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index cf66a52eb..13b000228 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -225,6 +225,13 @@ apps.forEach((app,appIdx) => { console.log("====================================================="); ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`, {file:appDirRelative+file.url}); } + // clock app checks + if (app.type=="clock") { + var a = fileContents.indexOf("Bangle.loadWidgets()"); + var b = fileContents.indexOf("Bangle.setUI("); + if (a>=0 && b>=0 && a