diff --git a/apps/bigdclock/ChangeLog b/apps/bigdclock/ChangeLog new file mode 100644 index 000000000..ec66c5568 --- /dev/null +++ b/apps/bigdclock/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/bigdclock/README.md b/apps/bigdclock/README.md new file mode 100644 index 000000000..71f4362fa --- /dev/null +++ b/apps/bigdclock/README.md @@ -0,0 +1,14 @@ +# Big Digit Clock + +There are a number of big digit clocks available for the Bangle, but this is +the first which shows all the essential information that a clock needs to show +in a manner that is easy to read by those with poor eyesight. + +The clock shows the time-of-day, the day-of-week and the day-of-month, as well +as an easy-to-see icon showing the current charge on the battery. + +![screenshot](./screenshot.png) + +## Creator + +Created by [Deirdre O'Byrne](https://github.com/deirdreobyrne) diff --git a/apps/bigdclock/bigdclock.app.js b/apps/bigdclock/bigdclock.app.js new file mode 100644 index 000000000..983ca0d1c --- /dev/null +++ b/apps/bigdclock/bigdclock.app.js @@ -0,0 +1,104 @@ +// + +Graphics.prototype.setFontOpenSans = function(scale) { + // Actual height 48 (50 - 3) + this.setFontCustom( + atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAAAAP8AAAAAAAAB/wAAAAAAAAH/gAAAAAAAAf+AAAAAAAAB/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAA/wAAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAPAAAAAAAAAH8AAAAAAAAD/wAAAAAAAA//AAAAAAAAf/8AAAAAAAP//wAAAAAAH///AAAAAAB///4AAAAAA///8AAAAAAf///AAAAAAH///gAAAAAD///wAAAAAB///4AAAAAAf//+AAAAAAP///AAAAAAH///gAAAAAA///4AAAAAAD//8AAAAAAAP/+AAAAAAAA//gAAAAAAAD/wAAAAAAAAP4AAAAAAAAA8AAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAAAAAP///AAAAAAH////gAAAAD/////gAAAAf/////gAAAH//////AAAA//////+AAAD//////8AAAf//////4AAD//4AH//gAAP/gAAAf/AAA/4AAAAf8AAH/AAAAA/4AAf4AAAAB/gAB/AAAAAD+AAH8AAAAAP4AAfwAAAAA/gAB/AAAAAD+AAH8AAAAAP4AAf4AAAAB/gAB/gAAAAH+AAD/gAAAB/wAAP/gAAAf/AAA//4AAf/8AAB///////gAAD//////8AAAH//////wAAAP/////+AAAAf/////wAAAA/////8AAAAAf////AAAAAAf///gAAAAAAB//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHgAAAAAAAAA/AAAAAAAAAH+AAAAAAAAA/4AAAAAAAAD/AAAAAAAAAf8AAAAAAAAD/gAAAAAAAAf8AAAAAAAAB/gAAAAAAAAP8AAAAAAAAB/wAAAAAAAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAD8AAAOAAAAAfwAAB+AAAAD/AAAH8AAAAf8AAA/wAAAH/wAAH/AAAA//AAAf4AAAH/8AAD/gAAA//wAAP8AAAH//AAA/gAAA//8AAD+AAAH//wAAf4AAA/9/AAB/AAAH/n8AAH8AAA/8fwAAfwAAH/h/AAB/AAA/8H8AAH8AAH/gfwAAfwAA/8B/AAB/gAH/gH8AAH+AB/8AfwAAP8Af/gB/AAA////8AH8AAD////gAfwAAH///8AB/AAAf///gAH8AAA///8AAfwAAB///gAB/AAAD//4AAH8AAAH/+AAAfwAAAH/gAAB/AAAAAAAAAH8AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAYAAAAB/gAAB4AAAAD+AAAPwAAAAP4AAA/gAAAAfwAAH+AAAAB/AAAf4AAAAH8AAD/AB+AAfwAAP8AH4AA/gAA/gAfgAD+AAH+AB+AAP4AAfwAH4AA/gAB/AAfgAD+AAH8AB+AAP4AAfwAH4AA/gAB/AA/wAD+AAH8AD/AAP4AAfwAP8AA/gAB/AA/wAD+AAH+AH/AAf4AAf4Af+AB/AAA/wH/8AP8AAD////4D/wAAP//+////AAAf//7///4AAB///P///gAAD//8f//8AAAP//h///gAAAf/8D//+AAAA//gH//wAAAA/4AP/8AAAAAAAAP/AAAAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAA/wAAAAAAAAH/AAAAAAAAB/8AAAAAAAAP/wAAAAAAAD//AAAAAAAAf/8AAAAAAAH//wAAAAAAA/+/AAAAAAAP/z8AAAAAAB/8PwAAAAAAf/g/AAAAAAD/4D8AAAAAAf/APwAAAAAH/4A/AAAAAA/+AD8AAAAAP/wAPwAAAAB/8AA/AAAAAf/gAD8AAAAD/4AAPwAAAA//AAA/AAAAD///////wAAP///////AAA///////8AAD///////wAAP///////AAA///////8AAD///////wAAP///////AAAAAAAA/AAAAAAAAAD8AAAAAAAAAPwAAAAAAAAA/AAAAAAAAAD8AAAAAAAAAPwAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAQAB/gAAAD//gAD+AAA////AAP8AAD///8AAfwAAP///wAB/AAA////AAH8AAD///8AAf4AAP///wAA/gAA////AAD+AAD/+H8AAP4AAP4AfwAA/gAA/gB+AAD+AAD+AH4AAP4AAP4AfwAA/gAA/gB/AAD+AAD+AH8AAP4AAP4AfwAB/gAA/gB/AAH+AAD+AH+AA/wAAP4Af8AD/AAA/gB/4A/8AAD+AD////gAAP4AP///+AAA/gAf///wAAD+AB////AAAP4AD///4AAA/gAH///AAAAAAAP//4AAAAAAAf/+AAAAAAAAf/gAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//AAAAAAAf///gAAAAAP////gAAAAD/////gAAAAf/////AAAAD/////+AAAA//////8AAAD//////4AAAf/8/4//gAAD/8H+Af/AAAP/AfgAf8AAB/wD+AA/wAAH+APwAB/gAA/wB/AAD+AAD+AH4AAP4AAP4AfgAA/gAB/gB+AAD+AAH8AH4AAP4AAfwAfgAA/gAB/AB/AAH+AAH8AH8AAf4AAfwAf4AD/AAB/AB/4A/8AAH8AH////wAAfwAP///+AAB/AA////4AAH8AB////AAAfwAD///4AAA/AAH///AAAAAAAP//4AAAAAAAP/+AAAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AAAAAAAAAP4AAAAAAAAA/gAAAAAAAAD+AAAAAAAAAP4AAAAADAAA/gAAAAB8AAD+AAAAAfwAAP4AAAAH/AAA/gAAAB/8AAD+AAAAf/wAAP4AAAP//AAA/gAAD//8AAD+AAA///wAAP4AAP//8AAA/gAD///AAAD+AB///wAAAP4Af//4AAAA/gH//+AAAAD+B///gAAAAP4f//wAAAAA/v//8AAAAAD////AAAAAAP///wAAAAAA///4AAAAAAD//+AAAAAAAP//gAAAAAAA//wAAAAAAAD/8AAAAAAAAP/AAAAAAAAA/wAAAAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAH/wAAAAH8AB//gAAAB/8AP//gAAAf/8B//+AAAB//4P//8AAAP//w///4AAB///n///gAAH//+////AAA/////gf8AAD/h//4AfwAAP4B//AB/gAB/gD/4AD+AAH8AP/gAP4AAfwAf8AA/gAB/AA/wAB+AAH4AD/AAH4AAfwAP8AAfgAB/AB/4AD+AAH8AH/gAP4AAfwA//AA/gAA/gH/+AH+AAD/h//8AfwAAP//+/4D/AAAf//7///8AAB///H///gAAD//4P//+AAAP//g///wAAAf/8B//+AAAA//gD//wAAAA/4AH/+AAAAAAAAH/wAAAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAD//AAAAAAAA///AAAAAAAH//+AAPwAAB///8AA/gAAP///4AD+AAA////wAP4AAH////AA/gAAf///8AD+AAD/4B/4AP4AAP+AB/gA/gAA/gAD+AD+AAH+AAP4AP4AAfwAAfgA/gAB/AAB+AD+AAH8AAH4AP4AAfwAAfgB/AAB/AAB+AH8AAH8AAH4A/wAAf4AA/AH+AAA/gAD8A/4AAD/gAfwH/gAAP/AD+B/8AAAf/g/w//gAAB//////+AAAD//////wAAAH/////+AAAAP/////wAAAAf////8AAAAA/////AAAAAA////wAAAAAAf//4AAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAfgAAAAP8AAD/AAAAA/4AAf8AAAAH/gAB/4AAAAf+AAH/gAAAB/4AAf+AAAAH/gAB/4AAAAf+AAH/AAAAA/wAAP8AAAAB+AAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='), + 46, + atob("EhklJSUlJSUlJSUlEg=="), + 64+(scale<<8)+(1<<16) + ); +}; + +// the following 2 sections are used from waveclk to schedule minutely updates +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function () { + drawTimeout = undefined; + draw(); + }, 60300 - (Date.now() % 60000)); +} + +function drawBackground() { + g.setBgColor(0, 0, 0); + g.setColor(1, 1, 1); + g.clear(); +} + +function digit(num) { + return String.fromCharCode(num + 48); +} + +function timeString(h, m) { + return digit(h / 10) + digit(h % 10) + ":" + digit(m / 10) + digit(m % 10); +} + +function dayString(w) { + return digit(w / 10) + digit(w % 10); +} + +function draw() { + g.reset(); + drawBackground(); + var date = new Date(); + var h = date.getHours(), + m = date.getMinutes(); + var d = date.getDate(), + w = date.getDay(); // d=1..31; w=0..6 + const level = E.getBattery(); + const width = level + (level/2); + + g.setBgColor(0, 0, 0); + g.setColor(1, 1, 1); + + g.setFontOpenSans(); + g.setFontAlign(0, -1); + g.drawString(timeString(h, m), g.getWidth() / 2, 30); + g.drawString(dayString(d), g.getWidth() * 3 / 4, 98); + g.setFont('Vector', 52); + g.setFontAlign(-1, -1); + g.drawString("SUMOTUWETHFRSA".slice(2*w,2*w+2), 6, 103); + g.setColor(0, 1, 0); + g.fillRect(0, 90, g.getWidth(), 94); + g.reset(); + + g.setColor(1,1,1); + g.fillRect(9,159,166,171); + g.fillRect(167,163,170,167); + if (Bangle.isCharging()) { + g.setColor(1,1,0); + } else if (level > 40) { + g.setColor(0,1,0); + } else { + g.setColor(1,0,0); + } + g.fillRect(12,162,12+width,168); + if (level < 100) { + g.setColor(0,0,0); + g.fillRect(12+width+1,162,162,168); + } + // widget redraw + Bangle.drawWidgets(); + queueDraw(); +} + +Bangle.loadWidgets(); +draw(); + +//the following section is also from waveclk +Bangle.on('lcdPower', on => { + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +Bangle.setUI("clock"); + +Bangle.drawWidgets(); diff --git a/apps/bigdclock/bigdclock.icon.js b/apps/bigdclock/bigdclock.icon.js new file mode 100644 index 000000000..4aaecfa23 --- /dev/null +++ b/apps/bigdclock/bigdclock.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgJC/AAMD4F4AgN4g/D/4FB/E/AoUH/F/AoOAh4FCz4FD4EPAoUHAoOHwAFDx/AAoUfAol/g4RD/w1Cg/B/AFD4fwn4XC4fg8/wAoPH//P7AFE9wFE8YFEEwcf4+BwAFBiACBAoUwAQPAAQMgAQNAArIjFF4sYgEBAoUIAoIRChi3B8AFBg8Ah/wAoIVBjH8ZAXguF+AoSDBn7WEh4FEg4")) diff --git a/apps/bigdclock/bigdclock.png b/apps/bigdclock/bigdclock.png new file mode 100644 index 000000000..4da1a9010 Binary files /dev/null and b/apps/bigdclock/bigdclock.png differ diff --git a/apps/bigdclock/metadata.json b/apps/bigdclock/metadata.json new file mode 100644 index 000000000..275a33343 --- /dev/null +++ b/apps/bigdclock/metadata.json @@ -0,0 +1,17 @@ +{ "id": "bigdclock", + "name": "Big digit clock containing just the essentials", + "shortName":"Big digit clock", + "version":"0.01", + "description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.", + "icon": "bigdclock.png", + "type": "clock", + "tags": "clock", + "allow_emulator":true, + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "screenshots": [ { "url":"screenshot.png" } ], + "storage": [ + {"name":"bigdclock.app.js","url":"bigdclock.app.js"}, + {"name":"bigdclock.img","url":"bigdclock.icon.js","evaluate":true} + ] +} diff --git a/apps/bigdclock/screenshot.png b/apps/bigdclock/screenshot.png new file mode 100644 index 000000000..96f2dd4ef Binary files /dev/null and b/apps/bigdclock/screenshot.png differ diff --git a/apps/cassioWatch/ChangeLog b/apps/cassioWatch/ChangeLog new file mode 100644 index 000000000..f00b3fa0a --- /dev/null +++ b/apps/cassioWatch/ChangeLog @@ -0,0 +1,10 @@ +0.0: Main App. +0.1: Performance Fixes. +0.2: Correct Screen Clear and Draw. +0.3: Minor Fixes. +0.4: Clear Old Time on Screen Before Draw new Time +0.5: Update Date and Time to use Native date Funcions +0.6: Add Settings Page +0.7: Update Rocket Sequences Scope to not use memory all time +0.8: Update Some Variable Scopes to not use memory until need +0.9: Remove ESLint spaces \ No newline at end of file diff --git a/apps/cassioWatch/README.md b/apps/cassioWatch/README.md new file mode 100644 index 000000000..1342af8e6 --- /dev/null +++ b/apps/cassioWatch/README.md @@ -0,0 +1,10 @@ +# cassioWatch + +![Screenshot](screens/screen_night.png) ![Screenshot](screens/screen_day.png) + +Clock with Space Cassio Watch Style. + +It displays current temperature,day,steps,battery.heartbeat and weather. + +**To-do**: +Integrate heartbeat and Weather, Align and change size of some elements. diff --git a/apps/cassioWatch/app.js b/apps/cassioWatch/app.js new file mode 100644 index 000000000..93538ec50 --- /dev/null +++ b/apps/cassioWatch/app.js @@ -0,0 +1,133 @@ +require("Font6x12").add(Graphics); +require("Font8x12").add(Graphics); +require("Font7x11Numeric7Seg").add(Graphics); + +let ClockInterval; +let RocketInterval; +let BatteryInterval; + +function bigThenSmall(big, small, x, y) { + g.setFont("7x11Numeric7Seg", 2); + g.drawString(big, x, y); + x += g.stringWidth(big); + g.setFont("8x12"); + g.drawString(small, x, y); +} + +function ClearIntervals(inoreclock) { + if (RocketInterval) clearInterval(RocketInterval); + if (BatteryInterval) clearInterval(BatteryInterval); + RocketInterval = undefined; + BatteryInterval = undefined; + if (inoreclock) return; + if (ClockInterval) clearInterval(ClockInterval); + ClockInterval = undefined; +} + +function getBackgroundImage() { + return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA==")); +} + +function getRocketSequences() { + return { + 1: require("heatshrink").decompress(atob("qFGwkCkQAiiEBEkUgKQhPhE8ogCE8YhCiQoEE7pKEPIgncTQ4neEwpQCPoh1eJYYwCJ7QmHKAh1hZIpOjPAUBJ0ZQCTzEhExZ1lPAZ1kKDQmOJ65O2E65OPOy5O2E64mPOyxO/J2wnPJyx2QJ35O/J2khE0p2POq52PEy4nOiQnlOrEhiSfMJrEggQnLJzB1CPBQmZkInMEzBQDPBImbPBR1ZEoRMCZYImhgQgEE0BzFKAgmaDwLDFKAbqdYQwHBOrcgDgLBFJrsiiRNGYbpLBY4Ymhd4omkkUhE0pQEEwUBJjrHBd4QmCdzoiBDwYrCPLyZHF4QnagQeCE8UgJwYniJwgnIOzwfFO0wJCJzMQE4gyFEzR2FBQombkInDQI4AakAnBTYS+ZE5BMDE0LEES7YnLE0R3FAEQA=")), + 2: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYon/AA0gEAQniEwIhCAgYndEIjqBE8CaGKogmgKAp1fKAgncExBQBBQR1gKAp7BJ0IndExR4CE0idaOpYnbExqeYJxxPYEx0BJ0x2XExx2XJ20QE6xONJi5OPGwJOlBwLFkLoLFlBwJOkOwJOlE4JOkTjBOOE/52Pdi5OPEy7FnE5wmXE5xOZT5gmYEoMiiB1lgR4KTLAkDPBJ1WIAYDDKA4mWJwchDwYEDTjQiDJQh4GYLAhHFosSJy6OCTIxaEEywbBKYwjEEzMgUQxQFBogAURwZOGOjTKJdTYnOEryfHE0JQEfIpQgYQMAgJLeAgrtfTI4ndgSaFE4h0bdQkSZQpOfEAgIBO0AnEdrh2FJAb1EdbInEBIpObOwhOEEzYnFXzZ2HE4QlhE4QlDFMKcDYooniO0QnDT0YnCE0ciA")), + 3: require("heatshrink").decompress(atob("qFGwkEogAjiMUEkVAKYgnhPYolgOQIniOYZ4FOcLqBE8CaGKojpgKAomhEYUQE7gmHKAIxCE0QkCPYR1gZIgnZExR4CJ0idmE7ZONYzImNgEUJ0p3YJRh2ZJJwnXOpQhBdkpaETsMEGQhOhE7jFLUYpOfTzgmKE4hOiE4hOigEUJ0rvCEywnPEqx2OTjBOOE7ImOTsqeZE5zFYoJOmT5kBJzEAih4LdK5mBAQInKOqoYDEgR4JEypHDEYbxJOq5ABdgZ7CEzZOEJQgnGihOYEIzJFTionCKYxWGEy9ADAYnGUIYmWog/EdBFAEy7KIKAwnjKwLqWE5pMeT48CVQpQfgMjKEtEiAnfEQJQCgJSCTcB6FJzkEdYcUE8FAdQghDOzonKTjh2EZAidcDoInHJzodBOwx/BE8JxcOwsAOwQmhJgSXDObwnFEwUUO0LFGE8aeiE4YmiokQE0tE")), + 4: require("heatshrink").decompress(atob("qFGwkCkQAjiMSEkRTFE/4AGkMAgQCBE8MgEIYEDE7whDdQIngTQxVEE0ChFTjxQFE7jnFKAgxCOsBQFZgJ1gE7wmKPAROkTrTEHGAwnYiBHJFAaeXOoyXBEQZPac5AsFgJOhAoh2XJwwnFKoROdE4J9GJzwnIiQmVkInPAC0QE5AJFE64mHY5DFdE4SBEYr5JDJ0hKDJ0jCZJxoACgInmKLAmOTq5OOEy5OPTsxOYE5wmXO5wlYkAnMOqshiRNCgR4LOC8CkJCCEzxHDAgYnJOqpAEDoZ4HEyodDEQpQHdCsQOwwFHEyzoCPYzJGEy0gEwaZGA4acVEQSjHKAomXkQYEYAwlZeRKYDE8gjCYa7zJEwcCkImfKAb4FAD0hdTh4LgRSBOcR0CJz0gYYrrgN4QnEYrxOEE4bEeiAnGF4J2idL6VDE8ohBE0gnFE0J0BE4QGBiROgdIQABgJ2hJoTtjYgZSEE8ScgE4omikUQTcQADA=")), + 5: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYonhiAnjkEATIIniEwIhCAgYndEIhQFYUZVEE0BQFOr5QEeQQmiKAL1DOr5QEE7ROCDgZVEAoInZDwchFQQoDPAJOdEQYrBdrZFDOYwncEJDsDVIpOXgJxEE4pObEAgGFgJOaE48BaIhOZJ5ZObY5ROcE441CE6xOGPAwtCJzpGCJ0hHDkI1DJzwoEJzInLFg52dUo5O/J35OzE54mWOx4mXJxx1XE54mXkUhExkSJzCfMOrAlBPBiZXgQDBAQQmgJgh4JOqoYEFYwmaDoZzEFgh1YDgkiiAFEKAroXJJAGFiQmVkCNDTIz5EJy57HKAomXkQYEJoqaYeRadEJrAnJEQUAgJPiAoYmeT4cCkAnBE0BKCJkT1EkDCeJYYiDOkLDFFL5wBE4guCPDhEBEwQiDY70CkInDiQnCJzkhOwhKDdzp2Idb4nEE0B0Bdo4niE0J0CeYhOhgESUYYnidsgnEE0KeCE0gnDE0ciA")), + 6: require("heatshrink").decompress(atob("qFGwkCkQA/ABEgKQZPhEwgABEsAoGJkBxBE8JKEAowAbJIhQEgLDiPooAdKA4ncTZAndSwhQEFoInaJQkSKAwlZdgwnfSgYADE4h1ZDwInlcggnIOzAdCE8i7EY5J3XDgYhGd4pOZEI52bSYwGCOAJ2bYIodEOzZOFFAjFcEwwAIE6xOHABBO/J34ndEyx2PJ00BJ00SJ0p1XE54mXOxxO/J5wmYgQnMOrB2BPBgkWiJ1CPBbBYAYR4KiTAXRwIrFTjgZDJYZ4IEyoiEIwrDcEJJQFOqwiBDARxFFwgmXkAYDEogsBF4QmXEQJ7GUYYkBEzDKJAgYmdEQbKFEzonEKYgngJwgmfZggmjKQghgiBRGkBzeTgUikJRgc47LDErTnDEAkQJzkCJwYnEJzonEJIaddOwhJEJzgdBE4hYEJzieJADgnEE0KUCXzoAGkJLEiB2hOgQDBT0TsDT0YmlE4YmjkQ=")), + 7: require("heatshrink").decompress(atob("qFGwkCkQAhkIpBiQlhkBSEJ8InlEIIoFE7whEE8pQFE7giBJQoneI4MCTYhQDE7YdCYYondEQYnEPwZ1bE5BQCJzonHkR2ZEAkBE4pNBE7zHFYrYhFUgonaXAQeEEwruZEYcgiROHJ7AfDAwxOeAAURiAmHE65HIOzwmOJ35OPE6xOPO35O/J35O/J1gnPEyx2PEy5OOOq5OnE5xOYO5omZgJQMJrQnLiQnagR4JOq5nCDgZ1fEYRLDE5DoZkUQNoZ4GOrJKGAoomXOw7lCAwYmYDgJSEAAUBA4QDBJzB6FOQrDXJwTJFdLjJKE9jDYZRAmkKAwmhKAgmiKAYmBkApdJIgjCKYIncOQYvJYTovGE84lagR2DE4xOakBOEgJXFOjYnEJAbtdOwggEkAmbDgInDE0B0BE4QgcE5AkiXYbpCOLonGYo4nhPMYnCUEgnBY0kiA==")), + 8: require("heatshrink").decompress(atob("qFGwkCkQA/ABBSEJ8MgE4kBEsBPFE7xMCOIJ3hOYgFEE7rCGE70gE4pQBiAndYQwjBUohOZD4ZQFE7YkBE5AICYbZ2GE7sggJRCAA8iYzZOITroALE7EhExh4CAC0QExpPXOponZExx2XJ24nWdh52XdhzF/Yu5O/J35O0E55OXOx5O/J2omXE5x1XO54mYgQnMJrR4LOrciiAmiJgR4KEzIjDPBAlYiAiEeI51YkEBE4J5CD4KceTQQcBJgRQFdTZDCJIjDcNIqhGdTQmCkByFTTInDKgoAEE7ZEEJwhPdE1R1FE0InEE0R3DEwTGcDwomEE7hKFPYqafE8ROCE5DJbE5B/IEqh2ED4gnCJrMCJwgnEiB2bE4qeFEzUggQmIBQLEaEQImHLIImaE4YfcOw4lEFMLECS7onJO8wmkE4QljAAIA==")), + }; +} + +let rocket_sequence = 1; + +let settings = require('Storage').readJSON("cassioWatch.settings.json", true) || {}; +let rocketSpeed = settings.rocketSpeed || 700; +delete settings; + +g.clear(); + +function DrawClock() { + g.setFont("7x11Numeric7Seg", 3); + g.clearRect(80, 57, 170, 96); + g.setColor(0, 255, 255); + g.drawRect(80, 57, 170, 96); + g.fillRect(80, 57, 170, 96); + g.setColor(0, 0, 0); + g.drawString(require("locale").time(new Date(), 1), 70, 60); + g.setFont("8x12", 2); + g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130); + g.setFont("8x12"); + g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126); + g.setFont("8x12", 2); + const time = new Date().getDate(); + g.drawString(time < 10 ? "0" + time : time, 78, 137); +} + +function DrawBattery() { + bigThenSmall(E.getBattery(), "%", 135, 21); +} + +function DrawRocket() { + let Rocket = getRocketSequences(); + g.clearRect(5, 62, 63, 115); + g.setColor(0, 255, 255); + g.drawRect(5, 62, 63, 115); + g.fillRect(5, 62, 63, 115); + g.drawImage(Rocket[rocket_sequence], 5, 65, { scale: 0.7 }); + g.setColor(0, 0, 0); + rocket_sequence = rocket_sequence + 1; + if (rocket_sequence > 8) rocket_sequence = 1; +} + +function DrawScene() { + g.reset(); + g.clear(); + g.setColor(0, 255, 255); + g.fillRect(0, 0, g.getWidth(), g.getHeight()); + let background = getBackgroundImage(); + g.drawImage(background, 0, 0, { scale: 1 }); + g.setColor(0, 0, 0); + g.setFont("6x12"); + g.drawString("Launching Process", 30, 20); + g.setFont("8x12"); + g.drawString("ACTIVATE", 40, 35); + g.setFont("8x12", 2); + g.drawString("30", 142, 132); + g.drawString("55", 95, 98); + g.setFont("8x12", 1); + g.drawString(Bangle.getStepCount(), 143, 104); + ClockInterval = setInterval(DrawClock, 30000); + DrawClock(); + RocketInterval = setInterval(DrawRocket, rocketSpeed); + DrawRocket(); + BatteryInterval = setInterval(DrawBattery, 5 * 60000); + DrawBattery(); +} + +Bangle.on("lcdPower", (on) => { + if (!on) { + g.clear(); + ClearIntervals(true); + } +}); + +Bangle.on("lock", (locked) => { + if (locked) { + ClearIntervals(true); + } else { + ClearIntervals(); + DrawScene(); + } +}); + +g.reset(); +g.clear(); +Bangle.setUI("clock"); +DrawScene(); + +if (Bangle.isLocked()) { + ClearIntervals(true); +} \ No newline at end of file diff --git a/apps/cassioWatch/app.png b/apps/cassioWatch/app.png new file mode 100644 index 000000000..3f9bbb36e Binary files /dev/null and b/apps/cassioWatch/app.png differ diff --git a/apps/cassioWatch/icon.js b/apps/cassioWatch/icon.js new file mode 100644 index 000000000..4e4428f88 --- /dev/null +++ b/apps/cassioWatch/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lkswkGswAHtGIxGGBhATJAAYXNCYoWOFIwWNChQWKBYWYCxIqTxGJFgwnDnACBkUiCwuYFQo9ECYIAClAsJxIUElAUDwQWEyxAHBwITBmczmUiCwprHCgMjmUhiIYBA4JCGIAeCwQUBCYIXBiUyFggVCFQsziMjmdCkcxiZbBiJCEFQZUBmND93uolEochFgpWEIAUUCgIACp00iZVBzIVEAoJABmUeConu8cRiNEoMZNwIrCzEiiUxpwVF901IwNN6JuBtGJlAVBkchCg3umkSiMNCoIAEQIJWFkgCB8UYinUjIVFxEhKwszDAUU7tRCg2hilTH4xVB6kRzAUGBQIVECYVEorEDAAeBptBiUenw7BCYQACieCCosd6MYwUVBwNUAQYvBeYOJCYVoxVeoK5BAAq0C8MjiM4LALFCilNCAQpCN4lBmUoFQQVCxTlBiMieI3uqUoagIVDRAeCkclCgvlkciFYdmsxwDlESmTdE8lRmSCECosiFgMhqgUDkZABBwWYCoNpIIYXBmchiKLBkYqBNggVBNwIsECwMikQUBlEiBgWJCoRCEEQITDNIJrEIAQABZYOYnIWBFoQUGIAZCCTYYWCAAQUExIUDFggNDAA5AEFg4AIIAhvGEYU4ChgWHAAoUIWYpdFFRIWHChxEICZoWFFBIA==")) diff --git a/apps/cassioWatch/metadata.json b/apps/cassioWatch/metadata.json new file mode 100644 index 000000000..70cd9c242 --- /dev/null +++ b/apps/cassioWatch/metadata.json @@ -0,0 +1,18 @@ +{ + "id": "cassioWatch", + "name": "Cassio Watch", + "description": "Animated Clock with Space Cassio Watch Style", + "screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }], + "icon": "app.png", + "version": "0.9", + "type": "clock", + "tags": "clock, weather, cassio, retro", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "readme": "README.md", + "storage": [ + { "name": "cassioWatch.app.js", "url": "app.js" }, + {"name":"cassioWatch.settings.js","url":"settings.js"}, + { "name": "cassioWatch.img", "url": "icon.js", "evaluate": true } + ] +} diff --git a/apps/cassioWatch/screens/screen_day.png b/apps/cassioWatch/screens/screen_day.png new file mode 100644 index 000000000..ba150b4f7 Binary files /dev/null and b/apps/cassioWatch/screens/screen_day.png differ diff --git a/apps/cassioWatch/screens/screen_night.png b/apps/cassioWatch/screens/screen_night.png new file mode 100644 index 000000000..4055e0943 Binary files /dev/null and b/apps/cassioWatch/screens/screen_night.png differ diff --git a/apps/cassioWatch/settings.js b/apps/cassioWatch/settings.js new file mode 100644 index 000000000..b07c6c58f --- /dev/null +++ b/apps/cassioWatch/settings.js @@ -0,0 +1,24 @@ +(function(back) { + var FILE = "cassioWatch.settings.json"; + var settings = Object.assign({ + rocketSpeed: 700, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + + E.showMenu({ + "" : { "title" : "Cassio Watch" }, + "< Back" : () => back(), + 'Rocket Speed': { + value: 0|settings.rocketSpeed, + min: 100, max: 60000, + onchange: v => { + settings.rocketSpeed = v; + writeSettings(); + } + }, + }); + }) \ No newline at end of file diff --git a/apps/football/ChangeLog b/apps/football/ChangeLog new file mode 100644 index 000000000..9b91672a5 --- /dev/null +++ b/apps/football/ChangeLog @@ -0,0 +1 @@ +1.00: Initial implementation diff --git a/apps/football/README.md b/apps/football/README.md new file mode 100644 index 000000000..f751b927e --- /dev/null +++ b/apps/football/README.md @@ -0,0 +1,3 @@ +# Classic Football Chronometer Game + +Context: https://www.anaitgames.com/analisis/analisis-casio-football-14 diff --git a/apps/football/app-icon.js b/apps/football/app-icon.js new file mode 100644 index 000000000..7eec578c6 --- /dev/null +++ b/apps/football/app-icon.js @@ -0,0 +1 @@ +atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAA/wAAAAAADwAAAAAADwAAAAAADwAAAAAADwAAAAAA//AAAAAA//AAAAAA//AAAAAA//AAAAAADw8AD/AADw8AD/AADw8AD/AADw8AD/AP/wAAD/AP/wAAD/AP/wAAD/AP/wAAD/AA8PAAAAAA8PAAAAAA8PAAAAAA8PAAAAAAAA8AAAAAAA8AAAAAAA8AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") diff --git a/apps/football/app.js b/apps/football/app.js new file mode 100644 index 000000000..8350bea88 --- /dev/null +++ b/apps/football/app.js @@ -0,0 +1,440 @@ +const digit = []; + +const dash = { + width: 75, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4A/AH4A/AH4A/AH4A/AB0D/4AB+AJEBAX/BAk/CQ8PCQ4kDCQoIDCQgkDCQgkECQgIE4ASHFxH8JRgSEEgYSEPJAkEAH4A/AH4A/AH4A/AH4A/ACg=')) +}; +function loadDigits () { + digit[0] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4AGn//AAngBIMfBIvABIMPBIuABIMHBIoIBg0DBAn+gYSBgIJE/kHBIOABIn4h4JB4F/BIfwj4JB8BQEAoIJBBoJOEv4JBEIJOEIwMHGoIJDIIIJBJIJOEBIQOCJwYJDOIR9DBISFCSIYJCTISlDBIXwBIZoBBP4J/BP4J/BNX+gED//gBIc/BIMB//ABIcf/gDB/+ABIcP/AhCBAYuBFoU+BIkDFoUcBIkBFoUIBIkAFogA/AAZPJMZJ3JRZKfIWZLHJbZL5bBP4J/BP4J/BKPgBIc/BIfABIcfBIeA/4AB/EPBIcBBIX8AwIJB/0DBIQECBIIOCAAQYBBIIiCAAQsBBIPwGwIAC4F/BIPgJQIACAoIJBBoIJDDIIJBJwZQDBIJODKAcAgxODKAZxBJwgABPYROEKASFDAAiRCJwhQCTYYAkA')) + }; + + digit[1] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4A2wAIHgIJIgYJIg4JIh4JIj4JIn4JIv4JI/4JHgIJIgYJIg4JIh4JIj4JIn4J/BP4J/BP4Jqj//BA0Ah/+BI8H/gJHgf4BI8B+AJHgHgBJFABJAA/J55jIO5KLJT5KzJY5L5nBP4J/BP4J/AAcfBJEPBJEHBJEDBJEBBJEABJN/BJE/BJEfBJEPBJEHBJEDBJEBBJEABJIA/AAwA=')) + }; + + digit[2] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4AGj//AAnwBIMPBIvgBIMHBIvABIMDBIuABIMBBIsBGQQIE/0DBIV/BIf8g4JCn4JD/EPKA/wj4JCKAngn4JCKAnAv4JCKAmA/4JCKAgEBQYZOEBIgADFgIJHIAKlJBI5oBBP4J/BP4J/BOcfBJEP/wJHg/8Aof/AAP+gf4BAUBBIX/gPwBIUDBIeA8AiDBIfAoA2DBIYSDJQQACEwZeCAAQ6DgF/BJATJE5I7IghPFBIUOMYomDO4g6EwCLDJwgiDAAhyFTohKEToheEBP4J/BP4J/BOHwBJHgBJHAv////8BImABAP//wJEAIIACBIf+BImABIX8g4JD4AJC/EPBIZACgfwj4JDKgUD8E/BIZoCgZODKAkDJwZQEgcBBIhQCgROEKAhOEKAhOEKAhOEKAgAm')) + }; + + digit[3] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4AGj//AAnwBIMPBIvgBIMHBIvABIMDBIuABIMBBIsBGQQIE/0DBIV/BIf8g4JCn4JD/EPKA/wj4JCKAngn4JCKAnAv4JCKAmA/4JCKAgEBQYZOEBIgADFgIJHIAKlJBI5oBBI58BBP4J/BP4J/BL8/BJEf/wJHh/8BI8H/AFD/4AB/0D+AICgIJDgPgBIUDBIQ5B4AiDBIeAwA2DBIYSDJQIJDEwZeCAAQ6DOQQACJwgTJE5I7JJ5JjEgIUDO4kDFAgJC/kDIwipNj4JIn7HIbZL5TBP4J/BP4J/BJs/BJEfBJEPBIgjB//8g4JDgIIB//+gYJDAgIACBwIJCDAIACwAJDFgIAC4F/IAgAC8E/KggAC+EfIgoAB/EPBIQIDKAROFKAZOGKAROGKAQJI4BOGKAQJI+CfHAEAA==')) + }; + digit[4] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AH4AswEBBJOABAoHBgPABIsDBIPgBIsHBIPwBIsPBIP4BIsfBIP8BIs/BIP+BIt/BIP/BIv/BIRQEAwQCBKAkDBIZQEg4JDKAkPBIZQEj4JIn4J/BP4J/BP4JjgAJFj//AYN/8AJDh/+C4QJEg/8C4XAv////+gYjCh+ABAIABgPwC4Q9BAAWAEYUCgYJD4FAFgYJDIAoJDEwRUDAARoGAAROCCZYnJHZJPJMZAABO46hCRYwAFT4YAFWYYAFY4YAFbYYJ/BP4J/BP4Jnj4JIh4JIg4JIgYJIgIJIgAJJv4JIn4JIj4JIh4JIg4JIgYJIgIJIgAJJAH4AGA=')) + }; + + digit[5] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGUP/4AE/gJBg4JF/AJBgYJF+AJBgIJF8AJBwAJF4AJB4F/BImABIPgn4JEIoXwj4ID/wJC/BQEJwUA/hQEJwUA/xQEJwUA/5QEJwQJBKAhOCBIJQEJwQJBKAiVDFggAEIAgJFKgYJFNAYJ/BP4J/BP4Jmv/8BI8//AJHj/wBI8P8E//4ABBIcH4F/BIWABIUDwAIC//ABIUBgIJD8AeDgYJDGwkHBIZKEh4JDLwkfBIZyECZInJHZJPFkChEMYdwUIh3DFAiLDgKvIgbDIJQKvIUIgJFUIZ8FBP4J/BP4J/BL8PBJEHBJEDBJEBBIl//4ABwAJEBAQHBv4JCDAIAC8E/BIQsBAAXwj4JCIAIAC/EPBIRUBAAX8g4JCNAIAC/0DBI//gIJCJwZQCBIQIEKANAJwpQCJwxQCJwxQCJwxQCBJYAwA=')) + }; + + digit[6] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGU//4AE8AJBj4JF4AJBh4JFwAJBg4JFBAMGgYIE/wSCgIJE/gJCwAJE/AJC4F/BIfwBIXgKAhOCg/wKAhOCg/4KAhOD/hQEOwUH/xQDJwRiCKAZOCBIRQDJwQJCGwQAEBIJKCBIxeCBP4J/BP4J/BOED//gBI0B//ABI0A/+ABI9/CoIAB/gJDnwpBAAP+BIccHoIACBIcIh4JDFgkfBIZAEBIhUEv4JDNAgJE/ATNn4nIHZBPGKARjFgIUCO4sDFASLFg48COQsPKARyGUILHGn6hBBIJyGco4J/BP4J/BP4Jm8AJDn4JD4AJDj4JDwAJDh4JDgP/AAP8AwIJB/0DBIQECBIIOCAAQYBBIP4EQIACwAJC+A2BAAXAv4JB8BKBAAQFBBIINBBIYZBBIIhBAAYtBBIJODKAcAgxODKAZnBJwhQCOIYAEPoROEKASZDAAilCJwhQCTYYAuA==')) + }; + + digit[7] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGUH/4AE/wJBgYJF/gJBgIJF+AeCBJN/BIngsAJBn4JE4HgBIMfBImBBIUPBIkDBIRQE/0HBIRQE/kPBIRQE/EfBIRQE+E/BIZQD8AJEKAfABYIJCKAYsBBIYADIAIJHKgIJHNAIJ/BP4J/BP4Jzg//4AJGgf/wAJGgP/BAwAB/wJIvgJInAJIiAJIAH5PPMZJ3JRZCfJWZLHJfM4J/BP4J/BP4JNg4JIgYJIgIJIgAJJv4JIn4JIj4JIh4JIeg4JIgYJIgIJIgAJJsAJIkAJIAH4AQA=')) + }; + + digit[8] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGUf/4AE+AJBh4JF8AJBg4JF4AJBgYJFwAIBgIJFgOAgeABAn+A4MD4F/BIf8g4JB8E/BIf4h4JB+BQEAoIJBBoJOEn4JBEIJOEv4JBGoJOEAIIHBKAgEBBIRQDDAQJCKAYsCBISFCSIYJCTISlDBIX4BIZoBBP4J/BP4J/BNkB//wBIcf/4DB//gBIcP/wDBv/ABIcH/ghCwH/AAP+gYtCj4pBAAUBFoUOHoIACwAtCgkHBIfAoA2DBIZAEJQIACKgheBAARoGBKInJHZBPJMZJ3JRZKfJWZDHJfM4J/BP4J/BP4JP+AJDj4JD8AJDh4JD4AJDg4JDwH/AAP+AwQCBgIJCAgQJBBwQACDAIJB/giBAAXAv4JB/A2BAAXgn4JB+BKBAAQFBBIINBBIYZBBIIhBBIYtBBIJODKAYJBJwhQCwECJwhQCwBxCAAh9CJwhQCTIYAEUoROEKASbDAFwA=')) + }; + + digit[9] = { + width: 80, + height: 128, + bpp: 1, + buffer: require('heatshrink').decompress(atob('AGUP/4AE/gJBg4JF/AJBgYJF+AJBgIJF8AJBwAJF4FAgHAv4JEwHAgHgn4JEgIJB+EfBAf+gYJB/BQE/kHBIIDBJwkPBIIXBJwkfBIIrBJwk/BIRQEJYIJCKAgOBBIXgIwYiBBIR7CQ4YJCR4SbDBISjCV4YJC/wJDFYIJ/BP4J/BP4Jjv/8BIcP/+AgE//AJDg//C4XwBIcDEYUP8E//4ABgIjCg/Av4JCwAjCgeABAQ5BuAJBgMBBIfgkAsDBIY2EIAIACJQhUBAAReENAIACOQgTJE5I7JJ5KhBMYwABO44ABRY4AFT4YAFWYYAFY4YAFbYYJ/BP4J/BP4Jnh4JIg4JIgYJIgIJEv//AAOABIgICA4N/BIQYBAAXgn4JCFgIAC+EfBIRABAAX4h4JCKgIAC/kHBIRoBAAX+gYJCn4JD/8BBIRODKAQJCBAhQBoBOFKAROGKAROGKAROGKAQJLAGA=')) + }; +} + +// sprites + +const left0 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ADAwEDgUcCgEAA==') +}; + +const left1 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ADAwEDh0ECAoAA==') +}; + +const left2 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ADAwEBg4EBg0AA==') +}; + +const left4 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ABgYVDgQEChEAA==') +}; + +const right0 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAwMCBwoDhQgAA==') +}; + +const right1 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAwMCBwuCAQUAA==') +}; + +const right2 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAwMCBgcCBgsAA==') +}; + +const right4 = left4; + +const ball0 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAwMAAAAA==') +}; + +const ball1 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('AAAAAAAAGBgAAA==') +}; +const gol01 = { + width: 8, + height: 10, + bpp: 1, + transparent: 0, + buffer: atob('ABhkhIS0sIAAAA==') +}; + +const gol11 = { + width: 8, + height: 10, + bpp: 1, + buffer: require('heatshrink').decompress(atob('gEYk0hkMthsBBAI=')) + +}; + +loadDigits(); + +let goalFrame = 0; +let score0 = 0; +let score1 = 0; + +function printNumber (n, x, y, options) { + if (n > 9 || n < -1) { + console.log(n); + return; // error + } + if (digit.length === 0) { + g.setColor(1, 1, 1); + if (options.scale == 0.2) { + g.setFont12x20(1); + } else { + g.setFont12x20(2.5); + } + g.drawString('' + n, x, y); + return; + } + const img = (n == -1) ? dash : digit[n]; + if (img) { + // g.setColor(0,0,0); + // g.fillRect(x,y,x+32*options.scale,64*options.scale); + g.setColor(1, 1, 1); + g.drawImage(img, x, y, options); + } +} +let inc = 0; +let msinc = 0; +let seq0 = 0; +let seq1 = 0; +let goaler = -1; +const w = g.getWidth(); +let owner = -1; +g.setBgColor(0, 0, 0); +g.clear(); +g.setColor(1, 1, 1); +function onStop () { + if (goalFrame > 0) { + return; + } + stopped = !stopped; + if (stopped) { + // Bangle.beep(); + if (msinc == 0) { + // GOOL + if (owner == 0) { + score0++; + goaler = 0; + } else if (owner == 1) { + score1++; + goaler = 1; + } + goalFrame = 5; + } + let newOwner = 0; + if (msinc % 2) { + newOwner = 1; + } else { + newOwner = 0; + } + if (newOwner) { + seq0--; + seq1++; + } else { + seq0++; + seq1--; + } + if (seq0 < 0) seq0 = 0; + if (seq0 > 3) seq0 = 3; + if (seq1 < 0) seq1 = 0; + if (seq1 > 3) seq1 = 3; + owner = newOwner; + } + refresh(); + refresh_ms(); +} +var stopped = true; +Bangle.on('tap', function (pos) { + console.log('touch', pos); + if (endGame) { + Bangle.beep(); + score0 = 0; + score1 = 0; + seq0 = 0; + seq1 = 0; + inc = 0; + msinc = 0; + stopped = true; + endGame = false; + } else { + if (inc == 0) { + autogame(); + } else { + onStop(); + } + } +}); +g.setFont12x20(3); +let part = 0; +let endInc = 0; +var endGame = false; +function refresh () { + g.clear(); + if (inc > 59) { + inc = 0; + part++; + } + if (inc > 44) { + if (part < 2) { + part++; + } + if (part >= 2) { + if (score0 != score1) { + endGame = true; + endInc = inc; + inc = 0; + } + } + // end of 1st or 2nd part of the game? + } + let two = (inc < 10) ? '0' + inc : '' + inc; + if (endGame) { + g.setColor(0, 0, 0); + g.fillRect(0, 64, w, h); + if (inc % 2) { + two = (endInc < 10) ? '0' + endInc : '' + endInc; + printNumber(-1, 2, 64 + 16, { scale: 0.4 }); + printNumber(part, 34, 64 + 16, { scale: 0.4 }); + printNumber(two[0], 74, 64 + 16, { scale: 0.4 }); + printNumber(two[1], 74 + 32, 64 + 16, { scale: 0.4 }); + } + } else { + // seconds + printNumber(0, 2, 64 + 16, { scale: 0.4 }); + printNumber(part, 34, 64 + 16, { scale: 0.4 }); + printNumber(two[0], 74, 64 + 16, { scale: 0.4 }); + printNumber(two[1], 74 + 32, 64 + 16, { scale: 0.4 }); + } + refresh_ms(); + refresh_score(); + refresh_pixels(); +} + +function refresh_pixels () { + let frame4 = inc % 2; + if (goalFrame > 0) { + frame4 = goalFrame % 2; + if (goaler == 0) { + g.drawImage(frame4 ? right4 : right0, 20, 10, { scale: 5 }); + g.setColor(1, 1, 1); + g.drawImage(gol11, w - 50, 10, { scale: 5 }); + } else if (goaler == 1) { + g.drawImage(frame4 ? left0 : left4, w - 50, 10, { scale: 5 }); + g.setColor(1, 1, 1); + g.drawImage(gol01, 30, 10, { scale: 5 }); + } + return; + } + if (endGame) { + if (score0 > score1) { + g.drawImage(frame4 ? right1 : right0, 5 + (seq0 * 10), 10, { scale: 5 }); + } else if (score0 < score1) { + g.drawImage(frame4 ? left0 : left1, w - 30 - (seq1 * 10), 10, { scale: 5 }); + } + return; + } + g.drawImage(frame4 ? right1 : right0, 5 + (seq0 * 10), 10, { scale: 5 }); + g.drawImage(frame4 ? left0 : left1, w - 30 - (seq1 * 10), 10, { scale: 5 }); + let bx = (owner == 0) ? w / 3 : w / 2; + bx += 2; + g.drawImage(frame4 ? ball0 : ball1, bx, 10, { scale: 5 }); +} +let dots = 0; +function refresh_dots () { + if (endGame) { + dots = 0; + } else { + dots++; + } + if (dots % 2) { + g.setColor(1, 1, 1); + } else { + g.setColor(0, 0, 0); + } + const x = 67; + let y = 100; + g.fillRect(x, y, x + 4, y + 4); + y += 16; + g.fillRect(x, y, x + 4, y + 4); +} + +const h = g.getHeight(); + +function refresh_ms () { + if (endGame) { + if (inc % 2) { + printNumber(-1, 80 + 64 - 4, 64 + 16 + 8 + 16, { scale: 0.2 }); + printNumber(-1, 80 + 64 + 16 - 4, 64 + 32 + 8, { scale: 0.2 }); + } + return; + } + // nanoseconds + if (msinc > 59) { + msinc = 0; + } + g.setColor(0, 0, 0); + g.fillRect(80 + 64, h / 2, 80 + 64 + 32, g.getHeight()); + const two = (msinc < 10) ? '0' + msinc : '' + msinc; + printNumber(two[0], 80 + 64 - 4, 64 + 16 + 8 + 16, { scale: 0.2 }); + printNumber(two[1], 80 + 64 + 16 - 4, 64 + 32 + 8, { scale: 0.2 }); +} + +function refresh_score () { + g.setColor(0, 0, 0); + g.fillRect(0, h - 32, w, h); + let two = (score0 < 10) ? '0' + score0 : '' + score0; + printNumber(two[0], 64 - 16, 32 + 64 + 16 + 8 + 16, { scale: 0.2 }); + printNumber(two[1], 64, 32 + 64 + 32 + 8, { scale: 0.2 }); + two = (score1 < 10) ? '0' + score1 : '' + score1; + printNumber(two[0], 32 + 64, 32 + 64 + 16 + 8 + 16, { scale: 0.2 }); + printNumber(two[1], 32 + 64 + 16, 32 + 64 + 32 + 8, { scale: 0.2 }); +} +refresh(); + +setInterval(function () { + if (!stopped || endGame) { + inc++; + } + if (goalFrame > 0) { + goalFrame--; + } + refresh(); +}, 1000); + +setInterval(function () { + refresh_dots(); +}, 500); + +setInterval(function () { + if (!stopped) { + msinc++; + if (msinc > 59) { + msinc = 0; + } + } +}, 10); + +setInterval(function () { + if (!stopped) { + refresh_ms(); + } +}, 250); + +function autogame () { + if (endGame) { + return; + } + onStop(); + if (stopped) { + setTimeout(autogame, 500); + } else { + setTimeout(autogame, 315 + 10 * (Math.random() * 5)); + } +} + +Bangle.setOptions({ lockTimeout: 0, backlightTimeout: 0 }); +autogame(); diff --git a/apps/football/app.png b/apps/football/app.png new file mode 100644 index 000000000..80d7cea15 Binary files /dev/null and b/apps/football/app.png differ diff --git a/apps/football/media/ball0.png b/apps/football/media/ball0.png new file mode 100644 index 000000000..5b890c180 Binary files /dev/null and b/apps/football/media/ball0.png differ diff --git a/apps/football/media/ball1.png b/apps/football/media/ball1.png new file mode 100644 index 000000000..c72e56189 Binary files /dev/null and b/apps/football/media/ball1.png differ diff --git a/apps/football/media/dash.png b/apps/football/media/dash.png new file mode 100644 index 000000000..6a9b0c4ac Binary files /dev/null and b/apps/football/media/dash.png differ diff --git a/apps/football/media/digit0.png b/apps/football/media/digit0.png new file mode 100644 index 000000000..33856cc5e Binary files /dev/null and b/apps/football/media/digit0.png differ diff --git a/apps/football/media/digit1.png b/apps/football/media/digit1.png new file mode 100644 index 000000000..53b914ded Binary files /dev/null and b/apps/football/media/digit1.png differ diff --git a/apps/football/media/digit2.png b/apps/football/media/digit2.png new file mode 100644 index 000000000..7a7787b05 Binary files /dev/null and b/apps/football/media/digit2.png differ diff --git a/apps/football/media/digit3.png b/apps/football/media/digit3.png new file mode 100644 index 000000000..a197d5993 Binary files /dev/null and b/apps/football/media/digit3.png differ diff --git a/apps/football/media/digit4.png b/apps/football/media/digit4.png new file mode 100644 index 000000000..f2810a0b6 Binary files /dev/null and b/apps/football/media/digit4.png differ diff --git a/apps/football/media/digit5.png b/apps/football/media/digit5.png new file mode 100644 index 000000000..d8027c362 Binary files /dev/null and b/apps/football/media/digit5.png differ diff --git a/apps/football/media/digit6.png b/apps/football/media/digit6.png new file mode 100644 index 000000000..bd7980045 Binary files /dev/null and b/apps/football/media/digit6.png differ diff --git a/apps/football/media/digit7.png b/apps/football/media/digit7.png new file mode 100644 index 000000000..9ef0df11a Binary files /dev/null and b/apps/football/media/digit7.png differ diff --git a/apps/football/media/digit8.png b/apps/football/media/digit8.png new file mode 100644 index 000000000..6916a301a Binary files /dev/null and b/apps/football/media/digit8.png differ diff --git a/apps/football/media/digit9.png b/apps/football/media/digit9.png new file mode 100644 index 000000000..d8d327523 Binary files /dev/null and b/apps/football/media/digit9.png differ diff --git a/apps/football/media/digits.png b/apps/football/media/digits.png new file mode 100644 index 000000000..68ace56af Binary files /dev/null and b/apps/football/media/digits.png differ diff --git a/apps/football/media/digits128.png b/apps/football/media/digits128.png new file mode 100644 index 000000000..f363a7e8e Binary files /dev/null and b/apps/football/media/digits128.png differ diff --git a/apps/football/media/digits64.png b/apps/football/media/digits64.png new file mode 100644 index 000000000..445c14dfa Binary files /dev/null and b/apps/football/media/digits64.png differ diff --git a/apps/football/media/gol00.png b/apps/football/media/gol00.png new file mode 100644 index 000000000..3b16aa967 Binary files /dev/null and b/apps/football/media/gol00.png differ diff --git a/apps/football/media/gol01.png b/apps/football/media/gol01.png new file mode 100644 index 000000000..3b16aa967 Binary files /dev/null and b/apps/football/media/gol01.png differ diff --git a/apps/football/media/gol10.png b/apps/football/media/gol10.png new file mode 100644 index 000000000..178b6fe3d Binary files /dev/null and b/apps/football/media/gol10.png differ diff --git a/apps/football/media/gol11.png b/apps/football/media/gol11.png new file mode 100644 index 000000000..732fa815d Binary files /dev/null and b/apps/football/media/gol11.png differ diff --git a/apps/football/media/left0.png b/apps/football/media/left0.png new file mode 100644 index 000000000..20599cbb7 Binary files /dev/null and b/apps/football/media/left0.png differ diff --git a/apps/football/media/left1.png b/apps/football/media/left1.png new file mode 100644 index 000000000..b6ffc22f9 Binary files /dev/null and b/apps/football/media/left1.png differ diff --git a/apps/football/media/left2.png b/apps/football/media/left2.png new file mode 100644 index 000000000..11ff8885f Binary files /dev/null and b/apps/football/media/left2.png differ diff --git a/apps/football/media/left4.png b/apps/football/media/left4.png new file mode 100644 index 000000000..a7301a4f8 Binary files /dev/null and b/apps/football/media/left4.png differ diff --git a/apps/football/media/right0.png b/apps/football/media/right0.png new file mode 100644 index 000000000..ac418ad7b Binary files /dev/null and b/apps/football/media/right0.png differ diff --git a/apps/football/media/right1.png b/apps/football/media/right1.png new file mode 100644 index 000000000..31554cbc3 Binary files /dev/null and b/apps/football/media/right1.png differ diff --git a/apps/football/media/right2.png b/apps/football/media/right2.png new file mode 100644 index 000000000..8c8d0ece4 Binary files /dev/null and b/apps/football/media/right2.png differ diff --git a/apps/football/media/right4.png b/apps/football/media/right4.png new file mode 100644 index 000000000..83a78b52c Binary files /dev/null and b/apps/football/media/right4.png differ diff --git a/apps/football/metadata.json b/apps/football/metadata.json new file mode 100644 index 000000000..253026c39 --- /dev/null +++ b/apps/football/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "football", + "name": "football", + "shortName": "football", + "version": "1.00", + "type": "app", + "description": "Classic football game of the CASIO chronometer", + "icon": "app.png", + "allow_emulator": true, + "tags": "games", + "supports": [ + "BANGLEJS2" + ], + "readme": "README.md", + "storage": [ + { + "name": "football.app.js", + "url": "app.js" + }, + { + "name": "football.img", + "url": "app-icon.js", + "evaluate": true + } + ], + "screenshots": [ + { + "url": "screenshot.png" + } + ] +} diff --git a/apps/football/screenshot.png b/apps/football/screenshot.png new file mode 100644 index 000000000..5742fe9e1 Binary files /dev/null and b/apps/football/screenshot.png differ diff --git a/apps/iconlaunch/app.js b/apps/iconlaunch/app.js index 4eeaff589..59e9eb0e3 100644 --- a/apps/iconlaunch/app.js +++ b/apps/iconlaunch/app.js @@ -158,6 +158,8 @@ const itemsN = Math.ceil(apps.length / appsN); Bangle.setUI({ mode: "custom", drag: (e) => { + g.setColor(g.theme.fg); + g.setBgColor(g.theme.bg); let dy = e.dy; if (scroll + R.h - dy > itemsN * itemSize) { dy = scroll + R.h - itemsN * itemSize; diff --git a/apps/imageclock/ChangeLog b/apps/imageclock/ChangeLog new file mode 100644 index 000000000..5b99b5848 --- /dev/null +++ b/apps/imageclock/ChangeLog @@ -0,0 +1,9 @@ +0.01: New App +0.02: Allow drawing polys +0.03: Allow partly importing Amazfit decompiler formatted watchfaces +0.04: Allow writing all image data to separate file to save some RAM + Allow hiding elements on lock +0.05: Add precompilation to js for performance +0.06: Watchfaces can be refreshed partly +0.07: Allow wrapping drawing in timeouts to get faster reactions + Show/Hide widgets with swipe up or down diff --git a/apps/imageclock/README.md b/apps/imageclock/README.md new file mode 100644 index 000000000..c05c09a16 --- /dev/null +++ b/apps/imageclock/README.md @@ -0,0 +1,297 @@ +# Imageclock + +This app is a highly customizable watchface. To use it, you need to select +a watchface from another source. There is a native format as described here. You can also load decompiled watchfaces for Amazfit BIP fitness trackers. + +# Usage + +## Install a watchface + +Choose the folder which contains the watchface, then clock "Upload to watch". + +## Usage on the watch + +Slide up/down to hide/show widgets. +Press button to start launcher. + +# Design watch faces + +## Folder structure + +* watchfacename + * resources/ + * face.json + * info.json + + +### resources + +This folder contains image files. It can have subfolders. These files will +be read and converted into a resource bundle used by the clock + +Folder types: + +* Number + * Contains files named 0.ext to 9.ext and minus.ext +* CodedImage + * Contains files named with only digits for codes, i.e. 721.ext + * Contains a file named fallback.ext in case no code matches + * Codes are evaluated as follows: 721 -> if not found check 720 -> if not found check 700 -> if found use +* MultiState + * Notifications: sound.ext, silent.ext, vibrate.ext + * other status icons: on.ext, off.ext +* Scale + * Contains the components of the scale, named 0.ext to y.ext, y beeing the last element of the scale + +### face.json + +This file contains the description of the watch face elements. + +#### Object types: + +##### Properties +``` +Properties: { + "Redraw": { + "Unlocked": 5000, + "Locked": 60000, + "Default": "Always", + "Events": ["HRM"], + "Clear": true + }, + "Events": ["lock","HRM"] +} +``` + +Possible values for `Default` are `Always`, `Change`. + +##### Images + +``` +"Image": { + "X": 0, + "Y": 0, + "Scale": 1, + "RotationValue": "Seconds", + "MinRotationValue": 0, + "MaxRotationValue": 60, + "ImagePath": [ "path", "in", "resources", "file" ] +} +``` + +`RotationValue` references one of the implemented numerical values. +Mandatory: +* `ImagePath` + +``` +"Image": { + "X": 0, + "Y": 0, + "Value": "BatteryPercentage", + "Steps": 7, + "ImagePath": [ "path", "in", "resources", "file" ] +} +``` +If `Value` and `Steps`are given, the value is scaled and `Steps` number of images starting at 0 are used to display the value. + +##### Coded Images +``` +"CodedImage": { + "X": 0, + "Y": 0, + "Value": "WeatherCode", + "ImagePath": [ "path", "in", "resources", "file" ] +} +``` +The `Value` field is one of the implemented numerical values. + +##### Number + +Can be aligned to bottom left, top left, bottom right, top right and center. Will currently force all numbers to +be integer. + +``` +"Number": { + "X": 123, + "Y": 123, + "Alignment": "BottomRight", + "Value": "Temperature", + "Spacing": 1, + "ImagePath": [ "path", "to", "numbers", "folder" ] +} +``` +The `Value` field is one of the implemented numerical values. +`Alignment` is either `BottomRight` or `TopLeft` + +Mandatory: +* `ImagePath` +* `Value` + +##### Scale + +``` +"Scale": { + "X": 123, + "Y": 123, + "Value": "Temperature", + "MinValue": "-20", + "MaxValue": "50", + "ImagePath": [ "path", "to", "scale", "folder" ], + "Segments": [ + { "X": 5, "Y": 5}, + { "X": 10, "Y": 10 } + ] +} +``` +The `Value` field is one of the implemented numerical values. +`MaxValue` and `MinValue` set the start and endpoints of the scale. + +Mandatory: +* `ImagePath` +* `Value` + +##### MultiState + +``` +"MultiState": { + "X": 0, + "Y": 0, + "Value": "Lock", + "ImagePath": ["icons", "status", "lock"] +} +``` +The `Value` field is one of the implemented multi state values. + +Mandatory: +* `ImagePath` +* `Value` + +##### Poly + +``` +"Poly":{ + "Filled": true, + "RotationValue": "Second", + "MinRotationValue": "0", + "MaxRotationValue": "60", + "ForegroundColor": "#00f", + "BackgroundColor": "#008", + "Vertices":[ + {"X":-1, "Y":13}, + {"X":0, "Y":15}, + {"X":1, "Y":13}, + {"X":2, "Y":0}, + {"X":1, "Y":-75}, + {"X":0, "Y":-80}, + {"X":-1, "Y":-75}, + {"X":-2, "Y":0} + ] +} +``` +The `RotationValue` field is one of the implemented numeric values. + +##### Rect + +``` +"Rect":{ + "X": 10, + "Y": 20, + "Width": 30, + "Height": 40, + "Filled": true, + "ForegroundColor": "#00f", + "BackgroundColor": "#008" +} +``` +The `RotationValue` field is one of the implemented numeric values. + +##### Nesting +``` +"Container": { + "X": 10, + "Y": 10, + "OtherContainer": { + "X": 5, + "Y": 5, + "SomeElement": { + "X": 2, + "Y": 2, + + } + } +} +``` +`SomeElement` will be drawn at X- and Y-position 2+5+10=17, because positions are relative to parent element. +Container names can be everything but other object names. + +#### Implemented data sources + +##### Numerical + +* Hour +* Hour12Analog +* Hour12 +* HourTens +* HourOnes +* Minute +* MinuteAnalog +* MinuteTens +* MinuteOnes +* Second +* SecondAnalog +* SecondTens +* SecondOnes +* WeekDay +* WeekDayMondayFirst +* Day +* DayTens +* DayOnes +* Month +* MonthTens +* MonthOnes +* Pulse +* Steps +* Temperature +* Pressure +* Altitude +* BatteryPercentage +* BatteryVoltage +* StepsGoal +* WeatherCode +* WeatherTemperature + +##### Multistate + +* on/off + * Lock + * Charge + * Alarm + * Bluetooth + * BluetoothPeripheral + * HRM + * Barometer + * Compass + * GPS + * StepsGoal + * WeatherTemperatureNegative +* on/off/vibrate + * Notifications +* celsius/fahrenheit/unknown + * WeatherTemperatureUnit + +### info.json + +This file contains information for the conversion process, it will not be +stored on the watch + +# TODO + +* Handle events and redraws better +* Performance improvements + * Mark elements with how often they need to be redrawn +* Finalize the file format +* Localization + +# Creator + +[halemmerich](https://github.com/halemmerich) diff --git a/apps/imageclock/app-icon.js b/apps/imageclock/app-icon.js new file mode 100644 index 000000000..06f93e2ef --- /dev/null +++ b/apps/imageclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCEBAU/AQP4gfAj8AgPwAoMPwED8AFBg/AAYIBDA4ngg4TB4EBApkPKgJSBJQIFTMgIFCJIIFDKoIFEvgFBGoMAnw7DP4IFEh+BAoItBg+DNIQwBMIaeCKoKxCPoIzCEgKVHUIqtFXIrFFaIrdFdIwAV")) diff --git a/apps/imageclock/app.js b/apps/imageclock/app.js new file mode 100644 index 000000000..7b933b710 --- /dev/null +++ b/apps/imageclock/app.js @@ -0,0 +1,774 @@ +var watchface = require("Storage").readJSON("imageclock.face.json"); +var watchfaceResources = require("Storage").readJSON("imageclock.resources.json"); +var precompiledJs = eval(require("Storage").read("imageclock.draw.js")); +var settings = require('Storage').readJSON("imageclock.json", true) || {}; + +var performanceLog = {}; + +var startPerfLog = () => {}; +var endPerfLog = () => {}; +var printPerfLog = () => print("Deactivated"); +var resetPerfLog = () => {performanceLog = {};}; + +var colormap={ +"#000":0, +"#00f":1, +"#0f0":2, +"#0ff":3, +"#f00":4, +"#f0f":5, +"#ff0":6, +"#fff":7 +}; + +var palette = new Uint16Array([ +0x0000, //black #000 +0x001f, //blue #00f +0x07e0, //green #0f0 +0x07ff, //cyan #0ff +0xf800, //red #f00 +0xf81f, //magenta #f0f +0xffe0, //yellow #ff0 +0xffff, //white #fff +0xffff, //white +0xffff, //white +0xffff, //white +0xffff, //white +0xffff, //white +0xffff, //white +0xffff, //white +0xffff, //white +]) + +var p0 = g; +var p1; + +if (settings.perflog){ + startPerfLog = function(name){ + var time = getTime(); + if (!performanceLog.start) performanceLog.start={}; + performanceLog.start[name] = time; + }; + endPerfLog = function (name){ + var time = getTime(); + if (!performanceLog.last) performanceLog.last={}; + var duration = time - performanceLog.start[name]; + performanceLog.last[name] = duration; + if (!performanceLog.cum) performanceLog.cum={}; + if (!performanceLog.cum[name]) performanceLog.cum[name] = 0; + performanceLog.cum[name] += duration; + if (!performanceLog.count) performanceLog.count={}; + if (!performanceLog.count[name]) performanceLog.count[name] = 0; + performanceLog.count[name]++; + }; + + printPerfLog = function(){ + var result = ""; + var keys = []; + for (var c in performanceLog.cum){ + keys.push(c); + } + keys.sort(); + for (var k of keys){ + print(k, "last:", (performanceLog.last[k] * 1000).toFixed(0), "average:", (performanceLog.cum[k]/performanceLog.count[k]*1000).toFixed(0), "count:", performanceLog.count[k], "total:", (performanceLog.cum[k] * 1000).toFixed(0)); + } + }; +} + +function delay(t) { + return new Promise(function (resolve) { + setTimeout(resolve, t); + }); +} + +function prepareImg(resource){ + startPerfLog("prepareImg"); + //print("prepareImg: ", resource); + + if (resource.dataOffset !== undefined){ + resource.buffer = E.toArrayBuffer(require("Storage").read("imageclock.resources.data", resource.dataOffset, resource.dataLength)); + delete resource.dataOffset; + delete resource.dataLength; + if (resource.paletteData){ + result.palette = new Uint16Array(resource.paletteData); + delete resource.paletteData; + } + } + endPerfLog("prepareImg"); + return resource; +} + +function getByPath(object, path, lastElem){ + startPerfLog("getByPath"); + //print("getByPath", path,lastElem); + var current = object; + if (path.length) { + for (var c of path){ + if (!current[c]) return undefined; + current = current[c]; + } + } + if (lastElem!==undefined){ + if (!current["" + lastElem]) return undefined; + //print("Found by lastElem", lastElem); + current = current["" + lastElem]; + } + endPerfLog("getByPath"); + if (typeof current == "function"){ + //print("current was function"); + return undefined; + } + return current; +} + +function splitNumberToDigits(num){ + return String(num).split('').map(item => Number(item)); +} + +function isChangedNumber(element){ + return element.lastDrawnValue != getValue(element.Value); +} + +function isChangedMultistate(element){ + return element.lastDrawnValue != getMultistate(element.Value); +} + +function drawNumber(graphics, resources, element){ + startPerfLog("drawNumber"); + var number = getValue(element.Value); + var spacing = element.Spacing ? element.Spacing : 0; + var unit = element.Unit; + + var imageIndexMinus = element.ImageIndexMinus; + var imageIndexUnit = element.ImageIndexUnit; + var numberOfDigits = element.Digits; + + + //print("drawNumber: ", number, element); + if (number) number = number.toFixed(0); + + var isNegative; + var digits; + if (number == undefined){ + isNegative = true; + digits = []; + numberOfDigits = 0; + } else { + isNegative = number < 0; + if (isNegative) number *= -1; + digits = splitNumberToDigits(number); + } + + //print("digits: ", digits); + if (!numberOfDigits) numberOfDigits = digits.length; + var firstDigitX = element.X; + var firstDigitY = element.Y; + var imageIndex = element.ImageIndex ? element.ImageIndex : 0; + + var firstImage; + if (imageIndex){ + firstImage = getByPath(resources, [], "" + (0 + imageIndex)); + } else { + firstImage = getByPath(resources, element.ImagePath, 0); + } + + var minusImage; + if (imageIndexMinus){ + minusImage = getByPath(resources, [], "" + (0 + imageIndexMinus)); + } else { + minusImage = getByPath(resources, element.ImagePath, "minus"); + } + + var unitImage; + //print("Get image for unit", imageIndexUnit); + if (imageIndexUnit !== undefined){ + unitImage = getByPath(resources, [], "" + (0 + imageIndexUnit)); + //print("Unit image is", unitImage); + } else if (element.Unit){ + unitImage = getByPath(resources, element.ImagePath, getMultistate(element.Unit, "unknown")); + } + + var numberWidth = (numberOfDigits * firstImage.width) + (Math.max((numberOfDigits - 1),0) * spacing); + if (isNegative && minusImage){ + //print("Adding to width", minusImage); + numberWidth += minusImage.width + spacing; + } + if (unitImage){ + //print("Adding to width", unitImage); + numberWidth += unitImage.width + spacing; + } + //print("numberWidth:", numberWidth); + + if (element.Alignment == "Center") { + firstDigitX = Math.round(element.X - (numberWidth/2)) + 1; + firstDigitY = Math.round(element.Y - (firstImage.height/2)) + 1; + } else if (element.Alignment == "BottomRight"){ + firstDigitX = element.X - numberWidth + 1; + firstDigitY = element.Y - firstImage.height + 1; + } else if (element.Alignment == "TopRight") { + firstDigitX = element.X - numberWidth + 1; + firstDigitY = element.Y; + } else if (element.Alignment == "BottomLeft") { + firstDigitX = element.X; + firstDigitY = element.Y - firstImage.height + 1; + } + + var currentX = firstDigitX; + if (isNegative && minusImage){ + //print("Draw minus at", currentX); + if (imageIndexMinus){ + drawElement(graphics, resources, {X:currentX,Y:firstDigitY}, element, "" + (0 + imageIndexMinus)); + } else { + drawElement(graphics, resources, {X:currentX,Y:firstDigitY}, element, "minus"); + } + currentX += minusImage.width + spacing; + } + for (var d = 0; d < numberOfDigits; d++){ + var currentDigit; + var difference = numberOfDigits - digits.length; + if (d >= difference){ + currentDigit = digits[d-difference]; + } else { + currentDigit = 0; + } + //print("Digit " + currentDigit + " " + currentX); + drawElement(graphics, resources, {X:currentX,Y:firstDigitY}, element, currentDigit + imageIndex); + currentX += firstImage.width + spacing; + } + if (imageIndexUnit){ + //print("Draw unit at", currentX); + drawElement(graphics, resources, {X:currentX,Y:firstDigitY}, element, "" + (0 + imageIndexUnit)); + } else if (element.Unit){ + drawElement(graphics, resources, {X:currentX,Y:firstDigitY}, element, getMultistate(element.Unit,"unknown")); + } + element.lastDrawnValue = number; + + endPerfLog("drawNumber"); +} + +function drawElement(graphics, resources, pos, element, lastElem){ + startPerfLog("drawElement"); + var cacheKey = "_"+(lastElem?lastElem:"nole"); + if (!element.cachedImage) element.cachedImage={}; + if (!element.cachedImage[cacheKey]){ + var resource = getByPath(resources, element.ImagePath, lastElem); + if (resource){ + prepareImg(resource); + //print("lastElem", typeof resource) + if (resource) { + element.cachedImage[cacheKey] = resource; + //print("cache res ",typeof element.cachedImage[cacheKey]); + } else { + element.cachedImage[cacheKey] = null; + //print("cache null",typeof element.cachedImage[cacheKey]); + //print("Could not create image from", resource); + } + } else { + //print("Could not get resource from", element, lastElem); + } + } + + //print("cache ",typeof element.cachedImage[cacheKey], element.ImagePath, lastElem); + if(element.cachedImage[cacheKey]){ + //print("drawElement ",pos, path, lastElem); + //print("resource ", resource,pos, path, lastElem); + //print("drawImage from drawElement", image, pos); + var options={}; + if (element.RotationValue){ + options.rotate = radians(element); + } + if (element.Scale){ + options.scale = element.ScaleValue; + } + //print("options", options); + //print("Memory before drawing", process.memory(false)); + startPerfLog("drawElement_g.drawImage"); + try{ + graphics.drawImage(element.cachedImage[cacheKey] ,(pos.X ? pos.X : 0), (pos.Y ? pos.Y : 0), options);} catch (e) { + //print("Error", e, element.cachedImage[cacheKey]); + } + endPerfLog("drawElement_g.drawImage"); + } + endPerfLog("drawElement"); +} + +function getValue(value, defaultValue){ + if (typeof value == "string"){ + return numbers[value](); + } + if (value == undefined) return defaultValue; + return value; +} + +function getMultistate(name, defaultValue){ + if (typeof name == "string"){ + return multistates[name](); + } else { + if (name == undefined) return defaultValue; + } + return undefined; +} + +function drawScale(graphics, resources, scale){ + startPerfLog("drawScale"); + //print("drawScale", scale); + var segments = scale.Segments; + var imageIndex = scale.ImageIndex !== undefined ? scale.ImageIndex : 0; + + var value = scaledown(scale.Value, scale.MinValue, scale.MaxValue); + + //print("Value is ", value, "(", maxValue, ",", minValue, ")"); + + var segmentsToDraw = Math.ceil(value * segments.length); + + for (var i = 0; i < segmentsToDraw; i++){ + drawElement(graphics, resources, segments[i], scale, imageIndex + i); + } + scale.lastDrawnValue = segmentsToDraw; + + endPerfLog("drawScale"); +} + +function drawImage(graphics, resources, image, name){ + startPerfLog("drawImage"); + //print("drawImage", image.X, image.Y, name); + if (image.Value && image.Steps){ + var steps = Math.floor(scaledown(image.Value, image.MinValue, image.MaxValue) * (image.Steps - 1)); + //print("Step", steps, "of", image.Steps); + drawElement(graphics, resources, image, image, "" + steps); + } else if (image.ImageIndex !== undefined) { + drawElement(graphics, resources, image, image, image.ImageIndex); + } else { + drawElement(graphics, resources, image, image, name ? "" + name: undefined); + } + + endPerfLog("drawImage"); +} + +function drawCodedImage(graphics, resources, image){ + startPerfLog("drawCodedImage"); + var code = getValue(image.Value); + //print("drawCodedImage", image, code); + + if (image.ImagePath) { + var factor = 1; + var currentCode = code; + while (code / factor > 1){ + currentCode = Math.floor(currentCode/factor)*factor; + //print("currentCode", currentCode); + if (getByPath(resources, image.ImagePath, currentCode)){ + break; + } + factor *= 10; + } + if (code / factor > 1){ + //print("found match"); + drawImage(graphics, resources, image, currentCode); + } else { + //print("fallback"); + drawImage(graphics, resources, image, "fallback"); + } + } + image.lastDrawnValue = code; + + startPerfLog("drawCodedImage"); +} + +function getWeatherCode(){ + var jsonWeather = require("Storage").readJSON('weather.json'); + var weather = (jsonWeather && jsonWeather.weather) ? jsonWeather.weather : undefined; + + if (weather && weather.code){ + return weather.code; + } + return undefined; +} + +function getWeatherTemperature(){ + var jsonWeather = require("Storage").readJSON('weather.json'); + var weather = (jsonWeather && jsonWeather.weather) ? jsonWeather.weather : undefined; + + var result = { unit: "unknown"}; + if (weather && weather.temp){ + //print("Weather is", weather); + var temp = require('locale').temp(weather.temp-273.15); + result.value = Number(temp.match(/[\d\-]*/)[0]); + var unit; + if (temp.includes("C")){ + result.unit = "celsius"; + } else if (temp.includes("F")){ + result.unit = "fahrenheit"; + } + } + return result; +} + +function scaledown(value, min, max){ + //print("scaledown", value, min, max); + var scaled = E.clip(getValue(value),getValue(min,0),getValue(max,1)); + scaled -= getValue(min,0); + scaled /= getValue(max,1); + return scaled; +} + +function radians(rotation){ + var value = scaledown(rotation.RotationValue, rotation.MinRotationValue, rotation.MaxRotationValue); + value -= rotation.RotationOffset ? rotation.RotationOffset : 0; + value *= 360; + value *= Math.PI / 180; + return value; +} + +function drawPoly(graphics, resources, element){ + startPerfLog("drawPoly"); + var vertices = []; + + startPerfLog("drawPoly_transform"); + for (var c of element.Vertices){ + vertices.push(c.X); + vertices.push(c.Y); + } + var transform = { x: element.X ? element.X : 0, + y: element.Y ? element.Y : 0 + }; + if (element.RotationValue){ + transform.rotate = radians(element); + } + vertices = graphics.transformVertices(vertices, transform); + + endPerfLog("drawPoly_transform"); + + if (element.Filled){ + startPerfLog("drawPoly_g.fillPoly"); + graphics.fillPoly(vertices,true); + endPerfLog("drawPoly_g.fillPoly"); + } else { + startPerfLog("drawPoly_g.drawPoly"); + graphics.drawPoly(vertices,true); + endPerfLog("drawPoly_g.drawPoly"); + } + + endPerfLog("drawPoly"); +} + +function drawRect(graphics, resources, element){ + startPerfLog("drawRect"); + var vertices = []; + + if (element.Filled){ + startPerfLog("drawRect_g.fillRect"); + graphics.fillRect(element.X, element.Y, element.X + element.Width, element.Y + element.Height); + endPerfLog("drawRect_g.fillRect"); + } else { + startPerfLog("drawRect_g.fillRect"); + graphics.drawRect(element.X, element.Y, element.X + element.Width, element.Y + element.Height); + endPerfLog("drawRect_g.fillRect"); + } + endPerfLog("drawRect"); +} + +function drawCircle(graphics, resources, element){ + startPerfLog("drawCircle"); + + if (element.Filled){ + startPerfLog("drawCircle_g.fillCircle"); + graphics.fillCircle(element.X, element.Y, element.Radius); + endPerfLog("drawCircle_g.fillCircle"); + } else { + startPerfLog("drawCircle_g.drawCircle"); + graphics.drawCircle(element.X, element.Y, element.Radius); + endPerfLog("drawCircle_g.drawCircle"); + } + endPerfLog("drawCircle"); +} + +var numbers = {}; +numbers.Hour = () => { return new Date().getHours(); }; +numbers.HourTens = () => { return Math.floor(new Date().getHours()/10); }; +numbers.HourOnes = () => { return Math.floor(new Date().getHours()%10); }; +numbers.Hour12 = () => { return new Date().getHours()%12; }; +numbers.Hour12Analog = () => { var date = new Date(); return date.getHours()%12 + (date.getMinutes()/59); }; +numbers.Hour12Tens = () => { return Math.floor((new Date().getHours()%12)/10); }; +numbers.Hour12Ones = () => { return Math.floor((new Date().getHours()%12)%10); }; +numbers.Minute = () => { return new Date().getMinutes(); }; +numbers.MinuteAnalog = () => { var date = new Date(); return date.getMinutes() + (date.getSeconds()/59); }; +numbers.MinuteTens = () => { return Math.floor(new Date().getMinutes()/10); }; +numbers.MinuteOnes = () => { return Math.floor(new Date().getMinutes()%10); }; +numbers.Second = () => { return new Date().getSeconds(); }; +numbers.SecondAnalog = () => { var date = new Date(); return date.getSeconds() + (date.getMilliseconds()/999); }; +numbers.SecondTens = () => { return Math.floor(new Date().getSeconds()/10); }; +numbers.SecondOnes = () => { return Math.floor(new Date().getSeconds()%10); }; +numbers.WeekDay = () => { return new Date().getDay(); }; +numbers.WeekDayMondayFirst = () => { var day = (new Date().getDay() - 1); if (day < 0) day = 7 + day; return day; }; +numbers.Day = () => { return new Date().getDate(); }; +numbers.DayTens = () => { return Math.floor(new Date().getDate()/10); }; +numbers.DayOnes = () => { return Math.floor(new Date().getDate()%10); }; +numbers.Month = () => { return new Date().getMonth() + 1; }; +numbers.MonthTens = () => { return Math.floor((new Date().getMonth() + 1)/10); }; +numbers.MonthOnes = () => { return Math.floor((new Date().getMonth() + 1)%10); }; +numbers.Pulse = () => { return pulse; }; +numbers.Steps = () => { return Bangle.getHealthStatus ? Bangle.getHealthStatus("day").steps : undefined; }; +numbers.StepsGoal = () => { return settings.stepsgoal || 10000; }; +numbers.Temperature = () => { return temp; }; +numbers.Pressure = () => { return press; }; +numbers.Altitude = () => { return alt; }; +numbers.BatteryPercentage = E.getBattery; +numbers.BatteryVoltage = NRF.getBattery; +numbers.WeatherCode = getWeatherCode; +numbers.WeatherTemperature = () => { return getWeatherTemperature().value; }; + +var multistates = {}; +multistates.Lock = () => { return Bangle.isLocked() ? "on" : "off"; }; +multistates.Charge = () => { return Bangle.isCharging() ? "on" : "off"; }; +multistates.Notifications = () => { return ((require("Storage").readJSON("setting.json", 1) || {}).quiet|0) ? "off" : "vibrate"; }; +multistates.Alarm = () => { return (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? "on" : "off"; }; +multistates.Bluetooth = () => { return NRF.getSecurityStatus().connected ? "on" : "off"; }; +//TODO: Implement peripheral connection status +multistates.BluetoothPeripheral = () => { return NRF.getSecurityStatus().connected ? "on" : "off"; }; +multistates.HRM = () => { return Bangle.isHRMOn ? "on" : "off"; }; +multistates.Barometer = () => { return Bangle.isBarometerOn() ? "on" : "off"; }; +multistates.Compass = () => { return Bangle.isCompassOn() ? "on" : "off"; }; +multistates.GPS = () => { return Bangle.isGPSOn() ? "on" : "off"; }; +multistates.WeatherTemperatureNegative = () => { return getWeatherTemperature().value ? getWeatherTemperature().value : 0 < 0; }; +multistates.WeatherTemperatureUnit = () => { return getWeatherTemperature().unit; }; +multistates.StepsGoal = () => { return (numbers.Steps() >= (settings.stepsgoal || 10000)) ? "on": "off"; }; + +function drawMultiState(graphics, resources, element){ + startPerfLog("drawMultiState"); + //print("drawMultiState", element); + var value = multistates[element.Value](); + //print("drawImage from drawMultiState", element, value); + drawImage(graphics, resources, element, value); + element.lastDrawnValue = value; + endPerfLog("drawMultiState"); +} + +var pulse,alt,temp,press; + + +var requestedDraws = 0; +var isDrawing = false; + +var drawingTime; + +var start; + +function initialDraw(resources, face){ + //print("Free memory", process.memory(false).free); + requestedDraws++; + if (!isDrawing){ + //print(new Date().toISOString(), "Can draw,", requestedDraws, "draws requested so far"); + isDrawing = true; + resetPerfLog(); + requestedDraws = 0; + //print(new Date().toISOString(), "Drawing start"); + startPerfLog("initialDraw"); + //start = Date.now(); + drawingTime = 0; + //print("Precompiled"); + var promise = precompiledJs(watchfaceResources, watchface); + + promise.then(()=>{ + var currentDrawingTime = Date.now(); + if (showWidgets){ + //print("Draw widgets"); + Bangle.drawWidgets(); + g.setColor(g.theme.fg); + g.drawLine(0,24,g.getWidth(),24); + } + lastDrawTime = Date.now() - start; + drawingTime += Date.now() - currentDrawingTime; + //print(new Date().toISOString(), "Drawing done in", lastDrawTime.toFixed(0), "active:", drawingTime.toFixed(0)); + isDrawing=false; + firstDraw=false; + requestRefresh = false; + endPerfLog("initialDraw"); + }).catch((e)=>{ + print("Error during drawing", e); + }); + + if (requestedDraws > 0){ + //print(new Date().toISOString(), "Had deferred drawing left, drawing again"); + requestedDraws = 0; + setTimeout(()=>{initialDraw(resources, face);}, 10); + } + } //else { + //print("queued draw"); + //} +} + +function handleHrm(e){ + if (e.confidence > 70){ + pulse = e.bpm; + if (!redrawEvents || redrawEvents.includes("HRM") && !Bangle.isLocked()){ + //print("Redrawing on HRM"); + initialDraw(watchfaceResources, watchface); + } + } +} + +function handlePressure(e){ + alt = e.altitude; + temp = e.temperature; + press = e.pressure; + if (!redrawEvents || redrawEvents.includes("pressure") && !Bangle.isLocked()){ + //print("Redrawing on pressure"); + initialDraw(watchfaceResources, watchface); + } +} + +function handleCharging(e){ + if (!redrawEvents || redrawEvents.includes("charging") && !Bangle.isLocked()){ + //print("Redrawing on charging"); + initialDraw(watchfaceResources, watchface); + } +} + + +function getMatchedWaitingTime(time){ + var result = time - (Date.now() % time); + //print("Matched timeout", time, result); + return result; +} + + + +function setMatchedInterval(callable, time, intervalHandler, delay){ + //print("Setting matched interval for", time); + var matchedTime = getMatchedWaitingTime(time + delay); + setTimeout(()=>{ + var interval = setInterval(callable, time); + if (intervalHandler) intervalHandler(interval); + callable(); + }, matchedTime); +} + +var unlockedDrawInterval; +var lockedDrawInterval; + +var lastDrawTime = 0; +var firstDraw = true; + +var lockedRedraw = getByPath(watchface, ["Properties","Redraw","Locked"]) || 60000; +var unlockedRedraw = getByPath(watchface, ["Properties","Redraw","Unlocked"]) || 1000; +var defaultRedraw = getByPath(watchface, ["Properties","Redraw","Default"]) || "Always"; +var redrawEvents = getByPath(watchface, ["Properties","Redraw","Events"]); +var clearOnRedraw = getByPath(watchface, ["Properties","Redraw","Clear"]); +var events = getByPath(watchface, ["Properties","Events"]); + +//print("events", events); +//print("redrawEvents", redrawEvents); + +function handleLock(isLocked, forceRedraw){ + //print("isLocked", Bangle.isLocked()); + if (lockedDrawInterval) clearInterval(lockedDrawInterval); + if (unlockedDrawInterval) clearInterval(unlockedDrawInterval); + if (!isLocked){ + if (forceRedraw || !redrawEvents || (redrawEvents.includes("unlock"))){ + //print("Redrawing on unlock", isLocked); + initialDraw(watchfaceResources, watchface); + } + setMatchedInterval(()=>{ + //print("Redrawing on unlocked interval"); + initialDraw(watchfaceResources, watchface); + },unlockedRedraw, (v)=>{ + unlockedDrawInterval = v; + }, lastDrawTime); + if (!events || events.includes("HRM")) Bangle.setHRMPower(1, "imageclock"); + if (!events || events.includes("pressure")) Bangle.setBarometerPower(1, 'imageclock'); + } else { + if (forceRedraw || !redrawEvents || (redrawEvents.includes("lock"))){ + //print("Redrawing on lock", isLocked); + initialDraw(watchfaceResources, watchface); + } + setMatchedInterval(()=>{ + //print("Redrawing on locked interval"); + initialDraw(watchfaceResources, watchface); + },lockedRedraw, (v)=>{ + lockedDrawInterval = v; + }, lastDrawTime); + Bangle.setHRMPower(0, "imageclock"); + Bangle.setBarometerPower(0, 'imageclock'); + } +} + + +var showWidgets = false; +var showWidgetsChanged = false; +var currentDragDistance = 0; + +Bangle.setUI("clock"); +Bangle.on('drag', (e)=>{ + currentDragDistance += e.dy; + if (Math.abs(currentDragDistance) < 10) return; + dragDown = currentDragDistance > 0; + currentDragDistance = 0; + if (!showWidgets && dragDown){ + //print("Enable widgets"); + if (WIDGETS && typeof WIDGETS === "object") { + for (let w in WIDGETS) { + var wd = WIDGETS[w]; + wd.draw = originalWidgetDraw[w]; + wd.area = originalWidgetArea[w]; + } + } + showWidgetsChanged = true; + } + if (showWidgets && !dragDown){ + //print("Disable widgets"); + clearWidgetsDraw(); + firstDraw = true; + showWidgetsChanged = true; + } + if (showWidgetsChanged){ + showWidgetsChanged = false; + //print("Draw after widget change"); + showWidgets = dragDown; + initialDraw(); + } + } +); + +if (!events || events.includes("pressure")){ + Bangle.on('pressure', handlePressure); + try{ + Bangle.setBarometerPower(1, 'imageclock'); + } catch (e){ + print("Error during barometer power up", e); + } +} +if (!events || events.includes("HRM")) { + Bangle.on('HRM', handleHrm); + Bangle.setHRMPower(1, "imageclock"); +} +if (!events || events.includes("lock")) { + Bangle.on('lock', handleLock); +} +if (!events || events.includes("charging")) { + Bangle.on('charging', handleCharging); +} + +var originalWidgetDraw = {}; +var originalWidgetArea = {}; + +function clearWidgetsDraw(){ + //print("Clear widget draw calls"); + if (WIDGETS && typeof WIDGETS === "object") { + originalWidgetDraw = {}; + originalWidgetArea = {}; + for (let w in WIDGETS) { + var wd = WIDGETS[w]; + originalWidgetDraw[w] = wd.draw; + originalWidgetArea[w] = wd.area; + wd.draw = () => {}; + wd.area = ""; + } + } +} + +setTimeout(()=>{ + Bangle.loadWidgets(); + clearWidgetsDraw(); +}, 0); + +handleLock(Bangle.isLocked()); diff --git a/apps/imageclock/app.png b/apps/imageclock/app.png new file mode 100644 index 000000000..cf057046b Binary files /dev/null and b/apps/imageclock/app.png differ diff --git a/apps/imageclock/custom.html b/apps/imageclock/custom.html new file mode 100644 index 000000000..af7a1835f --- /dev/null +++ b/apps/imageclock/custom.html @@ -0,0 +1,1144 @@ + + + + + + + + + + + +

