diff --git a/.gitignore b/.gitignore index 273fdeae4..523dc5f20 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ appdates.csv _config.yml tests/Layout/bin/tmp.* tests/Layout/testresult.bmp -apps.json +apps.local.json \ No newline at end of file diff --git a/apps.json b/apps.json index 822af47f2..537a4f697 100644 --- a/apps.json +++ b/apps.json @@ -7,7 +7,10 @@ # Otherwise nothing has changed. GitHub Pages will automatically # create apps.json as your site is hosted, or if you're hosting # yourself you can run bin/create_apps_json.sh -# +# +# If you serve the store from localhost for development/testing, +# the loader looks for apps.local.json instead, you can run +# `bin/create_apps_json.sh apps.local.json` to create that file. # ================================================================= # Uncomment the following line if you only want explicitly listed diff --git a/apps/timerclk/ChangeLog b/apps/timerclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/timerclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/timerclk/README.md b/apps/timerclk/README.md new file mode 100644 index 000000000..fd6d2b16b --- /dev/null +++ b/apps/timerclk/README.md @@ -0,0 +1,72 @@ +# Timer Clock + +A clock based on the Anton Clock with stopwatches, timers and alarms based on the Stopwatch Touch style and an alarm widget based on the one from Default alarm & timer. + +## Features + +* two slots for stopwatches / timers on the clock screen +* configurable font and size (Anton font has fixed size) +* stopwatch with modifiable start value +* timer that can be paused +* alarms +* multiple stopwatches, timers and alarms +* stopwatches and timers keep running in the background + +## Images + +![](screenshot.png) + +### Stopwatch + +![](screenshot_stopwatch1.png) + +![](screenshot_stopwatch2.png) + +### Settings + +![](screenshot_settings1.png) + +![](screenshot_settings2.png) + +![](screenshot_settings3.png) + +## Controls + +### Bangle.js 1 + +#### Clock + +* Left: Stopwatch +* Right: Timer +* Button 1 / 2: Alarm + +#### Stopwatch / Timer / Alarm + +* Button 1: + * edit mode: increase + * control mode: play / pause +* Button 2: switch between edit / control mode +* Button 3: + * edit mode: decrease + * control mode: reset / remove +* Left: + * edit mode: previous index + * control mode: previous stopwatch / timer / alarm +* Right: + * edit mode: next index + * control mode: next stopwatch / timer / alarm + +### Bangle.js 2 + +#### Clock + +* Swipe left: Stopwatch +* Swipe right: Timer +* Swipe over date: Alarm + +#### Stopwatch / Timer / Alarm + +* Swipe left: previous stopwatch / timer / alarm +* Swipe right: next stopwatch / timer / alarm +* Swipe up: increase index swiped over +* Swipe down: decrease index swiped over \ No newline at end of file diff --git a/apps/timerclk/alarm.alert.js b/apps/timerclk/alarm.alert.js new file mode 100644 index 000000000..f4b61822a --- /dev/null +++ b/apps/timerclk/alarm.alert.js @@ -0,0 +1,57 @@ +if (timerclkAlarmTimeout) clearInterval(timerclkAlarmTimeout); +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "vibrate":10 +}, settings.alarm||{}); + +function showAlarm(alarm) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); + Bangle.setLocked(false); + E.showPrompt("Alarm!",{ + title:"ALARM!", + buttons : {/*LANG*/"Ok":true} + }).then(function(ok) { + buzzCount = 0; + if (ok) { + alarm.last = new Date().getDate(); + } + require("Storage").write("timerclk.alarm.json",JSON.stringify(alarms)); + load(); + }); + function vibrate(counter) { + VIBRATE.write(1); + setTimeout(() => VIBRATE.write(0), 100); + if (--counter) setTimeout(() => vibrate(counter), 250); + } + function buzz() { + if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence + vibrate(4); + if (buzzCount--) + setTimeout(buzz, 3000); + else { // auto-snooze + buzzCount = settings.vibrate; + setTimeout(buzz, 600000); + } + } + var buzzCount = settings.vibrate; + buzz(); +} + +// Check for alarms +console.log("checking for alarms..."); +var alarms = require("Storage").readJSON("timerclk.alarm.json",1)||[]; +var active = alarms.filter(e=>e.on); +if (active.length) { + // if there's an alarm, show it + active = active.sort((a,b)=>(a.time-b.time)+(a.last-b.last)*86400000); + if (active[0].last != new Date().getDate()) { + showAlarm(active[0]); + } else { + setTimeout(load, 100); + } +} else { + // otherwise just go back to default app + setTimeout(load, 100); +} diff --git a/apps/timerclk/alarm.info b/apps/timerclk/alarm.info new file mode 100644 index 000000000..1289f8cef --- /dev/null +++ b/apps/timerclk/alarm.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Alarm","src":"timerclk.alarm.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/alarm.js b/apps/timerclk/alarm.js new file mode 100644 index 000000000..4acaa6cf0 --- /dev/null +++ b/apps/timerclk/alarm.js @@ -0,0 +1,116 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.alarm.json") || []; +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":4, +}, settings = settings.alarm||{}); +var defaultElement = {time:43200000, on:true, last:null}; + +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; +var dragBorderHrsMins=0, dragBorderMinsSecs=0; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, 1000); draw();}, 1000 - (timerclk.getTime(all[current]) % 1000)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function activate() { + all[current].on = !all[current].on; + all[current].last = null; + update(); + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} + +function edit(position, change) { + if (position == 1) all[current].time += change*1000; + else if (position == 2) all[current].time += change*60000; + else if (position == 3) all[current].time += change*3600000; + require("Storage").write("timerclk.alarm.json",JSON.stringify(all)); + timerclkCheckAlarms(); +} + +var buttons = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:"#f50"}, // remove + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: activate, img: timerclk.play_img, col:"#0ff"}, // active +}; + + +function drawButtons() { + if (all[current].on) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + var timeStr = timerclk.formatTime(all[current].time, false, false, true); + g.drawString(timeStr,x,y); + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(":"); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/app-icon.js b/apps/timerclk/app-icon.js new file mode 100644 index 000000000..278cf4bb6 --- /dev/null +++ b/apps/timerclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgP/AFHzvmf+f8z/8tnv/vs9/1t/v+kv94jR/H4n/wn4CBAYPwnEP8AFDg/AAoUwAoPgmABBwfQAonwAo0/4gFC4AFE4gFLmGEAoQDBxgFCwEQAIIFIj4FD/k//hNBAoZZBAoc8j6oS8/P+1NAoP63+7+wMCz/u/YEB/v/v4dI1+pAQIFBx/J/2/AoP5tFJr71eA==")) diff --git a/apps/timerclk/app-icon.png b/apps/timerclk/app-icon.png new file mode 100644 index 000000000..074db4ed7 Binary files /dev/null and b/apps/timerclk/app-icon.png differ diff --git a/apps/timerclk/app.js b/apps/timerclk/app.js new file mode 100644 index 000000000..eeb3ac4cd --- /dev/null +++ b/apps/timerclk/app.js @@ -0,0 +1,151 @@ +Graphics.prototype.setFontAnton = function(scale) { +// Actual height 69 (68 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAA/gAAAAAAAAAAP/gAAAAAAAAAH//gAAAAAAAAB///gAAAAAAAAf///gAAAAAAAP////gAAAAAAD/////gAAAAAA//////gAAAAAP//////gAAAAH///////gAAAB////////gAAAf////////gAAP/////////gAD//////////AA//////////gAA/////////4AAA////////+AAAA////////gAAAA///////wAAAAA//////8AAAAAA//////AAAAAAA/////gAAAAAAA////4AAAAAAAA///+AAAAAAAAA///gAAAAAAAAA//wAAAAAAAAAA/8AAAAAAAAAAA/AAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////AAAAAB///////8AAAAH////////AAAAf////////wAAA/////////4AAB/////////8AAD/////////+AAH//////////AAP//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wA//8AAAAAB//4A//wAAAAAAf/4A//gAAAAAAP/4A//gAAAAAAP/4A//gAAAAAAP/4A//wAAAAAAf/4A///////////4Af//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH//////////AAD/////////+AAB/////////8AAA/////////4AAAP////////gAAAD///////+AAAAAf//////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAP/gAAAAAAAAAAf/gAAAAAAAAAAf/gAAAAAAAAAAf/AAAAAAAAAAA//AAAAAAAAAAA/+AAAAAAAAAAB/8AAAAAAAAAAD//////////gAH//////////gAP//////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/4AAAAB/gAAD//4AAAAf/gAAP//4AAAB//gAA///4AAAH//gAB///4AAAf//gAD///4AAA///gAH///4AAD///gAP///4AAH///gAP///4AAP///gAf///4AAf///gAf///4AB////gAf///4AD////gA////4AH////gA////4Af////gA////4A/////gA//wAAB/////gA//gAAH/////gA//gAAP/////gA//gAA///8//gA//gAD///w//gA//wA////g//gA////////A//gA///////8A//gA///////4A//gAf//////wA//gAf//////gA//gAf/////+AA//gAP/////8AA//gAP/////4AA//gAH/////gAA//gAD/////AAA//gAB////8AAA//gAA////wAAA//gAAP///AAAA//gAAD//8AAAA//gAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/+AAAAAD/wAAB//8AAAAP/wAAB///AAAA//wAAB///wAAB//wAAB///4AAD//wAAB///8AAH//wAAB///+AAP//wAAB///+AAP//wAAB////AAf//wAAB////AAf//wAAB////gAf//wAAB////gA///wAAB////gA///wAAB////gA///w//AAf//wA//4A//AAA//wA//gA//AAAf/wA//gB//gAAf/wA//gB//gAAf/wA//gD//wAA//wA//wH//8AB//wA///////////gA///////////gA///////////gA///////////gAf//////////AAf//////////AAP//////////AAP/////////+AAH/////////8AAH///+/////4AAD///+f////wAAA///8P////gAAAf//4H///+AAAAH//gB///wAAAAAP4AAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/wAAAAAAAAAA//wAAAAAAAAAP//wAAAAAAAAB///wAAAAAAAAf///wAAAAAAAH////wAAAAAAA/////wAAAAAAP/////wAAAAAB//////wAAAAAf//////wAAAAH///////wAAAA////////wAAAP////////wAAA///////H/wAAA//////wH/wAAA/////8AH/wAAA/////AAH/wAAA////gAAH/wAAA///4AAAH/wAAA//+AAAAH/wAAA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gA///////////gAAAAAAAAH/4AAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAA/////+B///AAA/////+B///wAA/////+B///4AA/////+B///8AA/////+B///8AA/////+B///+AA/////+B////AA/////+B////AA/////+B////AA/////+B////gA/////+B////gA/////+B////gA/////+A////gA//gP/gAAB//wA//gf/AAAA//wA//gf/AAAAf/wA//g//AAAAf/wA//g//AAAA//wA//g//gAAA//wA//g//+AAP//wA//g////////gA//g////////gA//g////////gA//g////////gA//g////////AA//gf///////AA//gf//////+AA//gP//////+AA//gH//////8AA//gD//////4AA//gB//////wAA//gA//////AAAAAAAH////8AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////gAAAAB///////+AAAAH////////gAAAf////////4AAB/////////8AAD/////////+AAH//////////AAH//////////gAP//////////gAP//////////gAf//////////wAf//////////wAf//////////wAf//////////wAf//////////4A//wAD/4AAf/4A//gAH/wAAP/4A//gAH/wAAP/4A//gAP/wAAP/4A//gAP/4AAf/4A//wAP/+AD//4A///wP//////4Af//4P//////wAf//4P//////wAf//4P//////wAf//4P//////wAP//4P//////gAP//4H//////gAH//4H//////AAH//4D/////+AAD//4D/////8AAB//4B/////4AAA//4A/////wAAAP/4AP////AAAAB/4AD///4AAAAAAAAAH/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAAAAA//gAAAAAAADgA//gAAAAAAP/gA//gAAAAAH//gA//gAAAAB///gA//gAAAAP///gA//gAAAD////gA//gAAAf////gA//gAAB/////gA//gAAP/////gA//gAB//////gA//gAH//////gA//gA///////gA//gD///////gA//gf///////gA//h////////gA//n////////gA//////////gAA/////////AAAA////////wAAAA///////4AAAAA///////AAAAAA//////4AAAAAA//////AAAAAAA/////4AAAAAAA/////AAAAAAAA////8AAAAAAAA////gAAAAAAAA///+AAAAAAAAA///4AAAAAAAAA///AAAAAAAAAA//4AAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//gB///wAAAAP//4H///+AAAA///8P////gAAB///+f////4AAD///+/////8AAH/////////+AAH//////////AAP//////////gAP//////////gAf//////////gAf//////////wAf//////////wAf//////////wA///////////wA//4D//wAB//4A//wB//gAA//4A//gA//gAAf/4A//gA//AAAf/4A//gA//gAAf/4A//wB//gAA//4A///P//8AH//4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////gAP//////////gAP//////////AAH//////////AAD/////////+AAD///+/////8AAB///8f////wAAAf//4P////AAAAH//wD///8AAAAA/+AAf//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH//gAAAAAAAAB///+AA/+AAAAP////gA//wAAAf////wA//4AAB/////4A//8AAD/////8A//+AAD/////+A///AAH/////+A///AAP//////A///gAP//////A///gAf//////A///wAf//////A///wAf//////A///wAf//////A///wA///////AB//4A//4AD//AAP/4A//gAB//AAP/4A//gAA//AAP/4A//gAA/+AAP/4A//gAB/8AAP/4A//wAB/8AAf/4Af//////////wAf//////////wAf//////////wAf//////////wAf//////////wAP//////////gAP//////////gAH//////////AAH/////////+AAD/////////8AAB/////////4AAAf////////wAAAP////////AAAAB///////4AAAAAD/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/AAB/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAA//AAD/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("EiAnGicnJycnJycnEw=="), 78+(scale<<8)+(1<<16)); +}; + +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "timeFont":"Anton", + "timeFontSize":0, + "dateFont":"6x8", + "dateFontSize":2, + "dowFont":"6x8", + "dowFontSize":2, + "specialFont":"6x8", + "specialFontSize":2, + "shortDate":true, + "showStopwatches":true, + "showTimers":true, +}, settings.clock||{}); + +var stopwatches = [], timers = []; +if (settings.showStopwatches) { + stopwatches = require("Storage").readJSON("timerclk.stopwatch.json") || []; + stopwatches = stopwatches.filter(e=>e.start||e.time); +} +if (settings.showTimers) { + timers = require("Storage").readJSON("timerclk.timer.json") || []; + timers = timers.filter(e=>e.start||e.timeAdd); +} + +// timeout used to update every minute +var drawTimeout; +var drawSpecialTimeout; +// border between time and date/dow +var dragBorder = g.getHeight()/2; + +// schedule a draw for the next minute +function queueDraw(timeout, interval, func) { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(function() { + timeout = undefined; + func(); + }, interval - (Date.now() % interval)); +} + +function drawSpecial() { + var interval = 60000; + var stopwatch = 0, timer = 0, time; + var x = g.getWidth()/4; + g.setColor(g.theme.fg); + g.setFontAlign(0,0).setFont(settings.specialFont, settings.specialFontSize); + var y = Bangle.appRect.y + g.stringMetrics("00:00").height/2; + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y+g.stringMetrics("00:00").height); + + if (stopwatches.length) { + time = timerclk.getTime(stopwatches[stopwatch]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + stopwatch++; + } else if (timers.length > 1) { + time = timers[timer].time - timerclk.getTime(timers[timer]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + timer++; + } + x += g.getWidth()/2; + if (timers.length) { + time = timers[timer].time - timerclk.getTime(timers[timer]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + } else if (stopwatches.length > 1) { + time = timerclk.getTime(stopwatches[stopwatch]); + g.drawString(timerclk.formatTime(time, true), x, y); + if (Math.floor(time/3600000) === 0) interval = 1000; + } + queueDraw(drawSpecialTimeout, interval, drawSpecial); +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + var date = new Date(); + var timeStr = require("locale").time(date,1); + var dateStr = require("locale").date(date,settings.shortDate).toUpperCase(); + var dowStr = require("locale").dow(date).toUpperCase(); + + // draw time + if (settings.timeFont == "Anton") { + g.setFontAlign(0,0).setFont("Anton"); + } else { + g.setFontAlign(0,0).setFont(settings.timeFont, settings.timeFontSize); + } + g.clearRect(Bangle.appRect.x, x-g.stringMetrics(timeStr).height/2, Bangle.appRect.x2, Bangle.appRect.y2); // clear the background + g.drawString(timeStr,x,y); + // draw date + y += g.stringMetrics(timeStr).height/2; + g.setFontAlign(0,0).setFont(settings.dateFont, settings.dateFontSize); + dragBorder = y; + y += g.stringMetrics(dateStr).height/2; + g.drawString(dateStr,x,y); + //draw day of week + y += g.stringMetrics(dateStr).height/2; + g.setFontAlign(0,0).setFont(settings.dowFont, settings.dowFontSize); + y += g.stringMetrics(dowStr).height/2; + g.drawString(dowStr,x,y); + // queue draw in one minute + queueDraw(drawTimeout, 60000, draw); +} + +if (process.env.HWVERSION==1) { + setWatch(()=>load("timerclk.stopwatch.js"), BTN4); + setWatch(()=>load("timerclk.timer.js"), BTN5); + setWatch(()=>load("timerclk.alarm.js"), BTN3); + setWatch(()=>load("timerclk.alarm.js"), BTN1); +} else { + var absY, lastX, lastY; + Bangle.on('drag', e=>{ + if (!e.b) { + if (lastX > 50) { // right + if (absY < dragBorder) { // drag over time + load("timerclk.timer.js"); + }else { // drag over date/dow + load("timerclk.alarm.js"); + } + } else if (lastX < -50) { // left + if (absY < dragBorder) { // drag over time + load("timerclk.stopwatch.js"); + }else { // drag over date/dow + load("timerclk.alarm.js"); + } + } else if (lastY > 50) { // down + } else if (lastY < -50) { // up + } + lastX = 0; + lastY = 0; + } else { + lastX = lastX + e.dx; + lastY = lastY + e.dy; + absY = e.y; + } + }); +} + +Bangle.setUI("clock"); // Show launcher when middle button pressed +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); +if (stopwatches || timers) drawSpecial(); diff --git a/apps/timerclk/boot.js b/apps/timerclk/boot.js new file mode 100644 index 000000000..9a09f68f3 --- /dev/null +++ b/apps/timerclk/boot.js @@ -0,0 +1,48 @@ +var timerclkTimerTimeout; +var timerclkAlarmTimeout; +function timerclkCheckTimers() { + if (timerclkTimerTimeout) clearTimeout(timerclkTimerTimeout); + var timers = require('Storage').readJSON('timerclk.timer.json',1)||[]; + timers = timers.filter(e=>e.start); + if (timers.length) { + timers = timers.sort((a,b)=>{ + var at = a.timeAdd; + if (a.start) at += Date.now()-a.start; + at = a.period-at; + var bt = b.timeAdd; + if (b.start) bt += Date.now()-b.start; + bt = b.period-bt; + return at-bt; + }); + if (!require('Storage').read("timerclk.timer.alert.js")) { + console.log("No timer app!"); + } else { + var time = timers[0].timeAdd; + if (timers[0].start) time += Date.now()-timers[0].start; + time = timers[0].time - time; + if (time<1000) t=1000; + if (timerclkTimerTimeout) clearTimeout(timerclkTimerTimeout); + timerclkTimerTimeout = setTimeout(() => load("timerclk.timer.alert.js"),time); + } + } +} +function timerclkCheckAlarms() { + if (timerclkAlarmTimeout) clearTimeout(timerclkAlarmTimeout); + var alarms = require('Storage').readJSON('timerclk.alarm.json',1)||[]; + var currentTime = require("timerclk.lib.js").getCurrentTime(); + alarms = alarms.filter(e=>e.on); + if (alarms.length) { + alarms = alarms.sort((a,b)=>(a.time-b.time)+(a.last-b.last)*86400000); + if (!require('Storage').read("timerclk.alarm.alert.js")) { + console.log("No alarm app!"); + } else { + var time = alarms[0].time-currentTime; + if (alarms[0].last == new Date().getDate() || time < 0) time += 86400000; + if (time<1000) t=1000; + if (timerclkAlarmTimeout) clearTimeout(timerclkAlarmTimeout); + timerclkAlarmTimeout = setTimeout(() => load("timerclk.alarm.alert.js"),time); + } + } +} +timerclkCheckTimers(); +timerclkCheckAlarms(); diff --git a/apps/timerclk/lib.js b/apps/timerclk/lib.js new file mode 100644 index 000000000..718962fe0 --- /dev/null +++ b/apps/timerclk/lib.js @@ -0,0 +1,127 @@ +exports.pause_img = atob("GBiBAf///////////+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B////////////w=="); +exports.play_img = atob("GBiBAf////////////P///D///A///Af//AH//AB//AAf/AAH/AAB/AAB/AAH/AAf/AB//AH//Af//A///D///P//////////////w=="); +exports.reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w=="); +exports.remove_img = atob("GBiBAf///////////+P/x+H/h+D/B/B+D/g8H/wYP/4Af/8A//+B//+B//8A//4Af/wYP/g8H/B+D+D/B+H/h+P/x////////////w=="); + +exports.formatTime = function(t, short, tnthEnable, fullTime) { + var negative = ""; + if (t < 0) { + t = t*(-1); + negative = "-"; + } + let hrs = Math.floor(t/3600000); + let mins = Math.floor(t/60000)%60; + let secs = Math.floor(t/1000)%60; + var tnth = ""; + if (tnthEnable) { + tnth = Math.floor(t/100)%10; + tnth = "."+tnth; + } + var hrsStr = hrs; + if (hrs < 10 && !negative) hrsStr = "0"+hrs; + var text; + if (short) { + if (hrs === 0) text = negative + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); + else text = negative + hrsStr + "/" + ("0"+mins).substr(-2); + } else { + if (hrs === 0 && !fullTime) text = negative + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + tnth; + else text = negative + hrsStr + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); + } + return text; +}; + +exports.getTime = function(e) { + var time = e.timeAdd; + if (e.start) { + time += Date.now() - e.start; + } + return time; +}; + +exports.getCurrentTime = function() { + var date = new Date(); + return date.getHours()*3600000+date.getMinutes()*60000+date.getSeconds()*1000+date.getMilliseconds(); +}; + +exports.registerControls = function(o) { + if (process.env.HWVERSION==1) { + setWatch(()=>{ + if (o.editIndex == 0) o.buttons.play.callback(); + else o.edit(o.editIndex, 1); + o.draw(); + }, BTN1, {repeat:true}); + setWatch(()=>{ + o.editIndex = !o.editIndex; + o.draw(); + }, BTN2, {repeat:true}); + setWatch(()=>{ + if (o.editIndex == 0) o.buttons.reset.callback(); + else o.edit(o.editIndex, -1); + o.draw(); + }, BTN3, {repeat:true}); + setWatch(()=>{ + if (o.editIndex) { + o.editIndex++; + if (o.editIndex > 3) o.editIndex = 1; + } else if (o.current > 0) o.current--; + o.update(); + }, BTN4, {repeat:true}); + setWatch(()=>{ + if (o.editIndex) { + o.editIndex--; + if (o.editIndex < 1) o.editIndex = 3; + } else { + o.current++; + if (o.current == o.all.length) o.all.push(o.defaultElement.clone()); + } + o.update(); + }, BTN5, {repeat:true}); + } else { + setWatch(()=>load(), BTN1); + Bangle.on('touch',(n,e)=>{ + for (var button of o.buttons) { + if (e.x>=button.pos[0] && e.y>=button.pos[1] && + e.x{ + if (!e.b) { + if (lastX > 40) { // right + o.current++; + if (o.current == o.all.length) o.all.push(o.defaultElement.clone()); + } else if (lastX < -40) { // left + if (o.current > 0) { + o.current--; + } + } else if (lastY > 30) { // down + if (absX < o.dragBorderHrsMins) { + o.edit(3, -1); + } else if (absX > o.dragBorderHrsMins && absX < o.dragBorderMinsSecs) { + o.edit(2, -1); + } else { + o.edit(1, -1); + } + } else if (lastY < -30) { // up + if (absX < o.dragBorderHrsMins) { + o.edit(3, 1); + } else if (absX > o.dragBorderHrsMins && absX < o.dragBorderMinsSecs) { + o.edit(2, 1); + } else { + o.edit(1, 1); + } + } + lastX = 0; + lastY = 0; + o.update(); + } else { + absX = e.x; + lastX = lastX + e.dx; + lastY = lastY + e.dy; + } + }); + } +}; diff --git a/apps/timerclk/metadata.json b/apps/timerclk/metadata.json new file mode 100644 index 000000000..6b415c0fc --- /dev/null +++ b/apps/timerclk/metadata.json @@ -0,0 +1,38 @@ +{ + "id": "timerclk", + "name": "Timer Clock", + "shortName":"Timer Clock", + "version":"0.01", + "description": "A clock with stopwatches, timers and alarms build in.", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports" : ["BANGLEJS","BANGLEJS2"], + "screenshots": [ + {"url":"screenshot.png"}, + {"url":"screenshot_stopwatch1.png"}, + {"url":"screenshot_stopwatch2.png"}, + {"url":"screenshot_settings1.png"}, + {"url":"screenshot_settings2.png"}, + {"url":"screenshot_settings3.png"} + ], + "readme": "README.md", + "storage": [ + {"name":"timerclk.app.js","url":"app.js"}, + {"name":"timerclk.img","url":"app-icon.js","evaluate":true}, + {"name":"timerclk.boot.js","url":"boot.js"}, + {"name":"timerclk.lib.js","url":"lib.js"}, + {"name":"timerclk.wid.js","url":"wid.js"}, + {"name":"timerclk.settings.js","url":"settings.js"}, + {"name":"timerclk.stopwatch.js","url":"stopwatch.js"}, + {"name":"timerclk.timer.js","url":"timer.js"}, + {"name":"timerclk.timer.alert.js","url":"timer.alert.js"}, + {"name":"timerclk.alarm.js","url":"alarm.js"}, + {"name":"timerclk.alarm.alert.js","url":"alarm.alert.js"}, + {"name":"timerclk.stopwatch.info","url":"stopwatch.info"}, + {"name":"timerclk.timer.info","url":"timer.info"}, + {"name":"timerclk.alarm.info","url":"alarm.info"} + ], + "data": [{"name":"timerclk.json"},{"name":"timerclk.stopwatch.json"},{"name":"timerclk.timer.json"},{"name":"timerclk.alarm.json"}], + "sortorder": 0 +} diff --git a/apps/timerclk/pause-24.png b/apps/timerclk/pause-24.png new file mode 100644 index 000000000..7ff72e906 Binary files /dev/null and b/apps/timerclk/pause-24.png differ diff --git a/apps/timerclk/play-24.png b/apps/timerclk/play-24.png new file mode 100644 index 000000000..26fa8d99c Binary files /dev/null and b/apps/timerclk/play-24.png differ diff --git a/apps/timerclk/remove-24.png b/apps/timerclk/remove-24.png new file mode 100644 index 000000000..b59505bcb Binary files /dev/null and b/apps/timerclk/remove-24.png differ diff --git a/apps/timerclk/reset-24.png b/apps/timerclk/reset-24.png new file mode 100644 index 000000000..73fb28dec Binary files /dev/null and b/apps/timerclk/reset-24.png differ diff --git a/apps/timerclk/screenshot.png b/apps/timerclk/screenshot.png new file mode 100644 index 000000000..1bccf6807 Binary files /dev/null and b/apps/timerclk/screenshot.png differ diff --git a/apps/timerclk/screenshot_settings1.png b/apps/timerclk/screenshot_settings1.png new file mode 100644 index 000000000..da187e496 Binary files /dev/null and b/apps/timerclk/screenshot_settings1.png differ diff --git a/apps/timerclk/screenshot_settings2.png b/apps/timerclk/screenshot_settings2.png new file mode 100644 index 000000000..4b12848d0 Binary files /dev/null and b/apps/timerclk/screenshot_settings2.png differ diff --git a/apps/timerclk/screenshot_settings3.png b/apps/timerclk/screenshot_settings3.png new file mode 100644 index 000000000..b1ef2381f Binary files /dev/null and b/apps/timerclk/screenshot_settings3.png differ diff --git a/apps/timerclk/screenshot_stopwatch1.png b/apps/timerclk/screenshot_stopwatch1.png new file mode 100644 index 000000000..f50d7a1d1 Binary files /dev/null and b/apps/timerclk/screenshot_stopwatch1.png differ diff --git a/apps/timerclk/screenshot_stopwatch2.png b/apps/timerclk/screenshot_stopwatch2.png new file mode 100644 index 000000000..89f91ae1b Binary files /dev/null and b/apps/timerclk/screenshot_stopwatch2.png differ diff --git a/apps/timerclk/settings.js b/apps/timerclk/settings.js new file mode 100644 index 000000000..556dded98 --- /dev/null +++ b/apps/timerclk/settings.js @@ -0,0 +1,292 @@ +(function(back) { + const FILE = "timerclk.json"; + const BOOL_FORMAT = v=>v?/*LANG*/"On":/*LANG*/"Off"; + // Load settings + var settings = require('Storage').readJSON(FILE, true) || {} + settings.clock = Object.assign({ + "timeFont":"Anton", + "timeFontSize":0, + "dateFont":"6x8", + "dateFontSize":2, + "dowFont":"6x8", + "dowFontSize":2, + "specialFont":"6x8", + "specialFontSize":2, + "shortDate":true, + "showStopwatches":true, + "showTimers":true, + }, settings.clock||{}); + settings.stopwatch = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + }, settings.stopwatch||{}); + settings.timer = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":10, + }, settings.timer||{}); + settings.alarm = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":10, + }, settings.alarm||{}); + var timeFonts = ["Anton"].concat(g.getFonts()); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + var mainMenu = { + "" : { "title" : "Timer Clock" }, + "< Back" : () => back(), + "Clock": ()=>{E.showMenu(clockMenu);}, + "Stopwatch": ()=>{E.showMenu(stopwatchMenu);}, + "Timer": ()=>{E.showMenu(timerMenu);}, + "Alarm": ()=>{E.showMenu(alarmMenu);}, + }; + var clockMenu = { + "" : { "title" : "Clock" }, + "< Back" : () => E.showMenu(mainMenu), + "time font":{ + value: 0|timeFonts.indexOf(settings.clock.timeFont), + format: v => timeFonts[v], + min: 0, max: timeFonts.length-1, + onchange: v => { + settings.clock.timeFont = timeFonts[v]; + writeSettings(); + } + }, + "time size":{ + value: 0|settings.clock.timeFontSize, + min: 0, + onchange: v => { + settings.clock.timeFontSize = v; + writeSettings(); + } + }, + "date font":{ + value: 0|g.getFonts().indexOf(settings.clock.dateFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.clock.dateFont = g.getFonts()[v]; + writeSettings(); + } + }, + "date size":{ + value: 0|settings.clock.dateFontSize, + min: 0, + onchange: v => { + settings.clock.dateFontSize = v; + writeSettings(); + } + }, + "dow font":{ + value: 0|g.getFonts().indexOf(settings.clock.dowFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.clock.dowFont = g.getFonts()[v]; + writeSettings(); + } + }, + "dow size":{ + value: 0|settings.clock.dowFontSize, + min: 0, + onchange: v => { + settings.clock.dowFontSize = v; + writeSettings(); + } + }, + "short date": { + value: !!settings.clock.shortDate, + format: BOOL_FORMAT, + onchange: v => { + settings.clock.shortDate = v; + writeSettings(); + } + }, + "stopwatches": { + value: !!settings.clock.showStopwatches, + format: v=>v?/*LANG*/"Show":/*LANG*/"Hide", + onchange: v => { + settings.clock.showStopwatches = v; + writeSettings(); + } + }, + "timers": { + value: !!settings.clock.showTimers, + format: v=>v?/*LANG*/"Show":/*LANG*/"Hide", + onchange: v => { + settings.clock.showTimers = v; + writeSettings(); + } + }, + }; + + var stopwatchMenu = { + "" : { "title" : "Stopwatch" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.stopwatch.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.stopwatch.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.stopwatch.fontSize, + min: 0, + onchange: v => { + settings.stopwatch.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.stopwatch.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.stopwatch.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.stopwatch.indexFontSize, + min: 0, + onchange: v => { + settings.stopwatch.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.stopwatch.buttonHeight, + min: 0, + onchange: v => { + settings.stopwatch.buttonHeight = v; + writeSettings(); + } + }, + }; + var timerMenu = { + "" : { "title" : "Timer" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.timer.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.timer.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.timer.fontSize, + min: 0, + onchange: v => { + settings.timer.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.timer.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.timer.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.timer.indexFontSize, + min: 0, + onchange: v => { + settings.timer.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.timer.buttonHeight, + min: 0, + onchange: v => { + settings.timer.buttonHeight = v; + writeSettings(); + } + }, + "vibrate":{ + value: 0|settings.timer.vibrate, + min: 0, + onchange: v=>{ + settings.timer.vibrate = v; + writeSettings(); + } + } + }; + var alarmMenu = { + "" : { "title" : "Alarm" }, + "< Back" : () => E.showMenu(mainMenu), + "font":{ + value: 0|g.getFonts().indexOf(settings.alarm.font), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.alarm.font = g.getFonts()[v]; + writeSettings(); + } + }, + "fontsize":{ + value: 0|settings.alarm.fontSize, + min: 0, + onchange: v => { + settings.alarm.fontSize = v; + writeSettings(); + } + }, + "index font":{ + value: 0|g.getFonts().indexOf(settings.alarm.indexFont), + format: v => g.getFonts()[v], + min: 0, max: g.getFonts().length-1, + onchange: v => { + settings.settings.alarm.indexFont = g.getFonts()[v]; + writeSettings(); + } + }, + "index size":{ + value: 0|settings.alarm.indexFontSize, + min: 0, + onchange: v => { + settings.alarm.indexFontSize = v; + writeSettings(); + } + }, + "button height":{ + value: 0|settings.alarm.buttonHeight, + min: 0, + onchange: v => { + settings.alarm.buttonHeight = v; + writeSettings(); + } + }, + "vibrate":{ + value: 0|settings.alarm.vibrate, + min: 0, + onchange: v=>{ + settings.alarm.vibrate = v; + writeSettings(); + } + } + }; + E.showMenu(mainMenu); +}); diff --git a/apps/timerclk/stopwatch.info b/apps/timerclk/stopwatch.info new file mode 100644 index 000000000..72ad418b1 --- /dev/null +++ b/apps/timerclk/stopwatch.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Stopwatch","src":"timerclk.stopwatch.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/stopwatch.js b/apps/timerclk/stopwatch.js new file mode 100644 index 000000000..8ac6d30a7 --- /dev/null +++ b/apps/timerclk/stopwatch.js @@ -0,0 +1,135 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.stopwatch.json") || []; + +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, +}, settings.stopwatch||{}); +var defaultElement = {start:null, timeAdd:0}; +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + var interval = Math.floor(timerclk.getTime(all[current])/3600000)?1000:100; + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, interval); draw();}, interval - (timerclk.getTime(all[current]) % interval)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function play() { + if (all[current].start) { // running + all[current].timeAdd += Date.now() - all[current].start; + all[current].start = null; + update(); + } else { // paused + all[current].start = Date.now(); + update(); + } + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} +function reset() { + all[current] = defaultElement.clone(); + update(); + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} + +function edit(position, change) { + if (position == 1) all[current].timeAdd += change*1000; + else if (position == 2) all[current].timeAdd += change*60000; + else if (position == 3) all[current].timeAdd += change*3600000; + require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all)); +} + + +var buttonsRunning = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: reset, img: timerclk.reset_img, col:"#f50"}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:"#0ff"}, +}; +var buttonsNormal = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:buttonsRunning.reset.col}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:buttonsRunning.play.col}, +}; +buttons = buttonsNormal; + +function drawButtons() { + if (all[current].start || all[current].time) { + buttons = buttonsRunning; + if (all[current].start) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + } else { + buttons = buttonsNormal; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + var timeStr = timerclk.formatTime(timerclk.getTime(all[current]), false, true); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + g.drawString(timeStr,x,y); + + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(".")[0].split(":"); + if (timeStr.length < 3) timeStr = [""].concat(timeStr); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/timer.alert.js b/apps/timerclk/timer.alert.js new file mode 100644 index 000000000..f51ea6767 --- /dev/null +++ b/apps/timerclk/timer.alert.js @@ -0,0 +1,62 @@ +if (timerclkTimerTimeout) clearInterval(timerclkTimerTimeout); +var timerclk = require("timerclk.lib.js"); +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "vibrate":10 +}, settings.timer||{}); + +function showTimer(timer) { + Bangle.loadWidgets(); + Bangle.drawWidgets(); + Bangle.setLocked(false); + E.showPrompt("Timer finished!",{ + title:"TIMER!", + buttons : {/*LANG*/"Ok":true} + }).then(function(ok) { + buzzCount = 0; + if (ok) { + timer.time += Date.now() - timer.start; + timer.start = null; + } + require("Storage").write("timerclk.timer.json",JSON.stringify(timers)); + load(); + }); + function vibrate(counter) { + VIBRATE.write(1); + setTimeout(() => VIBRATE.write(0), 100); + if (--counter) setTimeout(() => vibrate(counter), 250); + } + function buzz() { + if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence + vibrate(4); + if (buzzCount--) + setTimeout(buzz, 3000); + else { // auto-snooze + buzzCount = settings.vibrate; + setTimeout(buzz, 600000); + } + } + var buzzCount = settings.vibrate; + buzz(); +} + +// Check for timers +console.log("checking for timers..."); +var timers = require("Storage").readJSON("timerclk.timer.json",1)||[]; +var active = timers.filter(e=>e.start); +if (active.length) { + // if there's an timer, show it + active = active.sort((a,b)=>{ + var at = a.time; + if (a.start) at += Date.now()-a.start; + at = a.period-at; + var bt = b.time; + if (b.start) bt += Date.now()-b.start; + bt = b.period-bt; + return at-bt; + }); + showTimer(active[0]); +} else { + // otherwise just go back to default app + setTimeout(load, 100); +} diff --git a/apps/timerclk/timer.info b/apps/timerclk/timer.info new file mode 100644 index 000000000..39a338693 --- /dev/null +++ b/apps/timerclk/timer.info @@ -0,0 +1 @@ +{"id":"timerclk","name":"tclk Timer","src":"timerclk.timer.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10} diff --git a/apps/timerclk/timer.js b/apps/timerclk/timer.js new file mode 100644 index 000000000..060c07813 --- /dev/null +++ b/apps/timerclk/timer.js @@ -0,0 +1,139 @@ +var timerclk = require("timerclk.lib.js"); +const height = g.getHeight(), width = g.getWidth(); + +var all = require("Storage").readJSON("timerclk.timer.json") || []; +var settings = require('Storage').readJSON("timerclk.json", true) || {}; +settings = Object.assign({ + "font":"Vector", + "fontSize":40, + "indexFont":"6x8", + "indexFontSize":3, + "buttonHeight":40, + "vibrate":4, +}, settings = settings.timer||{}); +var defaultElement = {time:300000, start:null, timeAdd:0}; + +var current = 0; +var editIndex = 0; +var drawInterval; +var drawIntervalTimeout; +var buttons; +var dragBorderHrsMins=0, dragBorderMinsSecs=0; + +function update() { + if (drawInterval) clearInterval(drawInterval); + if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout); + if (all[current].start) { + drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, 1000); draw();}, 1000 - (timerclk.getTime(all[current]) % 1000)); + } else { + drawInterval = null; + drawIntervalTimeout = null; + } + draw(); + drawButtons(); +} +function play() { + if (all[current].start) { // running + all[current].timeAdd += Date.now() - all[current].start; + all[current].start = null; + update(); + } else { // paused + all[current].start = Date.now(); + update(); + } + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} +function reset() { + all[current] = defaultElement.clone(); + update(); + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} +function remove() { + all.splice(current, 1); + if (current == all.length) current--; + if (all.length == 0) { + all.push(defaultElement.clone()); + current++; + } + update(); + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} + +function edit(position, change) { + if (position == 1) all[current].time += change*1000; + else if (position == 2) all[current].time += change*60000; + else if (position == 3) all[current].time += change*3600000; + require("Storage").write("timerclk.timer.json",JSON.stringify(all)); + timerclkCheckTimers(); +} + +var buttonsRunning = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: reset, img: timerclk.reset_img, col:"#f50"}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:"#0ff"}, +}; +var buttonsNormal = { + reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:buttonsRunning.reset.col}, + play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:buttonsRunning.play.col}, +}; +buttons = buttonsNormal; + + +function drawButtons() { + if (all[current].start || all[current].timeAdd) { + buttons = buttonsRunning; + if (all[current].start) { + buttons.play.img = timerclk.pause_img; + } else { + buttons.play.img = timerclk.play_img; + } + } else { + buttons = buttonsNormal; + } + for (var button of buttons) { + g.setColor(button.col); + g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]); + g.setColor("#000"); + // scale 24px images + let iw = settings.buttonHeight-10; + var scale = iw/24; + let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2); + let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2); + g.drawImage(button.img, ix, iy, {scale: scale}); + } +} + +function draw() { + var x = g.getWidth()/2; + var y = g.getHeight()/2; + g.reset(); + + var time = all[current].time - timerclk.getTime(all[current]); + g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight); + g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize); + g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2)); + g.setFontAlign(0,0).setFont(settings.font, settings.fontSize); + var timeStr = timerclk.formatTime(time, false, false, true); + g.drawString(timeStr,x,y); + + var start = (width-g.stringMetrics(timeStr).width)/2; + timeStr = timeStr.split(":"); + var markerPosChange = g.stringMetrics("__").width/2; + if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange; + else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange; + else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange; + else x = 0; + if (x) g.drawString("__", x, y); + dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2; + dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2; +} + +if (all.length == 0) { + all.push(defaultElement.clone()); +} +timerclk.registerControls(this); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +update(); diff --git a/apps/timerclk/wid.js b/apps/timerclk/wid.js new file mode 100644 index 000000000..e3ddeb791 --- /dev/null +++ b/apps/timerclk/wid.js @@ -0,0 +1,7 @@ +WIDGETS["timerclk.alarm"]={area:"tl",width:0,draw:function() { + if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); + },reload:function() { + WIDGETS["timerclk.alarm"].width = (require('Storage').readJSON('timerclk.alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0; + } +}; +WIDGETS["timerclk.alarm"].reload(); diff --git a/bin/create_apps_json.sh b/bin/create_apps_json.sh index adc5f8a62..dd883b22a 100755 --- a/bin/create_apps_json.sh +++ b/bin/create_apps_json.sh @@ -13,17 +13,36 @@ # # If you do this, please do not attempt to commit your modified # apps.json back into the main BangleApps repository! +# +# You can pass an optional filename to this script, and it will write +# to that instead, apps.local.json is used when opening the loader on localhost +outfile="${1:-apps.json}" cd `dirname $0`/.. -echo "[" > apps.json +echo "[" > "$outfile" +first=1 for app in apps/*/; do echo "Processing $app..."; if [[ "$app" =~ ^apps/_example.* ]]; then echo "Ignoring $app" else - cat ${app}metadata.json >> apps.json + if [ $first -eq 1 ]; then + first=0; + else + echo "," >> "$outfile" + fi; + cat ${app}metadata.json >> "$outfile" # echo ",\"$app\"," >> apps.json # DEBUG ONLY - echo "," >> apps.json fi done -echo "null]" >> apps.json +echo "]" >> "$outfile" + +if [ -z "$1"]; then + # Running with no arguments: prevent accidental commit of modified apps.json. + # You can use `create_apps.json.sh apps.json` if you really want to both + # overwrite and still commit apps.json + git update-index --skip-worktree apps.json + echo "Told git to ignore modified apps.json." + # If you want to unignore it, use + # 'git update-index --no-skip-worktree apps.json' +fi \ No newline at end of file diff --git a/core b/core index 5023ee122..3093d78a5 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 5023ee1228030130ba9f026d5dbe920f7527ee7d +Subproject commit 3093d78a5d752cbf03ea8f9a1a7c0b50b9c8123b diff --git a/loader.js b/loader.js index 0355ea89c..d8ba26269 100644 --- a/loader.js +++ b/loader.js @@ -5,6 +5,11 @@ if (window.location.host=="banglejs.com") { document.title += " [Development]"; document.getElementById("apploaderlinks").innerHTML = 'This is the development Bangle.js App Loader - you can also try the Official Version for stable apps.'; +} else if (window.location.hostname==='localhost') { + document.title += " [Local]"; + Const.APPS_JSON_FILE = "apps.local.json"; + document.getElementById("apploaderlinks").innerHTML = + 'This is your local Bangle.js App Loader - you can try the Official Version here.'; } else { document.title += " [Unofficial]"; document.getElementById("apploaderlinks").innerHTML = diff --git a/package.json b/package.json index b796044c9..32c96e3ea 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,17 @@ "scripts": { "lint-apps": "eslint ./apps --ext .js", "test": "node bin/sanitycheck.js && eslint ./apps --ext .js", + "update-local-apps": "./bin/create_apps_json.sh apps.local.json", + "local": "npm-watch & npx http-server -a localhost -c-1", "start": "npx http-server -c-1" }, + "watch": { + "update-local-apps": "apps/*/metadata.json" + }, "dependencies": { "acorn": "^7.2.0" + }, + "devDpendencies": { + "npm-watch": "^0.11.0" } }