{ Bangle.loadWidgets(); Bangle.drawWidgets(); const BANGLEJS2 = process.env.HWVERSION==2; const storage = require('Storage'); let settings; const scrolls = []; let menuScroller; function updateSettings() { //storage.erase('setting.json'); // - not needed, just causes extra writes if settings were the same storage.write('setting.json', settings); } function pushMenu(menu) { if(menuScroller) scrolls.push(menuScroller.scroll); // if !menu, we're just pushing and something else takes // care of E.showMenu() if(menu) { const m = E.showMenu(menu); menuScroller = m.scroller; return m; } } function restoreMenu(menu) { // equivalent to pushMenu(null); popMenu(menu); if(!menu[""]) menu[""] = {}; menu[""].scroll = menuScroller.scroll; menuScroller = E.showMenu(menu).scroller; } function popMenu(menu) { if(!menu[""]) menu[""] = {}; menu[""].scroll = scrolls.pop() | 0; const m = E.showMenu(menu); menuScroller = m.scroller; return m; } function updateOptions() { var o = settings.options; // Check to make sure nobody disabled all wakeups and locked themselves out! if (BANGLEJS2) { if (!(o.wakeOnBTN1||o.wakeOnFaceUp||o.wakeOnTouch||o.wakeOnDoubleTap||o.wakeOnTwist)) { o.wakeOnBTN1 = true; } } else { if (!(o.wakeOnBTN1||o.wakeOnBTN2||o.wakeOnBTN3||o.wakeOnFaceUp||o.wakeOnTouch||o.wakeOnTwist)) o.wakeOnBTN2 = true; } updateSettings(); Bangle.setOptions(o) } function resetSettings() { settings = { ble: true, // Bluetooth enabled by default blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used? log: false, // Do log messages appear on screen? 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: 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 // clockHasWidgets: false, // Does the clock in 'clock' contain the string 'Bangle.loadWidgets' "12hour" : false, // 12 or 24 hour clock? firstDayOfWeek: 0, // 0 -> Sunday (default), 1 -> Monday brightness: 1, // LCD brightness from 0 to 1 // welcomed : undefined/true (whether welcome app should show) options: { wakeOnBTN1: true, wakeOnBTN2: true, wakeOnBTN3: true, wakeOnFaceUp: false, wakeOnTouch: false, wakeOnTwist: false, twistThreshold: 819.2, twistMaxY: -800, twistTimeout: 1000 }, }; updateSettings(); } settings = storage.readJSON('setting.json', 1); if (("object" != typeof settings) || ("object" != typeof settings.options)) resetSettings(); function mainMenu() { const mainmenu = { '': { 'title': /*LANG*/'Settings' }, '< Back': ()=>load(), /*LANG*/'Apps': ()=>pushMenu(appSettingsMenu()), /*LANG*/'System': ()=>pushMenu(systemMenu()), /*LANG*/'Bluetooth': ()=>pushMenu(BLEMenu()), /*LANG*/'Alerts': ()=>pushMenu(alertsMenu()), /*LANG*/'Utils': ()=>pushMenu(utilMenu()) }; return mainmenu; } function systemMenu() { const mainmenu = { '': { 'title': /*LANG*/'System' }, '< Back': ()=>popMenu(mainMenu()), /*LANG*/'Theme': ()=>showThemeMenu(), /*LANG*/'LCD': ()=>pushMenu(LCDMenu()), /*LANG*/'Locale': ()=>pushMenu(localeMenu()), /*LANG*/'Clock': ()=>pushMenu(clockMenu()), /*LANG*/'Launcher': ()=>pushMenu(launcherMenu()), /*LANG*/'Date & Time': ()=>pushMenu(setTimeMenu()) }; return mainmenu; } function alertsMenu() { var beepMenuItem; if (BANGLEJS2) { beepMenuItem = { value: settings.beep!=false, onchange: v => { settings.beep = v; updateSettings(); if (settings.beep) { analogWrite(VIBRATE,0.1,{freq:2000}); setTimeout(()=>VIBRATE.reset(),200); } // beep with vibration moter } }; } else { // Bangle.js 1 var beepV = [false, true, "vib"]; var beepN = [/*LANG*/"Off", /*LANG*/"Piezo", /*LANG*/"Vibrate"]; beepMenuItem = { value: Math.max(0 | beepV.indexOf(settings.beep),0), min: 0, max: beepV.length-1, format: v => beepN[v], onchange: v => { settings.beep = beepV[v]; if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo on Bangle.js 1 else if (v==2) { analogWrite(VIBRATE,0.1,{freq:2000});setTimeout(()=>VIBRATE.reset(),200); } // vibrate updateSettings(); } }; } const mainmenu = { '': { 'title': /*LANG*/'Alerts' }, '< Back': ()=>popMenu(mainMenu()), /*LANG*/'Beep': beepMenuItem, /*LANG*/'Vibration': { value: settings.vibrate, onchange: () => { settings.vibrate = !settings.vibrate; updateSettings(); if (settings.vibrate) { VIBRATE.write(1); setTimeout(() => VIBRATE.write(0), 10); } } }, /*LANG*/"Quiet Mode": { value: settings.quiet|0, format: v => [/*LANG*/"Off", /*LANG*/"Alarms", /*LANG*/"Silent"][v%3], onchange: v => { settings.quiet = v%3; updateSettings(); updateOptions(); if ("qmsched" in WIDGETS) WIDGETS["qmsched"].draw(); }, } }; return mainmenu; } function BLEMenu() { var hidV = [false, "kbmedia", "kb", "com", "joy"]; var hidN = [/*LANG*/"Off", /*LANG*/"Kbrd & Media", /*LANG*/"Kbrd", /*LANG*/"Kbrd & Mouse", /*LANG*/"Joystick"]; var privacy = [/*LANG*/"Off", /*LANG*/"Show name", /*LANG*/"Hide name"]; return { '': { 'title': /*LANG*/'Bluetooth' }, '< Back': ()=>popMenu(mainMenu()), /*LANG*/'Make Connectable': ()=>makeConnectable(), /*LANG*/'BLE': { value: settings.ble, onchange: () => { settings.ble = !settings.ble; updateSettings(); } }, /*LANG*/'Programmable': { value: settings.blerepl, onchange: () => { settings.blerepl = !settings.blerepl; updateSettings(); } }, /*LANG*/'Privacy': { min: 0, max: privacy.length-1, format: v => privacy[v], value: (() => { // settings.bleprivacy may be some custom object, but we ignore that for now if (settings.bleprivacy && settings.blename === false) return 2; if (settings.bleprivacy) return 1; return 0; })(), onchange: v => { settings.bleprivacy = 0; delete settings.blename; switch (v) { case 0: break; case 1: settings.bleprivacy = 1; break; case 2: settings.bleprivacy = 1; settings.blename = false; break; } updateSettings(); } }, /*LANG*/'HID': { value: Math.max(0,0 | hidV.indexOf(settings.HID)), min: 0, max: hidN.length-1, format: v => hidN[v], onchange: v => { settings.HID = hidV[v]; updateSettings(); } }, /*LANG*/'Passkey': { value: settings.passkey?settings.passkey:/*LANG*/"none", onchange: () => setTimeout(() => pushMenu(passkeyMenu())) // graphical_menu redraws after the call }, /*LANG*/'Whitelist': { value: ( (settings.whitelist_disabled || !settings.whitelist) ? /*LANG*/"off" : /*LANG*/"on" ) + ( settings.whitelist ? " (" + settings.whitelist.length + ")" : "" ), onchange: () => setTimeout(() => pushMenu(whitelistMenu())) // graphical_menu redraws after the call } }; } function showThemeMenu(pop) { function cl(x) { return g.setColor(x).getColor(); } function upd(th) { g.theme = th; settings.theme = th; updateSettings(); delete g.reset; g._reset = g.reset; g.reset = function(n) { return g._reset().setColor(th.fg).setBgColor(th.bg); }; g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); }; g.clear(1); Bangle.drawWidgets(); m.draw(); } var themesMenu = { '':{title:/*LANG*/'Theme'}, '< Back': ()=>popMenu(systemMenu()), /*LANG*/'Dark BW': ()=>{ upd({ fg:cl("#fff"), bg:cl("#000"), fg2:cl("#fff"), bg2:cl("#004"), fgH:cl("#fff"), bgH:cl("#00f"), dark:true }); }, /*LANG*/'Light BW': ()=>{ upd({ fg:cl("#000"), bg:cl("#fff"), fg2:cl("#000"), bg2:cl("#cff"), fgH:cl("#000"), bgH:cl("#0ff"), dark:false }); } }; storage.list(/^.*\.theme$/).forEach( n => { let newTheme = 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 = (pop ? popMenu : pushMenu)(themesMenu); function showCustomThemeMenu() { function setT(t, v) { let th = g.theme; th[t] = v; if (t==="bg") { th['dark'] = (v===cl("#000")); } upd(th); } let rgb = {}; rgb[/*LANG*/'black'] = "#000"; rgb[/*LANG*/'white'] = "#fff"; rgb[/*LANG*/'red'] = "#f00"; rgb[/*LANG*/'green'] = "#0f0"; rgb[/*LANG*/'blue'] = "#00f"; rgb[/*LANG*/'cyan'] = "#0ff"; rgb[/*LANG*/'magenta'] = "#f0f"; rgb[/*LANG*/'yellow'] = "#ff0"; if (!BANGLEJS2) { // these would cause dithering, which is not great for e.g. text rgb[/*LANG*/'orange'] = "#ff7f00"; rgb[/*LANG*/'purple'] = "#7f00ff"; rgb[/*LANG*/'grey'] = "#7f7f7f"; } let colors = [], names = []; for(const c in rgb) { names.push(c); colors.push(cl(rgb[c])); } let menu = { '':{title:/*LANG*/'Custom Theme'}, "< Back": () => showThemeMenu(1) }; const labels = { fg: /*LANG*/'Foreground', bg: /*LANG*/'Background', fg2: /*LANG*/'Foreground 2', bg2: /*LANG*/'Background 2', fgH: /*LANG*/'Highlight FG', bgH: /*LANG*/'Highlight BG', }; ["fg", "bg", "fg2", "bg2", "fgH", "bgH"].forEach(t => { menu[labels[t]] = { min : 0, max : colors.length-1, wrap : true, value: Math.max(colors.indexOf(g.theme[t]),0), format: v => names[v], onchange: function(v) { var c = colors[v]; // if we select the same fg and bg: set the other to the old color // e.g. bg=black;fg=white, user selects fg=black -> bg changes to white automatically // so users don't end up with a black-on-black menu if (t === 'fg' && g.theme.bg === c) setT('bg', g.theme.fg); if (t === 'bg' && g.theme.fg === c) setT('fg', g.theme.bg); setT(t, c); }, }; }); m = pushMenu(menu); } } function passkeyMenu() { var menu = { "< Back" : ()=>popMenu(BLEMenu()), /*LANG*/"Disable" : () => { settings.passkey = undefined; updateSettings(); popMenu(BLEMenu()); } }; if (!settings.passkey || settings.passkey.length!=6) { settings.passkey = "123456"; updateSettings(); } for (var i=0;i<6;i++) (function(i){ menu[`Digit ${i+1}`] = { value : 0|settings.passkey[i], min: 0, max: 9, onchange: v => { var p = settings.passkey.split(""); p[i] = v; settings.passkey = p.join(""); updateSettings(); } }; })(i); return menu; } function whitelistMenu() { var menu = { "< Back" : ()=>popMenu(BLEMenu()), }; if (settings.whitelist_disabled) { menu[/*LANG*/"Enable"] = () => { delete settings.whitelist_disabled; updateSettings(); popMenu(BLEMenu()); }; } else { menu[/*LANG*/"Disable"] = () => { settings.whitelist_disabled = true; updateSettings(); popMenu(BLEMenu()); }; } if (settings.whitelist) settings.whitelist.forEach(function(d){ menu[d.substr(0,17)] = function() { E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => { if (v) { settings.whitelist.splice(settings.whitelist.indexOf(d),1); updateSettings(); } setTimeout(() => restoreMenu(whitelistMenu()), 50); }); } }); menu[/*LANG*/'Add Device']=function() { E.showAlert(/*LANG*/"Connect device\nto add to\nwhitelist",/*LANG*/"Whitelist").then(function() { NRF.removeAllListeners('connect'); restoreMenu(whitelistMenu()); }); NRF.removeAllListeners('connect'); NRF.on('connect', function(addr) { if (!settings.whitelist) settings.whitelist=[]; delete settings.whitelist_disabled; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) { addr = resolvedAddr + " (resolved)"; } } settings.whitelist.push(addr); updateSettings(); NRF.removeAllListeners('connect'); restoreMenu(whitelistMenu()); }); }; return menu; } function LCDMenu() { // converts g to Espruino internal unit function gToInternal(g) { return g * 8192; } // converts Espruino internal unit to g function internalToG(u) { return u / 8192; } var rotNames = [/*LANG*/"No",/*LANG*/"Rotate CW",/*LANG*/"Left Handed",/*LANG*/"Rotate CCW",/*LANG*/"Mirror"]; const lcdMenu = { '': { 'title': 'LCD' }, '< Back': ()=>popMenu(systemMenu()), }; if (BANGLEJS2) Object.assign(lcdMenu, { /*LANG*/'Calibrate': () => showTouchscreenCalibration() }); Object.assign(lcdMenu, { /*LANG*/'LCD Brightness': { value: settings.brightness, min: 0.1, max: 1, step: 0.1, onchange: v => { settings.brightness = v || 1; updateSettings(); Bangle.setLCDBrightness(settings.brightness); } }, /*LANG*/'LCD Timeout': { value: settings.timeout, min: 0, max: 60, step: 5, onchange: v => { settings.timeout = 0 | v; updateSettings(); Bangle.setLCDTimeout(settings.timeout); } }, /*LANG*/'Rotate': { value: 0|settings.rotate, min: 0, max: rotNames.length-1, format: v=> rotNames[v], onchange: v => { settings.rotate = 0 | v; updateSettings(); g.setRotation(settings.rotate&3,settings.rotate>>2).clear(); Bangle.drawWidgets(); } } }); if (BANGLEJS2) { Object.assign(lcdMenu, { /*LANG*/'Wake on Button': { value: !!settings.options.wakeOnBTN1, onchange: () => { settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1; updateOptions(); } }, /*LANG*/'Wake on Tap': { value: !!settings.options.wakeOnTouch, onchange: () => { settings.options.wakeOnTouch = !settings.options.wakeOnTouch; updateOptions(); } } }); if (process.env.VERSION.replace("v",0)>=2020) Object.assign(lcdMenu, { /*LANG*/'Wake on Double Tap': { value: !!settings.options.wakeOnDoubleTap, onchange: () => { settings.options.wakeOnDoubleTap = !settings.options.wakeOnDoubleTap; updateOptions(); } } }); } else Object.assign(lcdMenu, { /*LANG*/'Wake on BTN1': { value: !!settings.options.wakeOnBTN1, onchange: () => { settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1; updateOptions(); } }, /*LANG*/'Wake on BTN2': { value: !!settings.options.wakeOnBTN2, onchange: () => { settings.options.wakeOnBTN2 = !settings.options.wakeOnBTN2; updateOptions(); } }, /*LANG*/'Wake on BTN3': { value: !!settings.options.wakeOnBTN3, onchange: () => { settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3; updateOptions(); } }, /*LANG*/'Wake on Touch': { value: !!settings.options.wakeOnTouch, onchange: () => { settings.options.wakeOnTouch = !settings.options.wakeOnTouch; updateOptions(); } }}); Object.assign(lcdMenu, { /*LANG*/'Wake on FaceUp': { value: !!settings.options.wakeOnFaceUp, onchange: () => { settings.options.wakeOnFaceUp = !settings.options.wakeOnFaceUp; updateOptions(); } }, /*LANG*/'Wake on Twist': { value: !!settings.options.wakeOnTwist, onchange: () => { settings.options.wakeOnTwist = !settings.options.wakeOnTwist; updateOptions(); } }, /*LANG*/'Twist Threshold': { value: internalToG(settings.options.twistThreshold), min: -0.5, max: 0.5, step: 0.01, onchange: v => { settings.options.twistThreshold = gToInternal(v || 0.1); updateOptions(); } }, /*LANG*/'Twist Max Y': { value: settings.options.twistMaxY, min: -1500, max: 1500, step: 100, onchange: v => { settings.options.twistMaxY = v || -800; updateOptions(); } }, /*LANG*/'Twist Timeout': { value: settings.options.twistTimeout, min: 0, max: 2000, step: 100, onchange: v => { settings.options.twistTimeout = v || 1000; updateOptions(); } } }); return lcdMenu } function localeMenu() { const localemenu = { '': { 'title': /*LANG*/'Locale' }, '< Back': ()=>popMenu(systemMenu()), /*LANG*/'Time Zone': { value: settings.timezone, format: v => (v > 0 ? "+" : "") + v, min: -11, max: 13, step: 0.5, onchange: v => { settings.timezone = v || 0; updateSettings(); } }, /*LANG*/'Time Format': { value: !!settings["12hour"], format: v => v ? "12h" : "24h", onchange: v => { settings["12hour"] = v; updateSettings(); } }, /*LANG*/'Start Week On': { value: settings["firstDayOfWeek"] || 0, min: 0, // Sunday max: 1, // Monday format: v => require("date_utils").dow(v, 1), onchange: v => { settings["firstDayOfWeek"] = v; updateSettings(); }, } }; return localemenu; } function utilMenu() { var menu = { '': { 'title': /*LANG*/'Utilities' }, '< Back': ()=>popMenu(mainMenu()), /*LANG*/'Debug': { value: E.clip(0|settings.log,0,3), min: 0, max: 3, format: v => [/*LANG*/"Off",/*LANG*/"Display",/*LANG*/"Log", /*LANG*/"Both"][E.clip(0|v,0,3)], onchange: v => { settings.log = v; updateSettings(); } }, /*LANG*/'Compact Storage': () => { E.showMessage(/*LANG*/"Compacting...\nTakes approx\n1 minute",{title:/*LANG*/"Storage"}); storage.compact(); restoreMenu(utilMenu()); }, /*LANG*/'Rewrite Settings': () => { storage.write(".boot0","eval(require('Storage').read('bootupdate.js'));"); load("setting.app.js"); }, /*LANG*/'Flatten Battery': () => { E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.'); Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); Bangle.setLCDBrightness(1); if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat"); if (Bangle.setHRMPower) Bangle.setHRMPower(1,"flat"); if (Bangle.setCompassPower) Bangle.setCompassPower(1,"flat"); if (Bangle.setBarometerPower) Bangle.setBarometerPower(1,"flat"); setInterval(function() { var i=1000;while (i--); }, 1); } }; const back = () => { restoreMenu(utilMenu()); }; if (BANGLEJS2) menu[/*LANG*/'Calibrate Battery'] = () => { E.showPrompt(/*LANG*/"Is the battery fully charged?",{title:/*LANG*/"Calibrate",back}).then(ok => { if (ok) { var s=storage.readJSON("setting.json"); s.batFullVoltage = (analogRead(D3)+analogRead(D3)+analogRead(D3)+analogRead(D3))/4; storage.writeJSON("setting.json",s); E.showAlert(/*LANG*/"Calibrated!").then(() => load("setting.app.js")); } else { E.showAlert(/*LANG*/"Please charge Bangle.js for 3 hours and try again").then(() => load("setting.app.js")); } }); }; menu[/*LANG*/'Reset Settings'] = () => { E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings",back}).then((v) => { if (v) { E.showMessage(/*LANG*/'Resetting'); resetSettings(); setTimeout(() => popMenu(mainMenu()), 50); } else restoreMenu(utilMenu()); }); }; menu[/*LANG*/"Turn Off"] = () => { E.showPrompt(/*LANG*/"Are you sure? Alarms and timers won't fire", { title:/*LANG*/"Turn Off",back }).then((confirmed) => { if (confirmed) { E.showMessage(/*LANG*/"See you\nlater!", /*LANG*/"Goodbye"); setTimeout(() => { // clear the screen so when the user will turn on the watch they'll see // an empty screen instead of the latest displayed screen E.showMessage(); g.clear(true); Bangle.softOff ? Bangle.softOff() : Bangle.off(); }, 2500); } else { restoreMenu(utilMenu()); } }); }; if (Bangle.factoryReset) { menu[/*LANG*/'Factory Reset'] = ()=>{ E.showPrompt(/*LANG*/'This will remove everything!',{title:/*LANG*/"Factory Reset",back}).then((v) => { if (v) { var n = ((Math.random()*4)&3) + 1; E.showPrompt(/*LANG*/"To confirm, please press "+n,{ title:/*LANG*/"Factory Reset", buttons : {"1":1,"2":2,"3":3,"4":4}, back }).then(function(v) { if (v==n) { E.showMessage(); Terminal.setConsole(); Bangle.factoryReset(); } else { back(); } }); } else back(); }); } } return menu; } function makeConnectable() { try { NRF.wake(); } catch (e) { } Bluetooth.setConsole(1); NRF.ignoreWhitelist = 1; var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", ""); E.showPrompt(name + /*LANG*/"\nStay Connectable?", { title: /*LANG*/"Connectable" }).then(r => { if (settings.ble != r) { settings.ble = r; updateSettings(); } if (!r) try { NRF.sleep(); } catch (e) { } delete NRF.ignoreWhitelist; restoreMenu(BLEMenu()); }); } function clockMenu() { var clockApps = storage.list(/\.info$/) .map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "clock")?a:undefined}) .filter(app => app) // filter out any undefined apps .sort((a, b) => a.sortorder - b.sortorder); const back = ()=>popMenu(systemMenu()); const clockMenu = { '': { 'title': /*LANG*/'Select Clock', }, '< Back': back, }; clockApps.forEach((app, index) => { var label = app.name; if ((!settings.clock && index === 0) || (settings.clock === app.src)) { label = "* " + label; } clockMenu[label] = () => { settings.clock = app.src; settings.clockHasWidgets = storage.read(app.src).includes("Bangle.loadWidgets"); updateSettings(); back(); }; }); if (clockApps.length === 0) { clockMenu[/*LANG*/"No Clocks Found"] = () => { }; } return clockMenu; } function launcherMenu() { var launcherApps = storage.list(/\.info$/) .map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "launch")?a:undefined}) .filter(app => app) // filter out any undefined apps .sort((a, b) => a.sortorder - b.sortorder); const back = ()=>popMenu(systemMenu()); const launcherMenu = { '': { 'title': /*LANG*/'Select Launcher', }, '< Back': back, }; launcherApps.forEach((app, index) => { var label = app.name; if ((!settings.launcher && index === 0) || (settings.launcher === app.src)) { label = "* " + label; } launcherMenu[label] = () => { settings.launcher = app.src; updateSettings(); back(); }; }); if (launcherApps.length === 0) { launcherMenu[/*LANG*/"No Launchers Found"] = () => { }; } return launcherMenu; } function setTimeMenu() { let d = new Date(); const timemenu = { '': { 'title': /*LANG*/'Date & Time' }, '< Back': function () { setTime(d.getTime() / 1000); popMenu(systemMenu()); }, /*LANG*/'Day': { value: d.getDate(), onchange: function (v) { this.value = ((v+30)%31)+1; d.setDate(this.value); } }, /*LANG*/'Month': { value: d.getMonth() + 1, format: v => require("date_utils").month(v), onchange: function (v) { this.value = ((v+11)%12)+1; d.setMonth(this.value - 1); } }, /*LANG*/'Year': { value: d.getFullYear(), min: 2019, max: 2100, onchange: function (v) { d.setFullYear(v); } }, /*LANG*/'Hour': { value: d.getHours(), onchange: function (v) { this.value = (v+24)%24; d.setHours(this.value); } }, /*LANG*/'Minute': { value: d.getMinutes(), onchange: function (v) { this.value = (v+60)%60; d.setMinutes(this.value); } }, /*LANG*/'Second': { value: d.getSeconds(), onchange: function (v) { this.value = (v+60)%60; d.setSeconds(this.value); } } }; return timemenu; } function appSettingsMenu() { let appmenu = { '': { 'title': /*LANG*/'App Settings' }, '< Back': ()=>popMenu(mainMenu()), } const apps = storage.list(/\.settings\.js$/) .map(s => s.substr(0, s.length-12)) .map(id => { const a=storage.readJSON(id+'.info',1) || {name: id}; return {id:id,name:a.name,sortorder:a.sortorder}; }) .sort((a, b) => { const n = (0|a.sortorder)-(0|b.sortorder); if (n) return n; // do sortorder first if (a.nameb.name) return 1; return 0; }) if (apps.length === 0) { appmenu[/*LANG*/'No app has settings'] = () => { }; } apps.forEach(function (app) { appmenu[app.name] = () => { showAppSettings(app) }; }) return appmenu; } function showAppSettings(app) { const back = () => popMenu(appSettingsMenu()); const showError = msg => { E.showMessage(`${app.name}:\n${msg}!\n\nBTN1 to go back`); setWatch(back, BTN1, { repeat: false }); } let appSettings = storage.read(app.id+'.settings.js'); try { appSettings = eval(appSettings); } catch (e) { console.log(`${app.name} settings error:`, e); return showError(/*LANG*/'Error in settings'); } if (typeof appSettings !== "function") { return showError(/*LANG*/'Invalid settings'); } try { // pass appSettingsMenu as "back" argument pushMenu(); appSettings(back); } catch (e) { console.log(`${app.name} settings error:`, e); return showError(/*LANG*/'Error in settings'); } } function showTouchscreenCalibration() { Bangle.setUI(); require('widget_utils').hide(); // disable touchscreen calibration (passed coords right through) Bangle.setOptions({touchX1: 0, touchY1: 0, touchX2: g.getWidth(), touchY2: g.getHeight() }); var P = 32; var corners = [ [P,P], [g.getWidth()-P,P], [g.getWidth()-P,g.getHeight()-P], [P,g.getHeight()-P], ]; var currentCorner = 0; var currentTry = 0; var pt = { x1 : 0, y1 : 0, x2 : 0, y2 : 0 }; function showTapSpot() { var spot = corners[currentCorner]; g.clear(1); g.drawLine(spot[0]-32,spot[1],spot[0]+32,spot[1]); g.drawLine(spot[0],spot[1]-32,spot[0],spot[1]+32); g.drawCircle(spot[0],spot[1], 16); var tapsLeft = (2-currentTry)*4+(4-currentCorner); g.setFont("6x8:2").setFontAlign(0,0).drawString(tapsLeft+/*LANG*/" taps\nto go", g.getWidth()/2, g.getHeight()/2); } function calcCalibration() { g.clear(1); // we should now have 6 of each tap in 'pt' pt.x1 /= 6; pt.y1 /= 6; pt.x2 /= 6; pt.y2 /= 6; // work out final values var calib = { x1 : Math.round(pt.x1 - (pt.x2-pt.x1)*P/(g.getWidth()-P*2)), y1 : Math.round(pt.y1 - (pt.y2-pt.y1)*P/(g.getHeight()-P*2)), x2 : Math.round(pt.x2 + (pt.x2-pt.x1)*P/(g.getWidth()-P*2)), y2 : Math.round(pt.y2 + (pt.y2-pt.y1)*P/(g.getHeight()-P*2)) }; var dx = calib.x2-calib.x1; var dy = calib.y2-calib.y1; if(dx<100 || dx>280 || dy<100 || dy>280) { g.setFont("6x8:2").setFontAlign(0,0).drawString(/*LANG*/"Out of Range.\nPlease\ntry again", g.getWidth()/2, g.getHeight()/2); } else { Bangle.setOptions({ touchX1: calib.x1, touchY1: calib.y1, touchX2: calib.x2, touchY2: calib.y2 }); var s = storage.readJSON("setting.json",1)||{}; s.touch = calib; storage.writeJSON("setting.json",s); g.setFont("6x8:2").setFontAlign(0,0).drawString(/*LANG*/"Calibrated!", g.getWidth()/2, g.getHeight()/2); } // now load the menu again setTimeout(() => restoreMenu(LCDMenu()), 500); } function touchHandler(_,e) { E.stopEventPropagation&&E.stopEventPropagation(); var spot = corners[currentCorner]; // store averages if (spot[0]*2 < g.getWidth()) pt.x1 += e.x; else pt.x2 += e.x; if (spot[1]*2 < g.getHeight()) pt.y1 += e.y; else pt.y2 += e.y; // go to next corner currentCorner++; if (currentCorner>=corners.length) { currentCorner = 0; currentTry++; if (currentTry==3) { Bangle.removeListener('touch', touchHandler); return calcCalibration(); } } showTapSpot(); } Bangle.prependListener?Bangle.prependListener('touch',touchHandler):Bangle.on('touch',touchHandler); showTapSpot(); } pushMenu(mainMenu()); }