Upload watchface:

+ +

+ Select format:
+ +
+ +
+

+ +

+ Options:
+ +
+ +
+

+ +

Select watchface folder:

+

or

+

Select watchface zip file:


+ +
+
+
+
+
+

Download Demo Watchface here:
+ digitalretro.zip
+ simpleanalog.zip
+

+ + + + + diff --git a/apps/imageclock/demomode.js b/apps/imageclock/demomode.js new file mode 100644 index 000000000..8c9d19195 --- /dev/null +++ b/apps/imageclock/demomode.js @@ -0,0 +1,29 @@ +var demostate = 0; +function demoMode(){ + lockedRedraw = 2000; + unlockedRedraw = 2000; + for (var c in multistates){ + multistates[c] = ()=>{return ["on","off"][demostate%2];}; + if (c == "WeatherTemperatureUnit") multistates[c] = ()=>{return ["celsius","fahrenheit"][demostate%2];}; + if (c == "Notifications") multistates[c] = ()=>{return ["on","off","vibrate"][demostate%3];}; + } + for (var c in numbers){ + if (c.contains("Minute")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + if (c.contains("Second")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + if (c.contains("Hour")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + } + for (var c in numbers){ + if (c.contains("Ones")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + if (c.contains("Tens")) numbers[c] = ()=>{return Math.floor((Math.random() * 9) + 1);}; + } + numbers.Pulse = ()=>{return Math.floor((Math.random() * 60) + 40);}; + numbers.Steps = ()=>{return Math.floor((Math.random() * 10000) + 10);}; + numbers.Temperature = ()=>{return Math.floor((Math.random() * 15) + 10);}; + numbers.Pressure = ()=>{return Math.floor((Math.random() * 1000) + 10);}; + numbers.Altitude = ()=>{return Math.floor((Math.random() * 1000) + 10);}; + numbers.WeatherCode = ()=>{return Math.floor((Math.random() * 800) + 100);}; + numbers.WeatherTemperature = ()=>{return Math.floor((Math.random() * 10) + 0);}; +} +demoMode(); +handleLock(false); +setInterval(()=>{demostate++;},1000); diff --git a/apps/imageclock/digitalretro.zip b/apps/imageclock/digitalretro.zip new file mode 100644 index 000000000..96ab90b6d Binary files /dev/null and b/apps/imageclock/digitalretro.zip differ diff --git a/apps/imageclock/metadata.json b/apps/imageclock/metadata.json new file mode 100644 index 000000000..a2594653e --- /dev/null +++ b/apps/imageclock/metadata.json @@ -0,0 +1,19 @@ +{ + "id": "imageclock", + "name": "Imageclock", + "shortName": "Imageclock", + "version": "0.07", + "type": "clock", + "description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.", + "icon": "app.png", + "tags": "clock", + "supports": ["BANGLEJS2"], + "custom": "custom.html", + "customConnect": false, + "readme": "README.md", + "storage": [ + {"name":"imageclock.app.js","url":"app.js"}, + {"name":"imageclock.settings.js","url":"settings.js"}, + {"name":"imageclock.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/imageclock/settings.js b/apps/imageclock/settings.js new file mode 100644 index 000000000..a86901b9e --- /dev/null +++ b/apps/imageclock/settings.js @@ -0,0 +1,35 @@ +(function(back) { + var FILE = "imageclock.json"; + + var settings = Object.assign({ + stepsgoal: 10000, + perflog: false + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + E.showMenu({ + '': { 'title': 'Imageclock' }, + '< Back': back, + 'Steps goal': { + value: settings.stepsgoal, + min: 0, + step: 500, + max: 50000, + onchange: v => { + settings.stepsgoal = v; + writeSettings(); + } + }, + 'Performance log': { + value: !!settings.perflog, + format: v => settings.perflog ? "On" : "Off", + onchange: v => { + settings.perflog = v; + writeSettings(); + } + } + }); +}) diff --git a/apps/imageclock/simpleanalog.zip b/apps/imageclock/simpleanalog.zip new file mode 100644 index 000000000..1301be055 Binary files /dev/null and b/apps/imageclock/simpleanalog.zip differ diff --git a/apps/invader/ChangeLog b/apps/invader/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/invader/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/invader/README.md b/apps/invader/README.md new file mode 100644 index 000000000..7ed98defb --- /dev/null +++ b/apps/invader/README.md @@ -0,0 +1,23 @@ +# App Name + +Invader + +## Usage + +For fun! - I'm creating this demo to learn JavaScript with the Bangle.js 2 + +## Features + +Shoot the Alien, you have three lives + +## Controls + +Touch the lower Left or Right hand sides of the screen to move turret left or right, tap upper Right hand part of screen to fire, tap upper Left hand part of screen to restart + +## Requests + +bkumanchik on Espruino Forums + +## Creator + +Brian Kumanchik 2022 diff --git a/apps/invader/app-icon.js b/apps/invader/app-icon.js new file mode 100644 index 000000000..dc7003c84 --- /dev/null +++ b/apps/invader/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AH4AYgAACC/4X/C/YbTFbYv/F/4rTAC4v/F/4vfC5YnPGaYv/F/4vLc7b3TF/4v/F/4v/F/4vTA5YAPE64v/F/4fTa55DXF/4v/AH4A/AH4A/AH4A/AHIA==")) diff --git a/apps/invader/app.js b/apps/invader/app.js new file mode 100644 index 000000000..89e7462f6 --- /dev/null +++ b/apps/invader/app.js @@ -0,0 +1,452 @@ +// Brian Kumanchik +// Started 05-25-22 +// My Invader Demo, for Bangle.js 2, written JavaScript - using Espruino Web IDE + + +// note: resolution is 176x176 + + +// to do: +// upload to official app page +// make invader clock + + + +// - variables ----------------------------------------- +// invader variables +var inv_x = 77; +var inv_y = 20; +var i_anim_delay = 10; // invader animation (and move) delay +var inv_frame = 1; // invader start animation frame +var ix_speed = 6; // march speed +var i_dir = 1; // 1 = right, 0 = left +var been_hit = false; // invader hit state +// - shoot variables +var inv_shot_x = -32; +var inv_shot_y = -32; +var inv_fire_pause = 30; +var inv_fired = false; // invader fired state +// - explode variables +var been_hit = false; // invader hit state +var bx = -32; // blast x +var by = -32; // blast y +var blast_delay = 15; // invader blast delay - pause after explosion +var boom_play = false; + +// turret variables +var tur_x = 77; +var tur_y = 148; +var shot_fired = false; // turret fired state +var sx = -20; // turret shot starting x - off screen +var sy = -20; // turret shot starting y - off screen +var turret_been_hit = false; +var turret_blast_delay = 25; // keep blast active on screen for 60 frames +var turret_exp_frame = 1; // turret explode start animation frame +var turret_anim_delay = 3; // turret explode animation delay +var explosion_play = false; + +// misc variables +var score = 0; // starting score +var lives = 3; // starting lives +var game_state = 0; // game state - 0 = game not started, 1 = game running, 3 = game over +var ang = 0.1; +var start_been_pressed = false; // stops double press on restart +var fire_been_pressed = false; // stops auto fire + +// input(screen controller) variables +var BTNL, BTNR, BTNF, BTNS; // button - left, right, fire, start +var tap = {}; +// use tapping on screen for left, right, fire, start +Bangle.on('drag',e=>tap=e); +BTNL = { read : _=>tap.b && tap.x < 88 && tap.y > 88}; +BTNR = { read : _=>tap.b && tap.x > 88 && tap.y > 88}; +BTNF = { read : _=>tap.b && tap.x > 88 && tap.y < 88}; +BTNS = { read : _=>tap.b && tap.x < 88 && tap.y < 88}; + + +// - sprites ------------------------------------------- +// invader sprites +var invader_a = + require("heatshrink").decompress(atob("hcIwkBiIBBAQoECCQQFBgEQAIMBEhUBDoYWDAYI=")); +var invader_b = + require("heatshrink").decompress(atob("hcIwkBiIBBAQMQAoQEBgISCAYUQAIQAEB4YEBEAgEDAYIA==")); +var boom = + require("heatshrink").decompress(atob("hcJwkBiMQAIURgMQAgIKBAIICFAIMAAwIWBBAYSIEAgrDiA=")); +var inv_shot = + require("heatshrink").decompress(atob("gcFwkBiERiAABAYQ")); + +// turret sprites +var turret = + require("heatshrink").decompress(atob("h8IwkBiIABAYYACgAHFiEABggADCAInFgITBAAgOPA==")); +var tur_exp_a = + require("heatshrink").decompress(atob("h8IwkBiMRiACBAAwJEiAABBQgZCAAkAiAJBBoIUBgIABBgQACDIQ9ECQIA==")); +var tur_exp_b = + require("heatshrink").decompress(atob("h8IwkBiIBBAAUBiADCiMQAwQFDCIYXEB4IABgMAEYQXBiEAAQIQBAoIABDAQUCAAIVBA")); +var shot = + require("heatshrink").decompress(atob("gMDwkBAoIA==")); + + +// function to move and animate invader +function move_anim_inv() { + // invader anim code + i_anim_delay -= 1; + if ((i_anim_delay < 0) && !(been_hit)) { + i_anim_delay = 10; + + inv_frame += 1; + if (inv_frame > 2) { + inv_frame = 1; + } + + // move right + if (i_dir == 1){ + inv_x += ix_speed; + if (inv_x >= 142) { + inv_y += 8; // step down + i_dir = -1; + } + } + + // move left + if (i_dir < 1){ + inv_x -= ix_speed; + if (inv_x <= 10) { + inv_y += 8; // step down + i_dir = 1; + } + } + } +} + + +// function to make invader fire +function invader_fire() { + inv_fire_pause -= 1; + + if (!(inv_fired)) { // so once invader shot is fired it doesn't follow the invader still + inv_shot_x = inv_x + 8; + inv_shot_y = inv_y + 18; + } + + if (inv_fire_pause < 0) { + inv_fired = true; + inv_shot_y += 8; + } +} + + +// function to make turret explode (when hit) then start back in center +function turret_hit() { + if (turret_been_hit) { + if (!(explosion_play)) { + //Bangle.buzz(); + //Bangle.beep(); + } + + explosion_play = true; + turret_anim_delay -= 1; + turret_blast_delay -= 1; + + if (turret_anim_delay < 0) { + turret_exp_frame += 1; + if (turret_exp_frame > 2) { + Bangle.buzz(); + turret_exp_frame = 1; + } + turret_anim_delay = 3; + } + + if (turret_blast_delay < 0) { + turret_blast_delay = 21; + turret_been_hit = false; + explosion_play = false; + tur_x = 77; // reset turret x + tur_y = 148; // reset turret y + } + } +} + + +// function to make invader explode (when hit) then randomly start somewhere else +function invader_hit() { + if (been_hit) { + if (!(boom_play)) { + Bangle.buzz(); + //Bangle.beep(); + } + + inv_shot_x = -32; // hide shot + inv_shot_y = -32; // hide shot + inv_fire_pause = 30; // and reset pause + + boom_play = true; + blast_delay -= 1; + + if (blast_delay < 0) { + blast_delay = 15; + boom_play = false; + been_hit = false; + bx = -32; // move boom off screen (following invader) + by = -32; + // generate a random rounded number between 10 and 142; + inv_x = Math.floor(Math.random() * 142) + 10; + inv_y = 20; // move invader back up after being hit + i_dir = 1; // reset invader direction + } + } +} + + +// - setup stuff --------------------------------------- +function gameStart() { + setInterval(onFrame, 50); +} + + +// - main loop ------------------------------------------------------------- +function onFrame() { + + // game not started state (title screen) *************************** + if(game_state == 0) { + g.clear(); + + + if (!(BTNS.read())) { + start_been_pressed = false; // stops double press on restart + } + + + // draw text during game over state + g.setFont("4x6", 4); // set font and size x 2 + g.setColor(0,1,0); // set color (black) + g.drawString("INVADER", 33, 55); + + + // just animate invader + // invader anim code + i_anim_delay -= 1; + if(i_anim_delay < 0) { + i_anim_delay = 15; + + inv_frame += 1; + if (inv_frame > 2) { + inv_frame = 1; + } + } + + + // draw sprites during game over state + // next 2 line for a rotating invader on the title screen + //ang += 0.1; + //g.drawImage(invader_a, 88, 98, {scale:4, rotate:ang}); + if(inv_frame == 1) { + g.drawImage(invader_a, 88-22, 85, {scale:4}); + } + else if(inv_frame == 2) { + g.drawImage(invader_b, 88-22, 85, {scale:4}); + } + + // reset stuff + if(BTNS.read() && !(start_been_pressed)) { + turret_been_hit = false; + tur_x = 77; // reset turret to center of screen + tur_y = 148; // reset turret y + inv_x = 77; // reset invader to center of screen + inv_y = 20; // reset invader back to top + i_dir = 1; // reset invader direction + lives = 3; // reset lives + score = 0; // reset score + explosion_play = false; + game_state = 1; + turret_blast_delay = 25; + } + + + g.flip(); + } + + + // game over state ************************************************* + if(game_state == 3) { + g.clear(); + + // draw text during game over state + g.setFont("4x6", 2); // set font and size x 2 + g.setColor(0,0,0); // set color (black) + g.drawString("SCORE:" + score ,5, 5); + g.drawString("LIVES:" + lives ,117, 5); + g.drawString("GAME OVER", 52, 80); + + + // draw sprites during game over state + // - invader frame 2 + g.drawImage(invader_b, inv_x, inv_y, {scale:2}); + g.drawImage(tur_exp_b, tur_x, tur_y, {scale:2}); + g.drawImage(inv_shot, inv_shot_x, inv_shot_y, {scale:2}); + + + // reset stuff + if(BTNS.read()) { + //turret_been_hit = false; + //tur_x = 77; // reset turret to center of screen + //tur_y = 148; // reset turret y + //inv_x = 77; // reset invader to center of screen + //inv_y = 20; // reset invader back to top + //i_dir = 1; // reset invader direction + //lives = 3; // reset lives + //score = 0; // reset score + //explosion_play = false; + game_state = 0; + start_been_pressed = true; + //turret_blast_delay = 25; + } + + + g.flip(); + } + + + // not game over state (game running) ****************************** + if(game_state == 1) { + Bangle.setLCDPower(1); // optional - this keeps the watch LCD lit up + g.clear(); + + + if (!(BTNF.read())) { + fire_been_pressed = false; // stops auto fire + } + + + // call function to move and animate invader + move_anim_inv(); + + // call function to make invader fire + invader_fire(); + + + // check input (screen presses) + if(BTNL.read() && tur_x >= 12 && !(turret_been_hit)) { + tur_x -= 6; + } + else if(BTNR.read() && tur_x <= 140 && !(turret_been_hit)) { + tur_x += 6; + } + else if(BTNF.read() && !(turret_been_hit) && !(fire_been_pressed) && !(shot_fired)) { + shot_fired = true; + fire_been_pressed = true; // stops auto fire + sx=tur_x + 12; + sy=tur_y - 7; + } + + + // check for turret shot going off screen before allowing to fire again + if (shot_fired) { + sy -= 8; + if (sy < 22) { + shot_fired = false; + sx = -32; + sy = -32; + } + } + + + // check for invader shot going off screen before allowing to fire again + if (inv_shot_y > 150 + ) { + inv_fired = false; + inv_shot_x = inv_x - 1; + inv_shot_y = inv_y + 7; + inv_fire_pause = 30; + } + + + // check for turret shot and invader collision + if ((sx >= inv_x) && (sx <= inv_x + 20) && (sy <= inv_y + 14)) { + sx = -32; + sy = -32; + been_hit = true; + score += 10; + } + + + // check for invader shot and turret collision + if ((inv_shot_x + 4) >= (tur_x) && (inv_shot_x) <= (tur_x + 24) && (inv_shot_y + 8) >= (tur_y + 6)) { + if (!(turret_been_hit)) { + lives -= 1; + + if (lives == 0) { + game_state = 3; + Bangle.buzz(); + } + turret_been_hit = true; + } + } + + + // - draw sprites ---------------------------------- + // invader sprites + if(!(been_hit)) { + if(inv_frame == 1) { + // - invader frame 1 + g.drawImage(invader_a, inv_x, inv_y, {scale:2}); + } + else if(inv_frame == 2) { + // - invader frame 2 + g.drawImage(invader_b, inv_x, inv_y, {scale:2}); + } + } + else { + // - invader explosion + g.drawImage(boom, inv_x, inv_y, {scale:2}); + } + // - invader shot + if (inv_fired) { + g.drawImage(inv_shot, inv_shot_x, inv_shot_y, {scale:2}); + } + else { + g.drawImage(inv_shot, -32, -32, {scale:2}); + } + + // turret sprites + if(!(turret_been_hit)) { + // - undamaged turret + g.drawImage(turret, tur_x, tur_y, {scale:2}); + } + else { + if(turret_exp_frame == 1) { + // - turret explosion frame 1 + g.drawImage(tur_exp_a, tur_x, tur_y, {scale:2}); + } + else if(turret_exp_frame == 2) { + // - turret explosion frame 2 + g.drawImage(tur_exp_b, tur_x, tur_y, {scale:2}); + } + } + // - turret shot + g.drawImage(shot, sx, sy, {scale:2}); + + + // call function to make invader explode then randomly start somewhere else + invader_hit(); + + + // call function to make turret explode (when hit) then start back in center + turret_hit(); + + + // - draw text ------------------------------------- + g.setFont("4x6", 2); // set font and size x 2 + g.setColor(0,0,0); // set color (black) + g.drawString("SCORE:" + score ,5,5); + g.drawString("LIVES:" + lives ,117,5); + + + g.flip(); + } + +} // end main loop --------------------------------------------------------- + + +gameStart(); + + diff --git a/apps/invader/app.png b/apps/invader/app.png new file mode 100644 index 000000000..3b9f82205 Binary files /dev/null and b/apps/invader/app.png differ diff --git a/apps/invader/metadata.json b/apps/invader/metadata.json new file mode 100644 index 000000000..bb74f5122 --- /dev/null +++ b/apps/invader/metadata.json @@ -0,0 +1,15 @@ +{ "id": "invader", + "name": "Invader", + "shortName":"Invader", + "version":"0.11", + "description": "A Space Invader game-like demo - work in progress", + "icon": "app.png", + "screenshots" : [ { "url":"screenshot_0.png" }, { "url":"screenshot_1.png" }, { "url":"screenshot_2.png" } ], + "tags": "game", + "supports" : ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"invader.app.js","url":"app.js"}, + {"name":"invader.img","url":"app-icon.js","evaluate":true} + ] +} diff --git a/apps/invader/screenshot.png b/apps/invader/screenshot.png new file mode 100644 index 000000000..ae936f6c7 Binary files /dev/null and b/apps/invader/screenshot.png differ diff --git a/apps/invader/screenshot_0.png b/apps/invader/screenshot_0.png new file mode 100644 index 000000000..804f3e435 Binary files /dev/null and b/apps/invader/screenshot_0.png differ diff --git a/apps/invader/screenshot_1.png b/apps/invader/screenshot_1.png new file mode 100644 index 000000000..14164bc6e Binary files /dev/null and b/apps/invader/screenshot_1.png differ diff --git a/apps/invader/screenshot_2.png b/apps/invader/screenshot_2.png new file mode 100644 index 000000000..5f6ade79c Binary files /dev/null and b/apps/invader/screenshot_2.png differ diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 67c2f903a..4b577e191 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -51,4 +51,5 @@ 0.36: Ensure a new message plus an almost immediate deletion of that message doesn't load the messages app (fix #1362) 0.37: Now use the setUI 'back' icon in the top left rather than specific buttons/menu items 0.38: Add telegram foss handling -0.39: Don't turn on the screen after unread timeout expires (#1873) +0.39: Set default color for message icons according to theme + Don't turn on the screen after unread timeout expires (#1873) diff --git a/apps/messages/app.js b/apps/messages/app.js index 46f010c0b..d4540b797 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -317,7 +317,7 @@ function showMessage(msgid) { {type:"txt", font:fontSmall, label:msg.src||/*LANG*/"Message", bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2, halign:1 }, title?{type:"txt", font:titleFont, label:title, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:2 }:{}, ]}, - { type:"btn", src:require("messages").getMessageImage(msg), col:require("messages").getMessageImageCol(msg), pad: 3, cb:()=>{ + { type:"btn", src:require("messages").getMessageImage(msg), col:require("messages").getMessageImageCol(msg, g.theme.fg2), pad: 3, cb:()=>{ cancelReloadTimeout(); // don't auto-reload to clock now showMessageSettings(msg); }}, diff --git a/apps/rgb/ChangeLog b/apps/rgb/ChangeLog new file mode 100644 index 000000000..a08433b7c --- /dev/null +++ b/apps/rgb/ChangeLog @@ -0,0 +1 @@ +0.01: initial release diff --git a/apps/rgb/README.md b/apps/rgb/README.md new file mode 100644 index 000000000..5723f99c6 --- /dev/null +++ b/apps/rgb/README.md @@ -0,0 +1,16 @@ +rgb +=== + +A simple RGB color selector utility for the BangleJS2 smartwatch. + +Features a vector toggle widget and swipe interactivity. + +Author +------ + +Written by pancake in 2022 + +Screenshots +----------- +![rgb app](screenshot.jpg) + diff --git a/apps/rgb/app-icon.js b/apps/rgb/app-icon.js new file mode 100644 index 000000000..9919a4365 --- /dev/null +++ b/apps/rgb/app-icon.js @@ -0,0 +1 @@ +atob("MDCI/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQUFBQAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHh4eHgUFBQUFBQMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tLS0tLS0tB4eHh4eHh4eHhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tGxsbGxsbBISEhISEhISEhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tGxsbGxsbBISEhISEhISEhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX1wAAALS0tGxsbGxsbBISEhISEhISEhISEgMDAwMDAwMDAwAAANfX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/tfX19fX1wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANfX19fX1/7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/tfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX1/7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/g==") diff --git a/apps/rgb/app.js b/apps/rgb/app.js new file mode 100644 index 000000000..aa18d93ae --- /dev/null +++ b/apps/rgb/app.js @@ -0,0 +1,133 @@ +const rgb = [0, 0, 0]; +const w = g.getWidth(); +const h = g.getHeight(); +function drawToggle (value, x, y, options) { + if (!options) options = {}; + if (!options.scale) options.scale = 1; + const h = (options.scale * 16); + const h2 = h / 2; + const w = (options.scale) * 32; + + g.setColor(0.3, 0, 0.3); + g.fillCircle(x + h2, y + h2, h2 - 1); + g.fillCircle(x + w - h2, y + h2, h2 - 1); + g.fillRect(x + h2, y, x + w - h2, y + h); + + y += 4; + g.setColor(0.6, 0.6, 0.6); + g.fillCircle(x + h2, y + h2 + 2, h2 - 1); + g.fillCircle(x + w - h2, y + h2 + 2, h2 - 1); + g.fillRect(x + h2, y + 2, x + w - h2, y + h + 1); + + if (value) { + x += w - h; + } + g.setColor(0, 0.5, 0); + g.fillCircle(x + h2, y + h2 + 2, h2 - 1); + y -= 4; + if (colorMode) { + g.setColor(0, 1, 0); + } else { + g.setColor(0.5, 0.5, 0.5); + } + g.fillCircle(x + h2, y + h2 + 2, h2 - 1); + + g.setColor(0, 0.8, 0); + g.fillCircle(x + h2 - 2, y + h2, h2 - 8); + if (colorMode) { + g.setColor(0, 1, 0); + } else { + g.setColor(0.5, 0.5, 0.5); + } g.fillCircle(x + h2 + 4, y + h2 + 4, h2 - 9); +} + +function refresh () { + g.setBgColor(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255); + g.clear(); + g.setColor(1, 1, 1); + g.setFont12x20(1); + g.setColor(0, 0, 0); + let s = '#' + hex[(rgb[0] >> 4) & 0xf] + hex[rgb[0] & 0xf]; + s += hex[(rgb[1] >> 4) & 0xf] + hex[rgb[1] & 0xf]; + s += hex[(rgb[2] >> 4) & 0xf] + hex[rgb[2] & 0xf]; + g.setColor(1, 0, 1); + g.fillRect(0, 0, w, 32); + g.setColor(0.2, 0, 1); + g.fillRect(0, 32, w, 33); + g.setColor(1, 1, 1); + g.drawString(s, 8, 8); + // drawToggle (colorMode, w - 8 - 32*(rgb[0]/50), 8 + 255- rgb[2], {scale:1 * rgb[0] / 50}); + drawToggle(colorMode, w - 40, 4, { scale: 1.2 }); + + if (colorMode) { + g.setColor(1, 0, 0); + g.fillRect(0, h, w / 3, h - 32); + g.setColor(0, 1, 0); + g.fillRect(w / 3, h, w - (w / 3), h - 32); + g.setColor(0, 0, 1); + g.fillRect(w - (w / 3), h, w, h - 32); + + g.setColor(0.5, 0, 0); + g.fillRect(0, h - 33, w / 3, h - 34); + g.setColor(0, 0.5, 0); + g.fillRect(w / 3, h - 33, w - (w / 3), h - 34); + g.setColor(0, 0, 0.5); + g.fillRect(w - (w / 3), h - 33, w, h - 34); + } else { + g.setColor(0.5, 0.5, 0.5); + g.fillRect(0, h, w, h - 32); + g.setColor(0.2, 0.2, 0.2); + g.fillRect(0, h - 33, w, h - 34); + } + // column lines + function f (x) { + const s = '' + (rgb[x] / 255); + return s.substring(0, 4); + } + g.setColor(1, 1, 1); + g.drawLine(w / 3, h, w / 3, h / 2); + g.drawLine(w - (w / 3), h, w - (w / 3), h / 2); + g.setFont6x15(2); + g.drawString(f(0), 8, h - 28); + g.drawString(f(1), 8 + (w / 3), h - 28); + g.drawString(f(2), 8 + (2 * w / 3), h - 28); +} +let k = -1; +var colorMode = true; +Bangle.on('touch', function (wat, tap) { + if (tap.x > w / 2 && tap.y < 32) { + colorMode = !colorMode; + refresh(); + } +}); + +function deltaComponent (k, dy) { + rgb[k] -= dy; + if (rgb[k] > 255) { + rgb[k] = 255; + } else if (rgb[k] < 0) { + rgb[k] = 0; + } +} +Bangle.on("button", function() { + rgb[0] = rgb[1] = rgb[2] = 127; +}); +Bangle.on('drag', function (tap, top) { + if (colorMode) { + if (tap.x < w / 3) { + k = 0; + } else if (tap.x > (w - (w / 3))) { + k = 2; + } else { + k = 1; + } + deltaComponent(k, tap.dy); + } else { + deltaComponent(0, tap.dy); + deltaComponent(1, tap.dy); + deltaComponent(2, tap.dy); + } + refresh(); +}); +refresh(); + diff --git a/apps/rgb/app.png b/apps/rgb/app.png new file mode 100644 index 000000000..e9210d2b6 Binary files /dev/null and b/apps/rgb/app.png differ diff --git a/apps/rgb/metadata.json b/apps/rgb/metadata.json new file mode 100644 index 000000000..7a47e65b9 --- /dev/null +++ b/apps/rgb/metadata.json @@ -0,0 +1,31 @@ +{ + "id": "rgb", + "name": "rgb", + "shortName": "rgb", + "version": "0.01", + "type": "app", + "description": "RGB utility", + "icon": "app.png", + "allow_emulator": true, + "tags": "tools", + "supports": [ + "BANGLEJS2" + ], + "readme": "README.md", + "storage": [ + { + "name": "rgb.app.js", + "url": "app.js" + }, + { + "name": "rgb.img", + "url": "app-icon.js", + "evaluate": true + } + ], + "screenshots": [ + { + "url": "screenshot.png" + } + ] +} diff --git a/apps/rgb/screenshot.png b/apps/rgb/screenshot.png new file mode 100644 index 000000000..1815cd492 Binary files /dev/null and b/apps/rgb/screenshot.png differ diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index ea2ecd02b..1513194fe 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -52,3 +52,4 @@ 0.46: Fix regression after making 'calibrate battery' only for Bangle.js 2 0.47: Allow colors to be translated Improve "Turn Off" user experience +0.48: Allow reading custom themes from files diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json index ce4e5b337..17519730e 100644 --- a/apps/setting/metadata.json +++ b/apps/setting/metadata.json @@ -1,7 +1,7 @@ { "id": "setting", "name": "Settings", - "version": "0.47", + "version": "0.48", "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 39cf6c1d1..3bb9b4e22 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -221,7 +221,8 @@ function showThemeMenu() { Bangle.drawWidgets(); m.draw(); } - var m = E.showMenu({ + + var themesMenu = { '':{title:/*LANG*/'Theme'}, '< Back': ()=>showSystemMenu(), /*LANG*/'Dark BW': ()=>{ @@ -239,9 +240,26 @@ function showThemeMenu() { fgH:cl("#000"), bgH:cl("#0ff"), dark:false }); - }, - /*LANG*/'Customize': ()=>showCustomThemeMenu(), - }); + } + }; + + require("Storage").list(/^.*\.theme$/).forEach( + n => { + let newTheme = require("Storage").readJSON(n); + themesMenu[newTheme.name ? newTheme.name : n] = () => { + upd({ + fg:cl(newTheme.fg), bg:cl(newTheme.bg), + fg2:cl(newTheme.fg2), bg2:cl(newTheme.bg2), + fgH:cl(newTheme.fgH), bgH:cl(newTheme.bgH), + dark:newTheme.dark + }); + }; + } + ); + + themesMenu[/*LANG*/'Customize'] = () => showCustomThemeMenu(); + + var m = E.showMenu(themesMenu); function showCustomThemeMenu() { function setT(t, v) { diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index 02658296a..036bdb0f4 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -2,3 +2,4 @@ 0.02: Fix position and overdraw bugs 0.03: Better memory usage, theme support 0.04: Replace the 8 phases by a more exact drawing, see forum.espruino.com/conversations/371985 +0.05: Fixed the algorithm for calculating the moon's phase diff --git a/apps/widmp/metadata.json b/apps/widmp/metadata.json index 94f05a426..ff7ad79ad 100644 --- a/apps/widmp/metadata.json +++ b/apps/widmp/metadata.json @@ -1,7 +1,7 @@ { "id": "widmp", "name": "Moon Phase Widget", - "version": "0.04", + "version": "0.05", "description": "Display the current moon phase in blueish for both hemispheres. In the southern hemisphere the 'My Location' app is needed.", "icon": "widget.png", "type": "widget", diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index 6da572aab..bf032a5ff 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -1,19 +1,24 @@ WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { const CenterX = this.x + 12, CenterY = this.y + 12, Radius = 11; var southernHemisphere = false; // when in southern hemisphere, use the "My Location" App + var lastCalculated = 0; // When we last calculated the phase + var phase = 0; // The last phase we calculated const simulate = false; // simulate one month in one minute const updateR = 1000; // update every x ms in simulation - function moonPhase() { - const d = Date(); - var month = d.getMonth(), year = d.getFullYear(), day = d.getDate(); - if (simulate) day = d.getSeconds() / 2 +1; - if (month < 3) {year--; month += 12;} - mproz = ((365.25 * year + 30.6 * ++month + day - 694039.09) / 29.5305882); - mproz = mproz - (mproz | 0); // strip integral digits, result is between 0 and <1 - if (simulate) console.log(mproz + " " + day); - return (mproz); + // https://deirdreobyrne.github.io/calculating_moon_phases/ + function moonPhase(millis) { + k = (millis - 946728000000) / 3155760000000; + mp = (8328.69142475915 * k) + 2.35555563685; + m = (628.30195516723 * k) + 6.24006012726; + d = (7771.37714483372 * k) + 5.19846652984; + t = d + (0.109764 * Math.sin (mp)) - (0.036652 * Math.sin(m)) + (0.022235 * Math.sin(d+d-mp)) + (0.011484 * Math.sin(d+d)) + (0.003735 * Math.sin(mp+mp)) + (0.00192 * Math.sin(d)); + k = (1 - Math.cos(t))/2; + if (Math.sin(t) < 0) { + k = -k; + } + return (k); // Goes 0 -> 1 for waxing, and from -1 -> 0 for waning } function loadLocation() { @@ -44,11 +49,19 @@ WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { g.fillRect(CenterX - Radius, CenterY - Radius, CenterX + Radius, CenterY + Radius); g.setColor(0x41f); - mproz = moonPhase(); // mproz = 0..<1 + millis = (new Date()).getTime(); + if ((millis - lastCalculated) >= 7200000) { + phase = moonPhase(millis); + lastCalculated = millis; + } - leftFactor = mproz * 4 - 1; - rightFactor = (1 - mproz) * 4 - 1; - if (mproz >= 0.5) leftFactor = 1; else rightFactor = 1; + if (phase < 0) { // waning - phase goes from -1 to 0 + leftFactor = 1; + rightFactor = -1 - 2*phase; + } else { // waxing - phase goes from 0 to 1 + rightFactor = 1; + leftFactor = -1 + 2*phase; + } if (true == southernHemisphere) { var tmp=leftFactor; leftFactor=rightFactor; rightFactor=tmp; } diff --git a/apps/widshipbell/metadata.json b/apps/widshipbell/metadata.json new file mode 100644 index 000000000..c130b04ee --- /dev/null +++ b/apps/widshipbell/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "widshipbell", + "name": "Ship's bell Widget", + "shortName": "Ship's bell", + "version": "0.01", + "description": "A widget that buzzes according to a nautical bell, one strike at 04:30, two strikes at 05:00, up to eight strikes at 08:00 and so on.", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widshipbell.wid.js","url":"widget.js"}, + {"name":"widshipbell.settings.js","url":"settings.js"} + ], + "data": [{"name":"widshipbell.json"}] +} diff --git a/apps/widshipbell/settings.js b/apps/widshipbell/settings.js new file mode 100644 index 000000000..bb47e9b20 --- /dev/null +++ b/apps/widshipbell/settings.js @@ -0,0 +1,27 @@ +(function(back) { + var FILE = "widshipbell.json"; + // Load settings + var settings = Object.assign({ + strength: 1, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + // Show the menu + E.showMenu({ + "" : { "title" : "Ship's bell" }, + "< Back" : () => back(), + 'Strength': { + value: settings.strength, + min: 0, max: 2, + format: v => ["Off", "Weak", "Strong"][v], + onchange: v => { + settings.strength = v; + writeSettings(); + } + }, + }); +}) + diff --git a/apps/widshipbell/widget.js b/apps/widshipbell/widget.js new file mode 100644 index 000000000..e37edb6fd --- /dev/null +++ b/apps/widshipbell/widget.js @@ -0,0 +1,52 @@ +(() => { + const strength = Object.assign({ + strength: 1, + }, require('Storage').readJSON("widshipbell.json", true) || {}).strength; + + function replaceAll(target, search, replacement) { + return target.split(search).join(replacement); + } + + function check() { + const now = new Date(); + const currentMinute = now.getMinutes(); + const currentSecond = now.getSeconds(); + const etaMinute = 30-(currentMinute % 30); + + if (etaMinute === 30 && currentSecond === 0) { + const strikeHour = now.getHours() % 4; + // buzz now + let pattern=''; + if (strikeHour === 0 && currentMinute == 0) { + pattern = '.. .. .. ..'; + } else if (strikeHour === 0 && currentMinute === 30) { + pattern = '.'; + } else if (strikeHour === 1 && currentMinute === 0) { + pattern = '..'; + } else if (strikeHour === 1 && currentMinute === 30) { + pattern = '.. .'; + } else if (strikeHour === 2 && currentMinute === 0) { + pattern = '.. ..'; + } else if (strikeHour === 2 && currentMinute === 30) { + pattern = '.. .. .'; + } else if (strikeHour === 3 && currentMinute === 0) { + pattern = '.. .. ..'; + } else if (strikeHour === 3 && currentMinute === 30) { + pattern = '.. .. .. .'; + } + pattern = replaceAll(pattern, ' ', ' '); // 4x pause + pattern = replaceAll(pattern, '.', '. '); // pause between bells + if (strength === 2) { // strong selected + pattern = replaceAll(pattern, '.', ':'); + } + require("buzz").pattern(pattern); + } + + const etaSecond = etaMinute*60-currentSecond; + setTimeout(check, etaSecond*1000); + } + + if (strength !== 0) { + check(); + } +})(); diff --git a/apps/widshipbell/widget.png b/apps/widshipbell/widget.png new file mode 100644 index 000000000..891679f7d Binary files /dev/null and b/apps/widshipbell/widget.png differ