diff --git a/apps/backlite/ChangeLog b/apps/backlite/ChangeLog new file mode 100644 index 000000000..d8cc5d85f --- /dev/null +++ b/apps/backlite/ChangeLog @@ -0,0 +1 @@ +0.01: New app! (settings, boot.js). diff --git a/apps/backlite/README.md b/apps/backlite/README.md new file mode 100644 index 000000000..c76f08de7 --- /dev/null +++ b/apps/backlite/README.md @@ -0,0 +1,20 @@ +# BackLite +### This app needs the latest settings app update (v 0.80), to ensure that setting the brightness to `0` does not default to `1`. + +BackLite is an app which greatly conserves battery life by only turning the backlight on when you long press the button from a locked state. + +Modern watches have a dedicated button to turn the backlight on, so as not to waste battery in an already light environment. This app recreates that functionality for the Bangle.js, which only has one button. + +#### Warning: This app overwrites the LCD brightness setting in `Bangle.js LCD settings`. If it is changed, the app will basically lose functionality. It auto-fixes itself every boot, so if you change the brightness, just reboot :) +# Usage +When you unlock with a press of the button, or any other way you unlock the watch, the backlight will not turn on, as most of the time you are able to read it, due to the transreflective display on the Bangle.js 2. + +If you press and hold the button to unlock the watch (for around half a second), the backlight will turn on for 5 seconds - just enough to see what you need to see. After that, it will turn off again. + +Some apps like `Light Switch Widget` will prevent this app from working properly. +# Settings +`Brightness` - The LCD brightness when unlocked with a long press. +# Creator +RKBoss6 + +TODO: Add a setting for long press time, or light duration diff --git a/apps/backlite/boot.js b/apps/backlite/boot.js new file mode 100644 index 000000000..897b443b1 --- /dev/null +++ b/apps/backlite/boot.js @@ -0,0 +1,36 @@ +{ + let getSettings = function(){ + return Object.assign({ + // default values + brightness: 0.3, + + }, require('Storage').readJSON("BackLite.settings.json", true) || {}); + }; + + + //Set LCD to zero every reboot + let s = require("Storage").readJSON("setting.json", 1) || {}; + s.brightness = 0; + if (!("lcdTimeout" in s)) s.lcdTimeout = 5; // fallback so logic doesn't break + require("Storage").writeJSON("setting.json", s); + + const longPressTime=400; //(ms) + + Bangle.on('lock', function(isLocked) { + Bangle.setLCDBrightness(0); + + if (!isLocked) { + // Just unlocked — give a short delay and check if BTN1 is still pressed + setTimeout(() => { + if (digitalRead(BTN1)) { + //set brightness until. locked. + Bangle.setLCDBrightness(getSettings().brightness); + } else { + Bangle.setLCDBrightness(0); + } + }, longPressTime); // Slight delay to allow unlock to settle + } + }); + +} + diff --git a/apps/backlite/icon.png b/apps/backlite/icon.png new file mode 100644 index 000000000..1337ef8ac Binary files /dev/null and b/apps/backlite/icon.png differ diff --git a/apps/backlite/metadata.json b/apps/backlite/metadata.json new file mode 100644 index 000000000..65d5c7c02 --- /dev/null +++ b/apps/backlite/metadata.json @@ -0,0 +1,17 @@ +{ + "id": "backlite", + "name": "BackLite", + "version": "0.01", + "description": "Conserves battery life by turning the backlight on only on a long press of the button from a locked state. **Requires the latest settings update (v0.80)**", + "icon": "icon.png", + "type": "bootloader", + "tags": "system", + "readme": "README.md", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"backlite.boot.js","url":"boot.js"}, + {"name":"backlite.settings.js","url":"settings.js"} + + ], + "data": [{"name":"BackLite.settings.json"}] +} diff --git a/apps/backlite/settings.js b/apps/backlite/settings.js new file mode 100644 index 000000000..7ce709b61 --- /dev/null +++ b/apps/backlite/settings.js @@ -0,0 +1,25 @@ +(function(back) { + var FILE = "BackLite.settings.json"; + // Load settings + var settings = Object.assign({ + brightness: 0.3, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "BackLite" }, + 'Brightness': { + value: 0.3|settings.brightness, + min: 0.1, max: 1, + step: 0.1, + onchange: v => { + settings.brightness = v; + writeSettings(); + } + }, + }); +}) diff --git a/apps/clkinfoclk/metadata.json b/apps/clkinfoclk/metadata.json index 8d676d0e0..0160b59d9 100644 --- a/apps/clkinfoclk/metadata.json +++ b/apps/clkinfoclk/metadata.json @@ -5,7 +5,7 @@ "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], "type": "clkinfo", - "tags": "clkinfo", + "tags": "clkinfo,clock", "supports" : ["BANGLEJS2"], "storage": [ {"name":"clkinfoclk.clkinfo.js","url":"clkinfo.js"} diff --git a/apps/clkinfodist/ChangeLog b/apps/clkinfodist/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/clkinfodist/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/clkinfodist/app.png b/apps/clkinfodist/app.png new file mode 100644 index 000000000..84544fa86 Binary files /dev/null and b/apps/clkinfodist/app.png differ diff --git a/apps/clkinfodist/clkinfo.js b/apps/clkinfodist/clkinfo.js new file mode 100644 index 000000000..93cbe9fcd --- /dev/null +++ b/apps/clkinfodist/clkinfo.js @@ -0,0 +1,22 @@ +(function() { + let strideLength = (require("Storage").readJSON("myprofile.json",1)||{}).strideLength ?? 0.79, + lastSteps = 0; + function stepUpdateHandler() { distance.emit("redraw"); } + var distance = { + name : "Distance", + get : () => { let v = (Bangle.getHealthStatus("day").steps - lastSteps)*strideLength; return { + text : require("locale").distance(v,1), + img : atob("GBiBAAMAAAeAAA/AAA/AAA/gAA/gwAfh4AfD4APD4AOH4AAH4ADj4AHjwAHhwADgAAACAAAHgAAPAAAHAAgCEBgAGD///BgAGAgAEA==") + };}, + run : function() { + lastSteps = (lastSteps>=Bangle.getHealthStatus("day").steps) ? 0 : Bangle.getHealthStatus("day").steps; + this.emit("redraw"); + }, + show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); }, + hide : function() { Bangle.removeListener("step", stepUpdateHandler); } + }; + return { + name: "Bangle", + items: [ distance ] + }; +}) diff --git a/apps/clkinfodist/icon.png b/apps/clkinfodist/icon.png new file mode 100644 index 000000000..70a6d496a Binary files /dev/null and b/apps/clkinfodist/icon.png differ diff --git a/apps/clkinfodist/metadata.json b/apps/clkinfodist/metadata.json new file mode 100644 index 000000000..2a60547dd --- /dev/null +++ b/apps/clkinfodist/metadata.json @@ -0,0 +1,14 @@ +{ "id": "clkinfodist", + "name": "Clockinfo Distance", + "version":"0.01", + "description": "Uses the 'My Profile' app's Stride Length to calculate distance travelled based on step count. Tap to reset for measuring distances.", + "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], + "type": "clkinfo", + "tags": "clkinfo,distance,steps,outdoors,tool", + "dependencies": {"myprofile":"app"}, + "supports" : ["BANGLEJS2"], + "storage": [ + {"name":"clkinfodist.clkinfo.js","url":"clkinfo.js"} + ] +} diff --git a/apps/clkinfodist/screenshot.png b/apps/clkinfodist/screenshot.png new file mode 100644 index 000000000..9463e4064 Binary files /dev/null and b/apps/clkinfodist/screenshot.png differ diff --git a/apps/clock_info/ChangeLog b/apps/clock_info/ChangeLog index 8915a702e..702d78bf1 100644 --- a/apps/clock_info/ChangeLog +++ b/apps/clock_info/ChangeLog @@ -17,3 +17,5 @@ 0.16: Add BLE clkinfo entry 0.17: Fix BLE icon alignment and border on some clocks 0.18: Tweak BLE icon to add gap and ensure middle of B isn't filled +0.19: Fix Altitude ClockInfo after BLE added + Tapping Altitude now updates the reading \ No newline at end of file diff --git a/apps/clock_info/lib.js b/apps/clock_info/lib.js index 756a0463b..8f8f8c230 100644 --- a/apps/clock_info/lib.js +++ b/apps/clock_info/lib.js @@ -39,22 +39,23 @@ exports.load = function() { var hrm = 0; var alt = "--"; // callbacks (needed for easy removal of listeners) - function batteryUpdateHandler() { bangleItems[0].emit("redraw"); } - function stepUpdateHandler() { bangleItems[1].emit("redraw"); } + function batteryUpdateHandler() { bangleItems.find(i=>i.name=="Battery").emit("redraw"); } + function stepUpdateHandler() { bangleItems.find(i=>i.name=="Steps").emit("redraw"); } function hrmUpdateHandler(e) { if (e && e.confidence>60) hrm = Math.round(e.bpm); - bangleItems[2].emit("redraw"); + bangleItems.find(i=>i.name=="HRM").emit("redraw"); } function altUpdateHandler() { try { Bangle.getPressure().then(data=>{ if (!data) return; alt = Math.round(data.altitude) + "m"; - bangleItems[3].emit("redraw"); + bangleItems.find(i=>i.name=="Altitude").emit("redraw"); }); } catch (e) { print("Caught "+e+"\n in function altUpdateHandler in module clock_info"); - bangleItems[3].emit('redraw');} + bangleItems.find(i=>i.name=="Altitude").emit('redraw'); + } } // actual menu var menu = [{ @@ -120,7 +121,6 @@ exports.load = function() { }, }, { name: "BLE", - hasRange: false, isOn: () => { const s = NRF.getSecurityStatus(); return s.advertising || s.connected; @@ -156,6 +156,7 @@ exports.load = function() { min : 0, max : settings.maxAltitude, img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") }), + run : function() { alt = "--"; this.emit("redraw"); altUpdateHandler(); }, show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); }, hide : function() { clearInterval(this.interval); delete this.interval; }, }); diff --git a/apps/clock_info/metadata.json b/apps/clock_info/metadata.json index fcdaef2d0..4123fbac4 100644 --- a/apps/clock_info/metadata.json +++ b/apps/clock_info/metadata.json @@ -1,7 +1,7 @@ { "id": "clock_info", "name": "Clock Info Module", "shortName": "Clock Info", - "version":"0.18", + "version":"0.19", "description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)", "icon": "app.png", "type": "module", diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 167ee26df..99e2490c9 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -38,4 +38,5 @@ 0.33: Ensure readAllRecordsSince always includes the current day Speed improvements (put temporary functions in RAM where possible) 0.34: Fix readFullDatabase (was skipping first month of data) -0.35: Update boot/lib.min.js \ No newline at end of file +0.35: Update boot/lib.min.js +0.36: Fix Distance graphs that used '1*' to remove the suffix \ No newline at end of file diff --git a/apps/health/app.js b/apps/health/app.js index 6a461da2b..d3dda2e2f 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -32,7 +32,7 @@ function menuStepCount() { } function menuDistance() { - const distMult = 1*require("locale").distance(myprofile.strideLength, 2); // hackish: this removes the distance suffix, e.g. 'm' + const distMult = parseFloat(require("locale").distance(myprofile.strideLength, 2)); // this removes the distance suffix, e.g. 'm' E.showMenu({ "": { title:/*LANG*/"Distance" }, /*LANG*/"< Back": () => menuStepCount(), diff --git a/apps/health/metadata.json b/apps/health/metadata.json index b2ecb3e8b..36497c5ba 100644 --- a/apps/health/metadata.json +++ b/apps/health/metadata.json @@ -2,7 +2,7 @@ "id": "health", "name": "Health Tracking", "shortName": "Health", - "version": "0.35", + "version": "0.36", "description": "Logs health data and provides an app to view it", "icon": "app.png", "screenshots" : [ { "url":"screenshot.png" } ], diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index d3c67401d..7dcfec866 100644 --- a/apps/messagegui/app.js +++ b/apps/messagegui/app.js @@ -14,7 +14,7 @@ // a message require("messages").pushMessage({"t":"add","id":1575479849,"src":"WhatsApp","title":"My Friend","body":"Hey! How's everything going?",reply:1,negative:1}) -require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1}) +require("messages").pushMessage({"t":"add","id":1575479850,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1}) require("messages").pushMessage({"t":"add","id":23232,"src":"Skype","title":"Mr. Bobby McBobFace","body":"Boopedy-boop",positive:1,negative:1}) require("messages").pushMessage({"t":"add","id":23233,"src":"Skype","title":"Thyttan test","body":"Nummerplåtsbelysning trodo",positive:1,negative:1}) require("messages").pushMessage({"t":"add","id":23234,"src":"Skype","title":"Thyttan test 2","body":"Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo",positive:1,negative:1}) diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 03ee8db12..dafaef48c 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -88,3 +88,4 @@ of 'Select Clock' 0.77: Save altitude calibration when user exits via reset 0.78: Fix menu scroll restore on BangleJS1 0.79: Ensure that tapping on pressure/altitude doesn't cause a menu to display temporarily +0.80: Add option to set LCD brightness to 0, default brightness is now 0 as well. diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index e255a0dcb..c7ba12e09 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.79", + "version": "0.80", "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 45b971e32..777131b5e 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -1,3 +1,4 @@ + Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -474,11 +475,11 @@ function LCDMenu() { Object.assign(lcdMenu, { /*LANG*/'LCD Brightness': { value: settings.brightness, - min: 0.1, + min : BANGLEJS2 ? 0 : 0.1, max: 1, step: 0.1, onchange: v => { - settings.brightness = v || 1; + settings.brightness = v ?? 1; updateSettings(); Bangle.setLCDBrightness(settings.brightness); } @@ -1077,4 +1078,4 @@ function showAltitude() { } // Show the main menu -pushMenu(mainMenu()); \ No newline at end of file +pushMenu(mainMenu()); diff --git a/apps/storageanalyzer/ChangeLog b/apps/storageanalyzer/ChangeLog index 2286a7f70..d2dcee110 100644 --- a/apps/storageanalyzer/ChangeLog +++ b/apps/storageanalyzer/ChangeLog @@ -1 +1,3 @@ -0.01: New App! \ No newline at end of file +0.01: New App! +0.02: Added pie chart for visualization, tweaked UI. +0.03: Fixed bug with total storage pie chart. diff --git a/apps/storageanalyzer/app.png b/apps/storageanalyzer/app.png deleted file mode 100644 index f204a5643..000000000 Binary files a/apps/storageanalyzer/app.png and /dev/null differ diff --git a/apps/storageanalyzer/custom.html b/apps/storageanalyzer/custom.html index 4b6c3879e..2adefd29a 100644 --- a/apps/storageanalyzer/custom.html +++ b/apps/storageanalyzer/custom.html @@ -3,67 +3,152 @@ - -
+ + + +
+ + +
+ + +
+ + + diff --git a/apps/storageanalyzer/icon.png b/apps/storageanalyzer/icon.png new file mode 100644 index 000000000..c752a077f Binary files /dev/null and b/apps/storageanalyzer/icon.png differ diff --git a/apps/storageanalyzer/metadata.json b/apps/storageanalyzer/metadata.json index 193b7c32c..902f93b06 100644 --- a/apps/storageanalyzer/metadata.json +++ b/apps/storageanalyzer/metadata.json @@ -1,9 +1,9 @@ { "id": "storageanalyzer", "name": "Storage Analyzer", - "version": "0.01", - "description": "Analyses Bangle.js storage and shows which apps are using it", - "icon": "app.png", + "version": "0.03", + "description": "Analyzes Bangle.js storage and shows which apps are using storage space", + "icon": "icon.png", "type": "RAM", "tags": "tool,storage,flash,memory", "supports": ["BANGLEJS","BANGLEJS2"], diff --git a/apps/taglaunch/ChangeLog b/apps/taglaunch/ChangeLog index e6bec3b85..6574fcb4d 100644 --- a/apps/taglaunch/ChangeLog +++ b/apps/taglaunch/ChangeLog @@ -6,3 +6,4 @@ 0.05: Make the "App source not found" warning less buggy 0.06: Fixed a crash if an app has no tags (app.tags is undefined) 0.07: Clear cached app list when updating showClocks setting +0.08: Add haptic feedback option when selecting app or category in menu, increase vector size limit in settings diff --git a/apps/taglaunch/README.md b/apps/taglaunch/README.md index 89540c514..cc32f54a1 100644 --- a/apps/taglaunch/README.md +++ b/apps/taglaunch/README.md @@ -11,6 +11,7 @@ Settings - `Font` - The font used (`4x6`, `6x8`, `12x20`, `6x15` or `Vector`). Default `12x20`. - `Vector Font Size` - The size of the font if `Font` is set to `Vector`. Default `10`. +- `Haptic Feedback` - Whether or not to vibrate slightly when selecting an app or category in the launcher. Default `No`. - `Show Clocks` - If set to `No` then clocks won't appear in the app list. Default `Yes`. - `Fullscreen` - If set to `Yes` then widgets won't be loaded. Default `No`. @@ -28,3 +29,4 @@ Contributors - [atjn](https://github.com/atjn) - [BlueFox4](https://github.com/BlueFox4) +- [RKBoss6](https://github.com/RKBoss6) diff --git a/apps/taglaunch/app.js b/apps/taglaunch/app.js index 9318973c5..3e322b5ba 100644 --- a/apps/taglaunch/app.js +++ b/apps/taglaunch/app.js @@ -17,7 +17,8 @@ let vectorval = 20; let font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; let settings = Object.assign({ showClocks: true, - fullscreen: false + fullscreen: false, + buzz:false }, s.readJSON("taglaunch.json", true) || {}); if ("vectorsize" in settings) vectorval = parseInt(settings.vectorsize); @@ -108,15 +109,25 @@ let showTagMenu = (tag) => { } }, select : i => { - let app = appsByTag[tag][i]; - if (!app) return; - if (!app.src || require("Storage").read(app.src)===undefined) { - Bangle.setUI(); - E.showMessage(/*LANG*/"App Source\nNot found"); - setTimeout(showMainMenu, 2000); - } else { - load(app.src); + const loadApp = () => { + let app = appsByTag[tag][i]; + if (!app) return; + if (!app.src || require("Storage").read(app.src)===undefined) { + Bangle.setUI(); + E.showMessage(/*LANG*/"App Source\nNot found"); + setTimeout(showMainMenu, 2000); + } else { + load(app.src); + } + }; + if(settings.buzz){ + Bangle.buzz(25); + //let the buzz have effect + setTimeout(loadApp,27); + }else{ + loadApp(); } + }, back : showMainMenu, remove: unload @@ -138,6 +149,7 @@ let showMainMenu = () => { } }, select : i => { + if(settings.buzz)Bangle.buzz(25); let tag = tagKeys[i]; showTagMenu(tag); }, diff --git a/apps/taglaunch/metadata.json b/apps/taglaunch/metadata.json index d366d7ead..f8ead20f9 100644 --- a/apps/taglaunch/metadata.json +++ b/apps/taglaunch/metadata.json @@ -2,8 +2,8 @@ "id": "taglaunch", "name": "Tag Launcher", "shortName": "Taglauncher", - "version": "0.07", - "description": "Launcher that puts all applications into submenus based on their tag. With many applications installed this can result in a faster application selection than the linear access of the default launcher.", + "version": "0.08", + "description": "Launcher that puts all applications into submenus based on their tag. With many applications installed this can result in a faster application selection than the linear access from the default launcher.", "readme": "README.md", "icon": "app.png", "type": "launch", diff --git a/apps/taglaunch/settings.js b/apps/taglaunch/settings.js index 64045d37c..09290cf24 100644 --- a/apps/taglaunch/settings.js +++ b/apps/taglaunch/settings.js @@ -2,7 +2,8 @@ (function(back) { let settings = Object.assign({ showClocks: true, - fullscreen: false + fullscreen: false, + buzz:false }, require("Storage").readJSON("taglaunch.json", true) || {}); let fonts = g.getFonts(); @@ -21,9 +22,16 @@ }, /*LANG*/"Vector Font Size": { value: settings.vectorsize || 10, - min:10, max: 20,step:1,wrap:true, + min:10, max: 25,step:1,wrap:true, onchange: (m) => {save("vectorsize", m)} }, + /*LANG*/"Haptic Feedback": { + value: settings.buzz == true, + onchange: (m) => { + save("buzz", m); + + } + }, /*LANG*/"Show Clocks": { value: settings.showClocks == true, onchange: (m) => { diff --git a/apps/teatimer/ChangeLog b/apps/teatimer/ChangeLog index fcdcda875..cf68c1d69 100644 --- a/apps/teatimer/ChangeLog +++ b/apps/teatimer/ChangeLog @@ -4,3 +4,4 @@ 0.04: Get time zone from settings for showing the clock 0.05: Minor code improvements 0.06: Adjust format of title, save counter before leaving help screen +0.07: Refactor code, fix stuttering timer, add settings menu diff --git a/apps/teatimer/README.md b/apps/teatimer/README.md index b7ece6022..b7c628420 100644 --- a/apps/teatimer/README.md +++ b/apps/teatimer/README.md @@ -3,7 +3,7 @@ A simple timer. You can easily set up the time. The initial time is 2:30 On the first screen, you can -- tap to get help +- double tap to get help - swipe up/down to change the timer by +/- one minute - swipe left/right to change the time by +/- 15 seconds - press Btn1 to start @@ -12,24 +12,31 @@ Press Btn1 again to stop the timer - when time is up, your Bangle will buzz for 15 seconds - and it will count up to 60 seconds and stop after that -## Images -_1. Startscreen_ +The time changes can be adjusted in the settings menu. -![](TeatimerStart.jpg) +## Images +_1. Start screen_ + +![](TeatimerStart.png) Current time is displayed below the Title. Initial time is 2:30. _2. Help Screen_ -![](TeatimerHelp.jpg) +![](TeatimerHelp.png) _3. Tea Timer running_ -![](TeatimerRun.jpg) -Remainig time is shown in big font size. Above the initial time is shown. +![](TeatimerRun.png) +Remainig time is shown in big font size. -_4. When time is up_ +_4. Pause Timer -![](TeatimerUp.jpg) +![](TeatimerPause.png) +While the timer is running, you can pause and unpause it by pressing BTN1. + +_5. When time is up_ + +![](TeatimerUp.png) When time is up, the watch will buzz for 15 seconds. It will count up to 60 seconds. ## Requests diff --git a/apps/teatimer/TeatimerHelp.jpg b/apps/teatimer/TeatimerHelp.jpg deleted file mode 100644 index e22960c66..000000000 Binary files a/apps/teatimer/TeatimerHelp.jpg and /dev/null differ diff --git a/apps/teatimer/TeatimerHelp.png b/apps/teatimer/TeatimerHelp.png new file mode 100644 index 000000000..f26e290fb Binary files /dev/null and b/apps/teatimer/TeatimerHelp.png differ diff --git a/apps/teatimer/TeatimerPause.png b/apps/teatimer/TeatimerPause.png new file mode 100644 index 000000000..bb6738e54 Binary files /dev/null and b/apps/teatimer/TeatimerPause.png differ diff --git a/apps/teatimer/TeatimerRun.jpg b/apps/teatimer/TeatimerRun.jpg deleted file mode 100644 index a442d12a5..000000000 Binary files a/apps/teatimer/TeatimerRun.jpg and /dev/null differ diff --git a/apps/teatimer/TeatimerRun.png b/apps/teatimer/TeatimerRun.png new file mode 100644 index 000000000..0ef0832ce Binary files /dev/null and b/apps/teatimer/TeatimerRun.png differ diff --git a/apps/teatimer/TeatimerStart.jpg b/apps/teatimer/TeatimerStart.jpg deleted file mode 100644 index 4fa8f2fc4..000000000 Binary files a/apps/teatimer/TeatimerStart.jpg and /dev/null differ diff --git a/apps/teatimer/TeatimerStart.png b/apps/teatimer/TeatimerStart.png new file mode 100644 index 000000000..2793c9bae Binary files /dev/null and b/apps/teatimer/TeatimerStart.png differ diff --git a/apps/teatimer/TeatimerUp.jpg b/apps/teatimer/TeatimerUp.jpg deleted file mode 100644 index 80b8c3c8a..000000000 Binary files a/apps/teatimer/TeatimerUp.jpg and /dev/null differ diff --git a/apps/teatimer/TeatimerUp.png b/apps/teatimer/TeatimerUp.png new file mode 100644 index 000000000..f16ce059e Binary files /dev/null and b/apps/teatimer/TeatimerUp.png differ diff --git a/apps/teatimer/app.js b/apps/teatimer/app.js index a22000342..7e2e3dbb7 100644 --- a/apps/teatimer/app.js +++ b/apps/teatimer/app.js @@ -1,237 +1,217 @@ -// Tea Timer -// Button press stops timer, next press restarts timer -let drag; -var counter = 0; -var counterStart = 150; // 150 seconds -var counterInterval; -const states = { - init: 1, // unused - help: 2, // show help text - start: 4, // show/change initial counter - count: 8, // count down - countUp: 16, // count up after timer finished - stop: 32 // timer stopped +const FILE = "teatimer.json"; +const DEFAULTS = { + timerDuration: 150, + bigJump: 60, + smallJump: 15, + finishBuzzDuration: 1500, + overtimeBuzzDuration: 100, + overtimeBuzzLimit: 60, + overtimeBuzzSeconds: 15 }; -var state = states.start; -let setting = require("Storage").readJSON("setting.json",1); -E.setTimeZone(setting.timezone); -// Title showing current time -function appTitle() { - return "Tea Timer\n" + currentTime(); +// Enum for states +const STATES = { + INIT: "init", + RUNNING: "running", + PAUSED: "paused", + FINISHED: "finished", + OVERTIME: "overtime" +}; + +let savedSettings = require("Storage").readJSON(FILE, 1) || {}; +let settings = Object.assign({}, DEFAULTS, savedSettings); + +let state = STATES.INIT; +let showHelp = false; + +let startTime = 0; +let remaining = settings.timerDuration; +let target = 0; + +let drag = null; +let dragAdjusted = false; +let lastTapTime = 0; + +// === Helpers === +function formatTime(s) { + let m = Math.floor(s / 60); + let sec = (s % 60).toString().padStart(2, '0'); + return `${m}:${sec}`; } -function currentTime() { - let min = Date().getMinutes(); - if (min < 10) min = "0" + min; - return Date().getHours() + ":" + min; +function getTimeStr() { + let d = new Date(); + return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`; } -function timeFormated(sec) { - let min = Math.floor(sec / 60); - sec = sec % 60; - if (sec < 10) sec = "0" + sec; - return min + ":" + sec; +function isState(s) { + return state === s; } -// initialize timer and show timer value => state: start -function initTimer() { - counter = counterStart; - setState(states.start); - showCounter(true); +function setState(s) { + state = s; } -// timer value (counter) can be changed in state start -function changeCounter(diff) { - if (state == states.start) { - if (counter + diff > 0) { - counter = counter + diff; - showCounter(true); - } +// === UI Drawing === +function drawUI() { + g.reset(); + g.setBgColor(g.theme.bg).clear(); + g.setColor(g.theme.fg); + let cx = g.getWidth() / 2; + + // Time (top right) + g.setFont("6x8", 2); + g.setFontAlign(1, 0); + g.drawString(getTimeStr(), g.getWidth() - 4, 10); + + // Help text + if (showHelp) { + g.setFontAlign(0, 0); + g.setFont("Vector", 15); + g.drawString( + `Swipe up/down: ±${settings.bigJump}s\nSwipe left/right: ±${settings.smallJump}s\n\nBTN1: Start/Pause\nDouble Tap: Hide Help`, + cx, 80 + ); + return; + } + + // Title + g.setFont("Vector", 20); + g.setFontAlign(0, 0); + let label = (isState(STATES.OVERTIME)) ? "Time's Up!" : "Tea Timer"; + g.drawString(label, cx, 40); + + // Time remaining / overtime + g.setFont("Vector", 60); + g.setColor(isState(STATES.OVERTIME) ? "#f00" : g.theme.fg); + g.drawString(formatTime(remaining), cx, 100); + + // Bottom state text + g.setFontAlign(0, 0); + if (isState(STATES.PAUSED)) { + g.setFont("6x8", 2); + g.drawString("paused", cx, g.getHeight() - 20); + } else if (!isState(STATES.RUNNING) && !isState(STATES.OVERTIME)) { + g.setFont("Vector", 13); + g.drawString("double tap for help", cx, g.getHeight() - 20); } } -// start or restart timer => state: count +// === Timer Logic === function startTimer() { - counterStart = counter; - setState(states.count); - countDown(); - if (!counterInterval) - counterInterval = setInterval(countDown, 1000); + setState(STATES.RUNNING); + startTime = Date.now(); + target = startTime + remaining * 1000; } -/* show current counter value at start and while count down - Show - - Title with current time - - initial timer value - - remaining time - - hint for help in state start -*/ -function showCounter(withHint) { - g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier - E.showMessage("", appTitle()); - g.reset().setFontAlign(0,0); // center font - // draw the current counter value - g.setBgColor(-1).setColor(0,0,1); // blue - g.setFont("Vector",20); // vector font, 20px - g.drawString("Timer: " + timeFormated(counterStart),80,55); - g.setFont("Vector",60); // vector font, 60px - g.drawString(timeFormated(counter),83,100); - if (withHint) { - g.setFont("Vector",20); // vector font, 80px - g.drawString("Tap for help",80,150); +function pauseTimer() { + if (isState(STATES.RUNNING)) { + remaining = Math.max(0, Math.ceil((target - Date.now()) / 1000)); + setState(STATES.PAUSED); } } -// count down and update every second -// when time is up, start counting up -function countDown() { - counter--; - // Out of time - if (counter<=0) { - outOfTime(); - countUp(); - counterInterval = setInterval(countUp, 1000); - return; +function resumeTimer() { + if (isState(STATES.PAUSED)) { + startTime = Date.now(); + target = startTime + remaining * 1000; + setState(STATES.RUNNING); } - showCounter(false); } -// -function outOfTime() { - E.showMessage("Time is up!",appTitle()); - setState(states.countUp); - resetTimer(); - Bangle.buzz(); - Bangle.buzz(); -} - -/* this counts up (one minute), after time is up - Show - - Title with current time - - initial timer value - - "Time is up!" - - time since timer finished -*/ -function countUp() { - // buzz for 15 seconds - counter++; - if (counter <=15) { - Bangle.buzz(); - } - // stop counting up after 60 seconds - if (counter > 60) { - outOfTime(); - return; - } - g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier - E.showMessage("", appTitle()); - g.reset().setFontAlign(0,0); // center font - g.setBgColor(-1).setColor(0,0,1); // blue - g.setFont("Vector",20); // vector font, 20px - g.drawString("Timer: " + timeFormated(counterStart),80,55); - g.setFont("Vector",30); // vector font, 80px - g.setBgColor(-1).setColor(1,0,0); // red - g.drawString("Time is up!",85,85); - g.setFont("Vector",40); // vector font, 80px - // draw the current counter value - g.drawString(timeFormated(counter),80,130); -} - -// reset when interupted by user oder 60 seconds after timer finished function resetTimer() { - clearInterval(); - counterInterval = undefined; + setState(STATES.INIT); + remaining = settings.timerDuration; } -// timer is stopped by user => state: stop -function stopTimer() { - resetTimer(); - E.showMessage("Timer stopped!", appTitle()); - setState(states.stop); -} - -// timer is stopped by user while counting up => state: start -function stopTimer2() { - resetTimer(); - initTimer(); -} - - -function setState(st) { - state = st; -} - -function buttonPressed() { - switch(state) { - case states.init: - initTimer(); - break; - case states.help: - initTimer(); - break; - case states.start: - startTimer(); - break; - case states.count: - stopTimer(); - break; - case states.countUp: - stopTimer2(); - break; - case states.stop: - initTimer(); - break; - default: - initTimer(); - break; - } -} - -/* Change initial counter value by swiping - swipe up: +1 minute - swipe down: -1 minute - swipe right: +15 seconds - swipe left: -15 seconds */ -function initDragEvents() { - Bangle.on("drag", e => { - if (state == states.start) { - if (!drag) { // start dragging - drag = {x: e.x, y: e.y}; - } else if (!e.b) { // released - const dx = e.x-drag.x, dy = e.y-drag.y; - drag = null; - if (Math.abs(dx)>Math.abs(dy)+10) { - // horizontal - changeCounter(dx>0 ? 15 : -15); - } else if (Math.abs(dy)>Math.abs(dx)+10) { - // vertical - changeCounter(dy>0 ? -60 : 60); - } +function tick() { + if (isState(STATES.RUNNING)) { + remaining -= 1; + if (remaining <= 0) { + remaining = 0; + setState(STATES.OVERTIME); + startTime = Date.now(); + remaining = 0; // Start overtime count-up from 0 + Bangle.buzz(settings.finishBuzzDuration); + } + } else if (isState(STATES.OVERTIME)) { + remaining += 1; + if (remaining <= settings.overtimeBuzzSeconds) { + Bangle.buzz(settings.overtimeBuzzDuration, 0.3); + } + if (remaining >= settings.overtimeBuzzLimit) { + resetTimer(); // Stop overtime after max duration } } -}); + drawUI(); } -// show help text while in start state (see initDragEvents()) -function showHelp() { - if (state == states.start) { - state = states.help; - g.setBgColor(g.theme.bg); - g.setColor(g.theme.fg); - E.showMessage("Swipe up/down\n+/- one minute\n\nSwipe left/right\n+/- 15 seconds\n\nPress Btn1 to start","Tea timer help"); +// === UI Controls === +function toggleTimer() { + if (showHelp) { + showHelp = false; + } else if (isState(STATES.OVERTIME)) { + resetTimer(); + } else if (isState(STATES.INIT)) { + startTimer(); + } else if (isState(STATES.PAUSED)) { + resumeTimer(); + } else if (isState(STATES.RUNNING)) { + pauseTimer(); } - // return to start - else if (state == states.help) { - counterStart = counter; - initTimer(); + + drawUI(); +} + +function handleDoubleTap() { + if (isState(STATES.INIT)) { + let now = Date.now(); + if (now - lastTapTime < 400) { + showHelp = !showHelp; + drawUI(); + } + lastTapTime = now; } } -// drag events in start state (to change counter value) -initDragEvents(); -// Show help test in start state -Bangle.on('touch', function(button, xy) { showHelp(); }); -// event handling for button1 -setWatch(buttonPressed, BTN1, {repeat: true}); -initTimer(); \ No newline at end of file +function adjustTimer(diff) { + if (isState(STATES.INIT)) { + remaining = Math.max(5, remaining + diff); + settings.timerDuration = remaining; + drawUI(); + } +} + +function handleDrag(e) { + if (isState(STATES.INIT) && !showHelp) { + if (e.b) { + if (!drag) { + drag = { x: e.x, y: e.y }; + dragAdjusted = false; + } else if (!dragAdjusted) { + let dx = e.x - drag.x; + let dy = e.y - drag.y; + + if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > settings.smallJump) { + adjustTimer(dx > 0 ? settings.smallJump : -settings.smallJump); + dragAdjusted = true; + } else if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > settings.bigJump) { + adjustTimer(dy > 0 ? -settings.bigJump : settings.bigJump); + dragAdjusted = true; + } + } + } else { + drag = null; + dragAdjusted = false; + } + } +} + +// === Init App === +setWatch(toggleTimer, BTN1, { repeat: true }); +Bangle.on("drag", handleDrag); +Bangle.on("touch", handleDoubleTap); + +resetTimer(); +drawUI(); +setInterval(tick, 1000); diff --git a/apps/teatimer/metadata.json b/apps/teatimer/metadata.json index 4d4ddd9e4..571053077 100644 --- a/apps/teatimer/metadata.json +++ b/apps/teatimer/metadata.json @@ -1,21 +1,26 @@ { "id": "teatimer", "name": "Tea Timer", - "version": "0.06", + "version": "0.07", "description": "A simple timer. You can easily set up the time.", "icon": "teatimer.png", "type": "app", "tags": "tool", "supports": ["BANGLEJS2"], "readme": "README.md", + "data": [ + { "name": "teatimer.json" } + ], "storage": [ {"name":"teatimer.app.js","url":"app.js"}, + {"name": "teatimer.settings.js", "url": "settings.js" }, {"name":"teatimer.img","url":"app-icon.js","evaluate":true} ], "screenshots": [ - {"url":"TeatimerStart.jpg"}, - {"url":"TeatimerHelp.jpg"}, - {"url":"TeatimerRun.jpg"}, - {"url":"TeatimerUp.jpg"} + {"url":"TeatimerStart.png"}, + {"url":"TeatimerHelp.png"}, + {"url":"TeatimerRun.png"}, + {"url":"TeatimerPause.png"}, + {"url":"TeatimerUp.png"} ] } diff --git a/apps/teatimer/settings.js b/apps/teatimer/settings.js new file mode 100644 index 000000000..8ed780344 --- /dev/null +++ b/apps/teatimer/settings.js @@ -0,0 +1,47 @@ +(function(back) { + const FILE = "teatimer.json"; + const DEFAULTS = { + timerDuration: 150, // Initial timer duration in seconds + bigJump: 60, // Jump for vertical swipes + smallJump: 15 // Jump for horizontal swipes + }; + + let settings = require("Storage").readJSON(FILE, 1) || DEFAULTS; + + function saveSettings() { + require("Storage").writeJSON(FILE, settings); + } + + function showSettingsMenu() { + E.showMenu({ + '': { title: 'Tea Timer Settings' }, + '< Back': back, + 'Default Duration (sec)': { + value: settings.timerDuration, + min: 5, max: 900, step: 5, + onchange: v => { + settings.timerDuration = v; + saveSettings(); + } + }, + 'Swipe Up/Down (sec)': { + value: settings.bigJump, + min: 5, max: 300, step: 5, + onchange: v => { + settings.bigJump = v; + saveSettings(); + } + }, + 'Swipe Left/Right (sec)': { + value: settings.smallJump, + min: 5, max: 60, step: 5, + onchange: v => { + settings.smallJump = v; + saveSettings(); + } + } + }); + } + + showSettingsMenu(); +})