diff --git a/apps.json b/apps.json index 4a4d94278..148833678 100644 --- a/apps.json +++ b/apps.json @@ -115,7 +115,7 @@ { "id": "setting", "name": "Settings", - "version": "0.32", + "version": "0.33", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", @@ -540,6 +540,21 @@ ], "data": [{"name":"trex.score","storageFile":true}] }, + { + "id": "cubescramble", + "name": "Cube Scramble", + "version":"0.01", + "description": "A random scramble generator for the 3x3 Rubik's cube", + "icon": "cube-scramble.png", + "tags": "", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"cubescramble.app.js","url":"cube-scramble.js"}, + {"name":"cubescramble.img","url":"cube-scramble-icon.js","evaluate":true} + ] + }, { "id": "astroid", "name": "Asteroids!", @@ -4262,5 +4277,20 @@ "storage": [ {"name":"a_battery_widget.wid.js","url":"widget.js"} ] + }, + { + "id": "lcars", + "name": "LCARS Clock", + "shortName":"LCARS", + "icon": "lcars.png", + "version":"0.01", + "supports": ["BANGLEJS2"], + "description": "Library Computer Access Retrieval System (LCARS) clock.", + "type": "clock", + "tags": "clock", + "storage": [ + {"name":"lcars.app.js","url":"lcars.app.js"}, + {"name":"lcars.img","url":"lcars.icon.js","evaluate":true} + ] } ] diff --git a/apps/cubescramble/ChangeLog b/apps/cubescramble/ChangeLog new file mode 100644 index 000000000..28f11c1c7 --- /dev/null +++ b/apps/cubescramble/ChangeLog @@ -0,0 +1 @@ +0.01: Initial Release diff --git a/apps/cubescramble/README.md b/apps/cubescramble/README.md new file mode 100644 index 000000000..779e32489 --- /dev/null +++ b/apps/cubescramble/README.md @@ -0,0 +1,18 @@ +# Cube Scramble + +A random scramble generator for the 3x3 Rubik's cube + +## Future features + +I'm keen to complete this project with + +* Add a timer +* Add the ability for times to be stored and exported + +## Requests + +Please reach out if you have feature requests or notice bugs. + +## Creator + +Made by [Nathan Lisgo](https://github.com/nlisgo) diff --git a/apps/cubescramble/cube-scramble-icon.js b/apps/cubescramble/cube-scramble-icon.js new file mode 100644 index 000000000..32ea10836 --- /dev/null +++ b/apps/cubescramble/cube-scramble-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("3YANB54AFgf/+ULCqOw/4ACC6EAxEAC4fwCp2YxGIwEPC4XxGBewqEICwIABGAUQRJWwp3u9xFBAAQUCr3u8gwHgAVBC4ugCwIKDC5nghGAgEODoIKEC44PBBgUAggFDBQZIHhAMB8j4CMgQHBgoDBoB5H3BuC5nM4AqDJAIABgnd7owF3GAhgWBAAJbD8oWBCoIABoAXLqBIBCoNmC4nQC4xGCJAQVBs93u9ghoYDJAmwwFwFwYWBCoIABuBeC7oLBGAmBu0MFgMFDIMHDAZIB6AhBiCoFBgIVBJAQXFAAMv//wI4kBKoJgDSIIVBuwVBgP/AAIXEJoNnuCRFg9gCoPwh4XHTwMHvxHDPQc///xC5PgC4PgCoPFLIQSC+QXEVAb9CunggpYCwMDC4/9C4bnBqFldQWIAAIXH+FNC4deJAMAC4IWCxECC4nybQQXDh3u8vQC4qiEHILxCC4ewF4PdJQIXDO4UwgEYC42wgEOC4NeoAXE+IsBhAFBC4u16EAC4PV8AWCwCpCCwIXDoBGD7rPB7vUC4UQCwRmDBoPQuwXE6ghCgAVEgsBC4fQu4XFD4MN7tE8oWD7oXEu4XE3YXCqHdovuoCgDI4WBC4VwC4YsB6HMgjBBYQKHCC4OKhnAC4K+DJAQXB4DxB9wfBC4cFBYN3tYWDAAO9C4PMC48ABQPAIogADqAMCpwXFhgKCIohJErgNBgoXDqHxC4QWIAAINCg1errmBh4XC4AWJAAIwBg9gonQgf/+QXBFxRJCC4N2OYP/C4VQCxZJCg61Bh4XB+cACxoABC4VvC4PxCx5JBC4k7C6AwCuf/+AWRAANnuX/RZhJIuBFSAAaLMA=")) diff --git a/apps/cubescramble/cube-scramble.js b/apps/cubescramble/cube-scramble.js new file mode 100644 index 000000000..c0b1d11c3 --- /dev/null +++ b/apps/cubescramble/cube-scramble.js @@ -0,0 +1,74 @@ + +// Scramble code from: https://raw.githubusercontent.com/bjcarlson42/blog-post-sample-code/master/Rubik's%20Cube%20JavaScript%20Scrambler/part_two.js +const makeScramble = () => { + const options = ["F", "F2", "F'", "R", "R2", "R'", "U", "U2", "U'", "B", "B2", "B'", "L", "L2", "L'", "D", "D2", "D'"]; + const numOptions = [0, 1, 2, 3, 4, 5]; // 0 = F, 1 = R, 2 = U, 3 = B, 4 = L, 5 = D + const scrambleMoves = []; + let bad = true; + + while (bad) { + let scramble = []; + for (let i = 0; i < 20; i++) { + scramble.push(numOptions[getRandomInt(6)]); + } + // check if moves directly next to each other involve the same letter + for (let i = 0; i < 20 - 1; i++) { + if (scramble[i] == scramble[i + 1]) { + bad = true; + break; + } else { + bad = false; + } + } + } + // switch numbers to letters + let move; + for (let i = 0; i < 20; i++) { + switch (scramble[i]) { + case 0: + move = options[getRandomInt(3)]; // 0,1,2 + scrambleMoves.push(move); + break; + case 1: + move = options[getRandomIntBetween(3, 6)]; // 3,4,5 + scrambleMoves.push(move); + break; + case 2: + move = options[getRandomIntBetween(6, 9)]; // 6,7,8 + scrambleMoves.push(move); + break; + case 3: + move = options[getRandomIntBetween(9, 12)]; // 9,10,11 + scrambleMoves.push(move); + break; + case 4: + move = options[getRandomIntBetween(12, 15)]; // 12,13,14 + scrambleMoves.push(move); + break; + case 5: + move = options[getRandomIntBetween(15, 18)]; // 15,16,17 + scrambleMoves.push(move); + break; + } + } + return scrambleMoves; +}; + +const getRandomInt = max => Math.floor(Math.random() * Math.floor(max)); // returns up to max - 1 + +const getRandomIntBetween = (min, max) => Math.floor(Math.random() * (max - min) + min); + +const presentScramble = () => { + g.clear(); + E.showMessage(makeScramble().join(" ")); +}; + +const init = () => { + presentScramble(); + + setWatch(() => { + presentScramble(); + }, BTN1, {repeat:true}); +}; + +init(); diff --git a/apps/cubescramble/cube-scramble.png b/apps/cubescramble/cube-scramble.png new file mode 100644 index 000000000..9d0a7c652 Binary files /dev/null and b/apps/cubescramble/cube-scramble.png differ diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog new file mode 100644 index 000000000..c7ec09d30 --- /dev/null +++ b/apps/lcars/ChangeLog @@ -0,0 +1 @@ +0.01: Launch app diff --git a/apps/lcars/README.md b/apps/lcars/README.md new file mode 100644 index 000000000..fdce30c1b --- /dev/null +++ b/apps/lcars/README.md @@ -0,0 +1,8 @@ +# LCARS clock + +A simple LCARS inspired clock that shows: + * Current time + * Current date + * Battery level + * Steps + diff --git a/apps/lcars/background.png b/apps/lcars/background.png new file mode 100644 index 000000000..1ee4297c6 Binary files /dev/null and b/apps/lcars/background.png differ diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js new file mode 100644 index 000000000..cf884a6b7 --- /dev/null +++ b/apps/lcars/lcars.app.js @@ -0,0 +1,99 @@ +const locale = require('locale'); + + +/* + * Assets: Images, fonts etc. + */ +var img = { + width : 176, height : 151, bpp : 3, + transparent : 0, + buffer : require("heatshrink").decompress(atob("gF58+eAR14IN1fvv374CN7yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AH4A/AH4A/AB1z588+YCN+RBuj158+eARyD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AUhyD/gEDQaHz4BCuQaNAIN0PQaHIIN0BQaF5IN0AQaHPkBBug6DQ8iEvQaE8yBBuhyDPAQNAINsBQaACBkhCuQaACpVo0cQaACo4CFGjyD/AAMPQf4ACQf4ADgiD+AH4A/AH8J02atICIwEAgPnz15AR3gEgM27dt2wCTF4IABgYROgN9+/fAR14ILsaQBKDakwjKF5oABKZ6DwgxTPQeEmQf5cPQeMBLhyDxgJTRQd0JKaKDuhKD/gENQf6D/F4VNQf8AKaKDvKBYnBAGZQKzBB1QZOwIGqDJsBA2QZJA3QZGYIPCDH4CD/0xA4QY+wIPKDGwCD/tpB6Qf6DHthA5QY1oIPSD/QY9gQf/bIPaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AF8JQYgCdsEHnnz54CJgIdLwEAhqDEATtggPnz15ARHkgIdLIIKAgQcCAgQcAA/gAA==")) +} + +Graphics.prototype.setFontMinaSmall = function(scale) { + // Actual height 18 (17 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAA/8w/8wAAQAAAAAA4AA8AAAAA8AAwAAAAAAEABEABEQB/w/8AxEABEwB/w/8AxEABEABEAAAAH4MP8MMEM8GPcGOMGMMGMMH4ABwAAA/gAwgAggAggQwhw/nAAOAA4ADgAOfw8YwwQwAQQAYwAfwAPgAGAAfg85w/wwzgww4wwdgwHggHgAPwAIQAAA8AAwAAAAAAfwH/+fAP4ABgAAgAA4ABfAPH/+A/wAAAAAAEAAHgAfAAfAAHgAMAAAAAAAABgABgABgAf4AP4ABgABgABAAAAAADAADwADAAAAAgAAwAAwAAwAAwAAwAAAAADAADAADAAAAAAEAA8AH4A+AHwA+AAwAAAAAf/g//wwAwwAwwAwwAwwAwwAwf/gH+AAAAAAAYAAwAAwAA//wAAAAAAAAAwAwwBwwDwwDwwGwwcww4wfwwPAwAAAAAAwAwwQwwQwwQwwQwwYww4wf/gHHAAAAAEAAeAA+ADmAPGAcGAwGAh/wD/wAEAAEAAAAf4w/4wwwwwwwwwwwwwwwww/wgfgAAAAAAP/Af/gwwwwwwwwwwwwwwwwwww/gAAAgAAwAAwAAwAwwHww/Az4A/AA8AAAAAAAAfPg//wxwwwwwwwwwwwwww//wffgAAAAAAfwQ/wwwQwwYwwQwwQwwQw//wP/AAAAAAAMDAMDAMDAAAAAAAMDAMDwMDAAAAAAADgADgAHwAGwAMYAMYAIIAAAAEQAGYAGYAGYAGYAGYAGYAGYAAAAMYAMYAGwAGwAHgADgADAAAAAwAAwAAwAAwcwwcwwQAwQA/wAfgAAAAAAAB/8D/+TAGbHjbPzbMTbMzbMzb/zZ/zYAGf/+H/8AAAAAAABwAPwA+AH+A+GA8GAfmAD+AAfgADwAAQAAA//w//wwwwwwwwwwwwwxww//wffgAAAAAAP/Af/gwBwwAwwAwwAwwAwwAwwAwAAA//w//wwAwwAwwAwwAwwAw4Bwf/gH+AAAAAAAf/g//wwQwgQQgQQgQQgQQgQQgAQAAAf/w//wwQAgQAgQAgQAgQAgQAgAAAAAP/Af/gwAwwAwwAwwYwwYwwfwwfwAAAAAA//w//wAYAAYAAYAAYAAYAAYA//w//wAAA//w//wAAAAAAAAwAAwAAw//w//AAAA//w//wAYAA4AD8AHHAeDg4AwgAQAAAAAA//g//wAAwAAwAAwAAwAAwAAwAAQAAAP/w//w+AAPwAB+AAHwADwA/gH4A/AA/4A//wAAwAAA//w//wcAAPAADgAA4AAeAAHAADw//wAAAAAAH/Af/g4AwwAwwAwwAwwAwwAwcDwP/gB4AAAA//w//wwQAwQAwQAwYAwwA/wAPgAAAAH/Af/gwAwwAwwAwwA8wA8wA2cDkP/gB4AAAA//w//wwYAwYAwYAwcAwfA/zwPgwAAAAAAfgA/wwwQwwYwwYwwYwwYwwfwAPgAAAAAAwAAwAAwAA//w//wwAAwAAwAAwAAAAA/+A//gABwAAwAAwAAwAAwAAwAPg//AAAAAAA4AA/AAH4AA/AAHwADwAfgD8AfgA8AAgAAAAA4AA/AAH4AA/AAHwAHwA/AP4A/4Aw/AAHwAHwA/AP4A+AAwAAAAAwAw8DwOHAD8AB4AD8AOHA8DwwAwAAAAAAwAA8AAPAADwAA/wB/wHgAeAA4AAgAAgAQwBwwHwwOww8wxww3gw+Aw4AwwAQAAAH//f//YAAYAAQAAwAA+AAPwAB+AAPwAB8AAMQAAYAAYAAf//AAAAAA"), 32, atob("BgUHDAoRCwMGBggJBQYFBwwHCwsLCwsKCwsFBQkICQoPDAsKDAoKCwsEBgsKDgwMCgwLCwoMDBELCwoGBwY="), 18+(scale<<8)+(1<<16)); +} + +Graphics.prototype.setFontMinaLarge = function(scale) { + // Actual height 35 (34 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAB4AAAAAPgAAAAA+AAAAAD4AAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAH4AAAAD/gAAAB/8AAAA/+AAAAf/AAAAP/gAAAH/wAAAD/4AAAD/8AAAB/+AAAAP/AAAAA/AAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///4AA////wAH////gA+AAAfADwAAA8AOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA8AAAPAD4AAB8AH////gAP///8AAf///gAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAcAAAAADgAAAAAOAAAAAB4AAAAAHAAAAAA8AAAAAD////8AP////wA/////AD////8AAAAAAAAAAAAAAAAAAAAAGAAAAAA4AAAHADgAAA8AOAAAHwA4AAA/ADgAAD8AOAAAfwA4AAD/ADgAAfcAOAAD5wA4AAfHADgAD4cAOAAfBwA4AD8HADwAfgcAPAD8BwAeA/AHAB+f4AcAD//ABwAH/wAHAAH8AAcAAAAAAAAAAAAAAAAAAAAAGAAABgAYAAAGADgAAAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADwB4A8APAPgDwAfD//+AB////4AD/8//AAD/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAA8AAAAAPwAAAAD/AAAAAf8AAAAH9wAAAB/HAAAAPwcAAAD+BwAAAfgHAAAH8AcAAB/ABwAAPwAHAAA+AAcAADgABwAAIAAHgAAAH///AAB///8AAH///wAAAAcAAAAABwAAAAAHAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAH/8AYAP//wBgA///AHAD//wAcAOAPABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgA8AOAOADwA4A8APADgD8H4AOAH//gA4AP/8AAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAD//+AAB///+AAP///8AB/BgH4APgOAHwA8A4APADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDwA8AOAP//wA4Af/+ABgA//wAAAA/8AAAAAAAAAAAAAAAAAAAAAA4AAAAADgAAAAAOAAAAAA4AAAAADgAAAAAOAAAAQA4AAAHADgAAD8AOAAA/wA4AAf+ADgAP/gAOAD/wAA4B/8AADg/+AAAOP/AAAA//wAAAD/4AAAAP8AAAAA/AAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/h/4AA//P/wAH////gA+B/AfADwD4A8AOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAPAPgDwA8A+APAB////4AH////gAP/j/8AAD4B8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAA//gAYAH//ABwA//+AHADwB4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAPADgDwA+AOAfAB+A4f4AD////AAH///4AAD//8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAeAAB8AD4AAHwAPgAAfAA+AAB4AB4AAAAAAAAAAAAAAAAAAAAAA="), 46, atob("CxAaDhgYGBgZFhkZCw=="), 40+(scale<<8)+(1<<16)); +} + + +/* + * Queue drawing every minute + */ +var drawTimeout; +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +/* + * Draw watch face + */ +function draw(){ + g.reset(); + g.clearRect(0, 24, g.getWidth(), g.getHeight()); + + // Draw background image + g.drawImage(img, 0, 24); + + // Write time + var currentDate = new Date(); + var timeStr = locale.time(currentDate,1); + g.setFontAlign(0,0,0); + g.setFontMinaLarge(); + g.drawString(timeStr, 115, 53); + + // Write date + g.setFontAlign(-1,-1,0); + g.setFontMinaSmall(); + + var dayName = locale.dow(currentDate, true).toUpperCase(); + var day = currentDate.getDate(); + g.drawString("DATE:", 40, 107); + g.drawString(dayName + " " + day, 100, 105); + + // Draw battery + var bat = E.getBattery(); + g.drawString("BAT:", 40, 127); + g.drawString(bat+"%", 100, 127); + + // Draw steps + var steps = Bangle.getStepCount(); + g.drawString("STEP:", 40, 147); + g.drawString(steps, 100, 147); + + // Queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); + +// draw immediately at first, queue update +draw(); + + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/lcars/lcars.icon.js b/apps/lcars/lcars.icon.js new file mode 100644 index 000000000..c404728e0 --- /dev/null +++ b/apps/lcars/lcars.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO")) diff --git a/apps/lcars/lcars.png b/apps/lcars/lcars.png new file mode 100644 index 000000000..167352ef4 Binary files /dev/null and b/apps/lcars/lcars.png differ diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 63f77edcf..faa50405f 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -35,3 +35,4 @@ 0.30: Move '< Back' to the top of menus 0.31: Remove Bangle 1 settings when running on Bangle 2 0.32: Fix 'beep' menu on Bangle.js 2 +0.33: Really fix 'beep' menu on Bangle.js 2 this time diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 395fdef00..fcf651b6f 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -38,7 +38,7 @@ function resetSettings() { quiet: 0, // quiet mode: 0: off, 1: priority only, 2: total silence timeout: 10, // Default LCD timeout in seconds vibrate: true, // Vibration enabled by default. App must support - beep: "vib", // Beep enabled by default. App must support + beep: BANGLEJS2?true:"vib", // Beep enabled by default. App must support timezone: 0, // Set the timezone for the device HID: false, // BLE HID mode, off by default clock: null, // a string for the default clock's name @@ -73,9 +73,9 @@ const boolFormat = v => v ? "On" : "Off"; function showMainMenu() { var beepMenuItem; - if (BANGLEJS2) { // Bangle.js 2 is simply on/off + if (BANGLEJS2) { beepMenuItem = { - value: settings.beep, + value: settings.beep!=false, format: boolFormat, onchange: v => { settings.beep = v; @@ -85,8 +85,8 @@ function showMainMenu() { setTimeout(()=>VIBRATE.reset(),200); } // beep with vibration moter } - } - } else { // Bangle.js 1 has different options + }; + } else { // Bangle.js 1 var beepV = [false, true, "vib"]; var beepN = ["Off", "Piezo", "Vibrate"]; beepMenuItem = { @@ -121,8 +121,8 @@ function showMainMenu() { 'Vibration': { value: settings.vibrate, format: boolFormat, - onchange: v => { - settings.vibrate = v; + onchange: () => { + settings.vibrate = !settings.vibrate; updateSettings(); if (settings.vibrate) { VIBRATE.write(1);