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/ChangeLog b/apps/messagegui/ChangeLog index 321c07be0..d849e9b6f 100644 --- a/apps/messagegui/ChangeLog +++ b/apps/messagegui/ChangeLog @@ -117,3 +117,4 @@ Remove workaround for 2v10 (>3 years ago) - assume everyone is on never firmware now 0.86: Default to showing message scroller (with title, bigger icon) 0.87: Make choosing of font size more repeatable +0.88: Adjust padding calculation so messages are spaced out properly even when using international fonts \ No newline at end of file diff --git a/apps/messagegui/app.js b/apps/messagegui/app.js index d47a43d65..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}) @@ -391,8 +391,6 @@ function showMessage(msgid, persist) { } } lines = g.setFont(bodyFont).wrapString(body, w); - if (lines.length<3) - lines.unshift(""); // if less lines, pad them out a bit at the top! } let negHandler,posHandler,rowLeftDraw,rowRightDraw; if (msg.negative) { @@ -432,12 +430,14 @@ function showMessage(msgid, persist) { } let fontHeight = g.setFont(bodyFont).getFontHeight(); let lineHeight = (fontHeight>25)?fontHeight:25; - if (title.includes("\n")) lineHeight=25; // ensure enough room for 2 lines of title in header + if (title.includes("\n") && lineHeight<25) lineHeight=25; // ensure enough room for 2 lines of title in header let linesPerRow = 2; if (fontHeight<17) { lineHeight = 16; linesPerRow = 3; } + if ((lines.length+4.5)*lineHeight < Bangle.appRect.h) + lines.unshift(""); // if less lines, pad them out a bit at the top! let rowHeight = lineHeight*linesPerRow; let textLineOffset = -(linesPerRow + ((rowLeftDraw||rowRightDraw)?1:0)); let msgIcon = require("messageicons").getImage(msg); diff --git a/apps/messagegui/metadata.json b/apps/messagegui/metadata.json index d2a2704c7..db54edb7e 100644 --- a/apps/messagegui/metadata.json +++ b/apps/messagegui/metadata.json @@ -2,7 +2,7 @@ "id": "messagegui", "name": "Message UI", "shortName": "Messages", - "version": "0.87", + "version": "0.88", "description": "Default app to display notifications from iOS and Gadgetbridge/Android", "icon": "app.png", "type": "app", 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/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(); +})