Merge branch 'master' of https://github.com/ihewitt/BangleApps
106
apps.json
|
|
@ -3,7 +3,7 @@
|
|||
"id": "fwupdate",
|
||||
"name": "Firmware Update",
|
||||
"version": "0.02",
|
||||
"description": "Uploads new Espruino firmwares to Bangle.js 2",
|
||||
"description": "[BETA] Uploads new Espruino firmwares to Bangle.js 2. For now, please use the instructions under https://www.espruino.com/Bangle.js2#firmware-updates",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
"tags": "tools,system",
|
||||
|
|
@ -11,12 +11,12 @@
|
|||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": [],
|
||||
"sortorder": -20
|
||||
"sortorder": 20
|
||||
},
|
||||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.38",
|
||||
"version": "0.39",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
@ -77,7 +77,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.11",
|
||||
"version": "0.13",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
@ -92,14 +92,15 @@
|
|||
{"name":"messages","url":"lib.js"}
|
||||
],
|
||||
"data": [{"name":"messages.json"},{"name":"messages.settings.json"}],
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-notify.gif"}],
|
||||
"sortorder": -9
|
||||
},
|
||||
{
|
||||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.04",
|
||||
"description": "Display notifications/music/etc from Gadgetbridge on Android. This replaces the old Gadgetbridge widget.",
|
||||
"version": "0.05",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications",
|
||||
"dependencies": {"messages":"app"},
|
||||
|
|
@ -115,7 +116,7 @@
|
|||
{
|
||||
"id": "ios",
|
||||
"name": "iOS Integration",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Display notifications/music/etc from iOS devices",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,ios,apple,messages,notifications",
|
||||
|
|
@ -166,7 +167,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.36",
|
||||
"version": "0.37",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
@ -217,7 +218,7 @@
|
|||
{
|
||||
"id": "locale",
|
||||
"name": "Languages",
|
||||
"version": "0.13",
|
||||
"version": "0.14",
|
||||
"description": "Translations for different countries",
|
||||
"icon": "locale.png",
|
||||
"type": "locale",
|
||||
|
|
@ -303,7 +304,7 @@
|
|||
"id": "gbridge",
|
||||
"name": "Gadgetbridge",
|
||||
"version": "0.25",
|
||||
"description": "(NOT RECOMMENDED) Handles Gadgetbridge notifications from Android. This is now replaced by the 'Android' app.",
|
||||
"description": "(NOT RECOMMENDED) Displays Gadgetbridge notifications from Android. Please use the 'Android' Bangle.js app instead.",
|
||||
"icon": "app.png",
|
||||
"type": "widget",
|
||||
"tags": "tool,system,android,widget",
|
||||
|
|
@ -1698,14 +1699,15 @@
|
|||
"id": "rtorch",
|
||||
"name": "Red Torch",
|
||||
"shortName": "RedTorch",
|
||||
"version": "0.01",
|
||||
"description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets",
|
||||
"version": "0.02",
|
||||
"description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or on Bangle 1 press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,torch",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"rtorch.app.js","url":"app.js"},
|
||||
{"name":"rtorch.wid.js","url":"widget.js"},
|
||||
{"name":"rtorch.wid.js","url":"widget.js", "supports": ["BANGLEJS"]},
|
||||
{"name":"rtorch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
|
|
@ -2167,14 +2169,15 @@
|
|||
{ "id": "snek",
|
||||
"name": "The snek game",
|
||||
"shortName":"Snek",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "A snek game where you control a snek to eat all the apples!",
|
||||
"icon": "snek-icon.js",
|
||||
"screenshots": [{"url":"screenshot_snek.png"}],
|
||||
"icon": "snek.png",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"tags": "game,fun",
|
||||
"storage": [
|
||||
{"name":"snek.app.js","url":"snek.js"},
|
||||
{"name":"snek.img","url":"snek-icon.js","evaluate":true}
|
||||
{"name":"snek.img","url":"snek.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -3294,7 +3297,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.05",
|
||||
"version": "0.07",
|
||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
|
|
@ -3305,8 +3308,11 @@
|
|||
"storage": [
|
||||
{"name":"dtlaunch.app.js","url":"app-b1.js", "supports": ["BANGLEJS"]},
|
||||
{"name":"dtlaunch.app.js","url":"app-b2.js", "supports": ["BANGLEJS2"]},
|
||||
{"name":"dtlaunch.settings.js","url":"settings-b1.js", "supports": ["BANGLEJS"]},
|
||||
{"name":"dtlaunch.settings.js","url":"settings-b2.js", "supports": ["BANGLEJS2"]},
|
||||
{"name":"dtlaunch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"dtlaunch.json"}]
|
||||
},
|
||||
{
|
||||
"id": "HRV",
|
||||
|
|
@ -4008,11 +4014,11 @@
|
|||
{
|
||||
"id": "thermom",
|
||||
"name": "Thermometer",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Displays the current temperature in degree Celsius, updated every 20 seconds",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"thermom.app.js","url":"app.js"},
|
||||
|
|
@ -4206,7 +4212,7 @@
|
|||
"version": "0.08",
|
||||
"description": "A Configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
|
||||
"icon": "pastel.png",
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"dependencies": {"mylocation":"app", "widpedom":"app"},
|
||||
"screenshots": [{"url":"screenshot_pastel.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
|
@ -4644,7 +4650,7 @@
|
|||
"id": "sensible",
|
||||
"name": "SensiBLE",
|
||||
"shortName": "SensiBLE",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Collect, display and advertise real-time sensor data.",
|
||||
"icon": "sensible.png",
|
||||
"screenshots": [
|
||||
|
|
@ -4734,7 +4740,7 @@
|
|||
{ "id": "pooqroman",
|
||||
"name": "pooq Roman watch face",
|
||||
"shortName":"pooq Roman",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "A classic watch face with a certain dynamicity. Most amusing in 24h mode. Slide up to show more hands, down for less(!). By design does not support standard widgets, sorry!",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
|
|
@ -4870,7 +4876,7 @@
|
|||
"id": "rebble",
|
||||
"name": "Rebble Clock",
|
||||
"shortName": "Rebble",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
|
||||
"readme": "README.md",
|
||||
"icon": "rebble.png",
|
||||
|
|
@ -4932,6 +4938,25 @@
|
|||
{"name":"awairmonitor.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "pooqround",
|
||||
"name": "pooq Round watch face",
|
||||
"shortName":"pooq Round",
|
||||
"version":"0.00",
|
||||
"description": "A 24 hour analogue watchface with high legibility and a novel style.",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"pooqround.app.js","url":"app.js"},
|
||||
{"name":"pooqround.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"pooqround.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "coretemp",
|
||||
"name": "Core Temp Display",
|
||||
|
|
@ -4947,5 +4972,36 @@
|
|||
{"name":"coretemp.app.js","url":"coretemp.js"},
|
||||
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "showimg",
|
||||
"name": "simple image viewer",
|
||||
"shortName":"showImage",
|
||||
"version":"0.1",
|
||||
"description": "Displays the image file in showimage.user.img. Returns to watch face after 60s or button push. I use it to display my vaccination certificate.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"showimg.app.js","url":"app.js"},
|
||||
{"name":"showimg.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "lapcounter",
|
||||
"name": "Lap Counter",
|
||||
"version": "0.01",
|
||||
"description": "Click button to count laps. Shows count and total time snapshot (like a stopwatch, but laid back).",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "tool,outdoors",
|
||||
"readme":"README.md",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"lapcounter.app.js","url":"app.js"},
|
||||
{"name":"lapcounter.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ function showAlarm(alarm) {
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
E.showPrompt(msg,{
|
||||
title:alarm.timer ? "TIMER!" : "ALARM!",
|
||||
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
|
||||
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
|
||||
buttons : {/*LANG*/"Sleep":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
|
||||
}).then(function(sleep) {
|
||||
buzzCount = 0;
|
||||
if (sleep) {
|
||||
|
|
|
|||
|
|
@ -33,16 +33,16 @@ function getCurrentHr() {
|
|||
function showMainMenu() {
|
||||
const menu = {
|
||||
'': { 'title': 'Alarm/Timer' },
|
||||
'< Back' : ()=>{load();},
|
||||
'New Alarm': ()=>editAlarm(-1),
|
||||
'New Timer': ()=>editTimer(-1)
|
||||
/*LANG*/'< Back' : ()=>{load();},
|
||||
/*LANG*/'New Alarm': ()=>editAlarm(-1),
|
||||
/*LANG*/'New Timer': ()=>editTimer(-1)
|
||||
};
|
||||
alarms.forEach((alarm,idx)=>{
|
||||
if (alarm.timer) {
|
||||
txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer);
|
||||
txt = /*LANG*/"TIMER "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatMins(alarm.timer);
|
||||
} else {
|
||||
txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
txt = /*LANG*/"ALARM "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += /*LANG*/" (repeat)";
|
||||
}
|
||||
menu[txt] = function() {
|
||||
if (alarm.timer) editTimer(idx);
|
||||
|
|
@ -70,27 +70,27 @@ function editAlarm(alarmIndex) {
|
|||
as = a.as;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Alarm' },
|
||||
'< Back' : showMainMenu,
|
||||
'Hours': {
|
||||
'': { 'title': /*LANG*/'Alarm' },
|
||||
/*LANG*/'< Back' : showMainMenu,
|
||||
/*LANG*/'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Minutes': {
|
||||
/*LANG*/'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Enabled': {
|
||||
/*LANG*/'Enabled': {
|
||||
value: en,
|
||||
format: v=>v?"On":"Off",
|
||||
onchange: v=>en=v
|
||||
},
|
||||
'Repeat': {
|
||||
/*LANG*/'Repeat': {
|
||||
value: en,
|
||||
format: v=>v?"Yes":"No",
|
||||
onchange: v=>repeat=v
|
||||
},
|
||||
'Auto snooze': {
|
||||
/*LANG*/'Auto snooze': {
|
||||
value: as,
|
||||
format: v=>v?"Yes":"No",
|
||||
onchange: v=>as=v
|
||||
|
|
@ -108,14 +108,14 @@ function editAlarm(alarmIndex) {
|
|||
last : day, rp : repeat, as: as
|
||||
};
|
||||
}
|
||||
menu["> Save"] = function() {
|
||||
menu[/*LANG*/"> Save"] = function() {
|
||||
if (newAlarm) alarms.push(getAlarm());
|
||||
else alarms[alarmIndex] = getAlarm();
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
if (!newAlarm) {
|
||||
menu["> Delete"] = function() {
|
||||
menu[/*LANG*/"> Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
|
|
@ -136,18 +136,18 @@ function editTimer(alarmIndex) {
|
|||
en = a.on;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Timer' },
|
||||
'Hours': {
|
||||
'': { 'title': /*LANG*/'Timer' },
|
||||
/*LANG*/'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Minutes': {
|
||||
/*LANG*/'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Enabled': {
|
||||
/*LANG*/'Enabled': {
|
||||
value: en,
|
||||
format: v=>v?"On":"Off",
|
||||
format: v=>v?/*LANG*/"On":/*LANG*/"Off",
|
||||
onchange: v=>en=v
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
active = active.sort((a,b)=>(a.hr-b.hr)+(a.last-b.last)*24);
|
||||
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||
if (!require('Storage').read("alarm.js")) {
|
||||
console.log("No alarm app!");
|
||||
console.log(/*LANG*/"No alarm app!");
|
||||
require('Storage').write('alarm.json',"[]");
|
||||
} else {
|
||||
var t = 3600000*(active[0].hr-hr);
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
Fix music control
|
||||
0.03: Handling of message actions (ok/clear)
|
||||
0.04: Android icon now goes to settings page with 'find phone'
|
||||
0.05: Fix handling of message actions
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
// Message response
|
||||
Bangle.messageResponse = (msg,response) => {
|
||||
if (msg.id=="call") return gbSend({ t: "call", n:response?"ACCEPT":"REJECT" });
|
||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS" });
|
||||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
// error/warn here?
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ Displays the level of CO2, VOC, PM 2.5, Humidity and Temperature, from your Awai
|
|||
|
||||

|
||||
|
||||

|
||||
|
||||
## Creator
|
||||
[@alainsaas](https://github.com/alainsaas)
|
||||
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 146 KiB |
|
|
@ -42,3 +42,4 @@
|
|||
0.36: Add comments to .boot0 to make debugging a bit easier
|
||||
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
|
||||
0.38: Option to log to file if settings.log==2
|
||||
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
|
|||
// Apply any settings-specific stuff
|
||||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
||||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
// Pre-2v10 firmwares without a theme/setUI
|
||||
delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
|
|
|
|||
|
|
@ -3,3 +3,5 @@
|
|||
0.03: cycle thru pages
|
||||
0.04: reset to clock after 2 mins of inactivity
|
||||
0.05: add Bangle 2 version
|
||||
0.06: Adds settings page (hide clocks or launchers)
|
||||
0.06: Adds setting for directly launching app on touch for Bangle 2
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
*
|
||||
*/
|
||||
|
||||
var settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
|
||||
function wdog(handle,timeout){
|
||||
if(handle !== undefined){
|
||||
wdog.handle = handle;
|
||||
|
|
@ -17,7 +22,13 @@ function wdog(handle,timeout){
|
|||
wdog(load,120000)
|
||||
|
||||
var s = require("Storage");
|
||||
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
|
||||
var apps = s.list(/\.info$/).map(app=>{
|
||||
var a=s.readJSON(app,1);
|
||||
return a && {
|
||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||
};}).filter(
|
||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
|
|
|
|||
|
|
@ -2,8 +2,20 @@
|
|||
*
|
||||
*/
|
||||
|
||||
var settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
|
||||
var s = require("Storage");
|
||||
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
|
||||
var apps = s.list(/\.info$/).map(app=>{
|
||||
var a=s.readJSON(app,1);
|
||||
return a && {
|
||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||
};}).filter(
|
||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||
|
||||
apps.sort((a,b)=>{
|
||||
var n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
|
|
@ -28,7 +40,7 @@ const YOFF = 30;
|
|||
function draw_icon(p,n,selected) {
|
||||
var x = (n%2)*72+XOFF;
|
||||
var y = n>1?72+YOFF:YOFF;
|
||||
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+10,y+2,x+60,y+52);
|
||||
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
|
||||
g.clearRect(x+12,y+4,x+59,y+51);
|
||||
g.setColor(g.theme.fg);
|
||||
try{g.drawImage(apps[p*4+n].icon,x+12,y+4);} catch(e){}
|
||||
|
|
@ -52,7 +64,7 @@ function drawPage(p){
|
|||
}
|
||||
for (var i=0;i<4;i++) {
|
||||
if (!apps[p*4+i]) return i;
|
||||
draw_icon(p,i,selected==i);
|
||||
draw_icon(p,i,selected==i && !settings.direct);
|
||||
}
|
||||
g.flip();
|
||||
}
|
||||
|
|
@ -81,9 +93,9 @@ Bangle.on("touch",(_,p)=>{
|
|||
for (i=0;i<4;i++){
|
||||
if((page*4+i)<Napps){
|
||||
if (isTouched(p,i)) {
|
||||
draw_icon(page,i,true);
|
||||
if (selected>=0) {
|
||||
if (selected!=i){
|
||||
draw_icon(page,i,true && !settings.direct);
|
||||
if (selected>=0 || settings.direct) {
|
||||
if (selected!=i && !settings.direct){
|
||||
draw_icon(page,selected,false);
|
||||
} else {
|
||||
load(apps[page*4+i].src);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
(function(back) {
|
||||
var FILE = "dtlaunch.json";
|
||||
|
||||
var settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
"" : { "title" : "Desktop launcher" },
|
||||
"< Back" : () => back(),
|
||||
'Show clocks': {
|
||||
value: settings.showClocks,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.showClocks = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Show launchers': {
|
||||
value: settings.showLaunchers,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.showLaunchers = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
(function(back) {
|
||||
var FILE = "dtlaunch.json";
|
||||
|
||||
var settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
"" : { "title" : "Desktop launcher" },
|
||||
"< Back" : () => back(),
|
||||
'Show clocks': {
|
||||
value: settings.showClocks,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.showClocks = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Show launchers': {
|
||||
value: settings.showLaunchers,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.showLaunchers = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Direct launch': {
|
||||
value: settings.direct,
|
||||
format: v => v?"On":"Off",
|
||||
onchange: v => {
|
||||
settings.direct = v;
|
||||
writeSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<p><b>THIS IS CURRENTLY BETA - PLEASE USE THE NORMAL FIRMWARE UPDATE
|
||||
INSTRUCTIONS FOR <a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">BANGLE.JS</a> 1 AND <a href="https://www.espruino.com/Bangle.js2#firmware-updates" target="_blank">BANGLE.JS 2</a></b>. For usage on Bangle.js 2 you'll likely need to have an updated bootloader.</p>
|
||||
<div id="fw-unknown">
|
||||
<p><b>Firmware updates using the App Loader are only possible on
|
||||
Bangle.js 2. For firmware updates on Bangle.js 1 please
|
||||
|
|
|
|||
|
|
@ -4,4 +4,7 @@
|
|||
0.04: Added common bundleId's
|
||||
0.05: Added more bundleId's (app-id's which can be used to
|
||||
determine a friendly app name in the notifications)
|
||||
0.06: Fix (not) popupping up old messages
|
||||
0.06: Fix (not) popupping up old messages
|
||||
0.07: Added more details from music (instead of Undefined)
|
||||
Added more app identifiers
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
# iOS integration app
|
||||
|
||||
This is the iOS integration app for Bangle.js. This app allows you to receive
|
||||
notifications from your iPhone. The Apple Notification Center Service (ANCS)
|
||||
sends all the messages to your watch.
|
||||
|
||||
You can allow this if you connect your Bangle to your iPhone. It will be
|
||||
prompted for immediatly after you connect the Bangle to the iPhone.
|
||||
|
||||
### Connecting your Bangle(2).js to your iPhone
|
||||
The Bangle watches are Bluetooth Low Energy (BLE) devices. Sometimes they
|
||||
will not be seen/detected by the Bluetooth scanner in your iPhone settings
|
||||
menu.
|
||||
|
||||
To resolve this, you can download numerous apps who can actually scan
|
||||
for BLE devices. There are great ones out there, free and paid.
|
||||
|
||||
We really like WebBLE, which we also recommend to load apps on your
|
||||
watch with your iOS device, as Safari does not support WebBluetooth
|
||||
for now. It's just a few bucks/pounds/euro's.
|
||||
|
||||
If you like to try a free app first, you can always use NRF Toolbox or
|
||||
Bluetooth BLE Device Finder to find and connect your Bangle.
|
||||
|
||||
## Requests
|
||||
|
||||
Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=ios%20app
|
||||
|
||||
## Creator
|
||||
|
||||
Gordon Williams
|
||||
|
|
@ -65,14 +65,16 @@ E.on('notify',msg=>{
|
|||
"com.apple.facetime": "FaceTime",
|
||||
"com.apple.mobilecal": "Calendar",
|
||||
"com.apple.mobilemail": "Mail",
|
||||
"com.apple.mobilephone": "Phone",
|
||||
"com.apple.MobileSMS": "SMS Message",
|
||||
"com.apple.Passbook": "iOS Wallet",
|
||||
"com.apple.podcasts": "Podcasts",
|
||||
"com.apple.reminders": "Reminders",
|
||||
"com.apple.shortcuts": "Shortcuts",
|
||||
"com.atebits.Tweetie2": "Twitter",
|
||||
"com.burbn.instagram" : "Instagram",
|
||||
"com.facebook.Facebook": "Facebook",
|
||||
"com.facebook.Messenger": "FB Messenger",
|
||||
"com.facebook.Messenger": "Messenger",
|
||||
"com.google.Chromecast" : "Google Home",
|
||||
"com.google.Gmail" : "GMail",
|
||||
"com.google.hangouts" : "Hangouts",
|
||||
|
|
@ -81,22 +83,26 @@ E.on('notify',msg=>{
|
|||
"com.ifttt.ifttt" : "IFTTT",
|
||||
"com.jumbo.app" : "Jumbo",
|
||||
"com.linkedin.LinkedIn" : "LinkedIn",
|
||||
"com.microsoft.Office.Outlook" : "Outlook Mail",
|
||||
"com.nestlabs.jasper.release" : "Nest",
|
||||
"com.netflix.Netflix" : "Netflix",
|
||||
"com.reddit.Reddit" : "Reddit",
|
||||
"com.skype.skype": "Skype",
|
||||
"com.skype.SkypeForiPad": "Skype",
|
||||
"com.spotify.client": "Spotify",
|
||||
"com.strava.stravaride": "Strava",
|
||||
"com.tinyspeck.chatlyio": "Slack",
|
||||
"com.toyopagroup.picaboo": "Snapchat",
|
||||
"com.ubercab.UberClient": "Uber",
|
||||
"com.ubercab.UberEats": "UberEats",
|
||||
"com.vilcsak.bitcoin2": "Coinbase",
|
||||
"com.wordfeud.free": "WordFeud",
|
||||
"com.zhiliaoapp.musically": "TikTok",
|
||||
"net.whatsapp.WhatsApp": "WhatsApp",
|
||||
"nl.ah.Appie": "Albert Heijn",
|
||||
"nl.postnl.TrackNTrace": "PostNL",
|
||||
"ph.telegra.Telegraph": "Telegram",
|
||||
"tv.twitch": "Twitch",
|
||||
|
||||
// could also use NRF.ancsGetAppInfo(msg.appId) here
|
||||
};
|
||||
|
|
@ -104,7 +110,7 @@ E.on('notify',msg=>{
|
|||
'2019':"'"
|
||||
};
|
||||
var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16));
|
||||
if (appNames[msg.appId]) msg.a
|
||||
//if (appNames[msg.appId]) msg.a
|
||||
require("messages").pushMessage({
|
||||
t : msg.event,
|
||||
id : msg.uid,
|
||||
|
|
@ -122,9 +128,10 @@ E.on('AMS',a=>{
|
|||
function push(m) {
|
||||
var msg = { t : "modify", id : "music", title:"Music" };
|
||||
if (a.id=="artist") msg.artist = m;
|
||||
else if (a.id=="album") msg.artist = m;
|
||||
else if (a.id=="title") msg.tracl = m;
|
||||
else return; // duration? need to reformat
|
||||
else if (a.id=="album") msg.album = m;
|
||||
else if (a.id=="title") msg.track = m;
|
||||
else if (a.id=="duration") msg.dur = m;
|
||||
else return;
|
||||
require("messages").pushMessage(msg);
|
||||
}
|
||||
if (a.truncated) NRF.amsGetMusicInfo(a.id).then(push)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: first release
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Lap Counter
|
||||
|
||||
Click button to count laps (e.g. in a swimming pool).
|
||||
Also shows total duration snapshot (like a stopwatch, but laid back).
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
* Click BTN1 to start counting. Counter becomes `0`, duration becomes `00:00.0`
|
||||
* Each time you click BTN1, counter is incremented, and you see duration between first and last clicks.
|
||||
|
||||
## Features
|
||||
|
||||
Disables LCD timeout (so that you can be _sure_ what BTN1 would do).
|
||||
|
||||
## Creator
|
||||
|
||||
[Nimrod Kerrett](https://zzzen.com)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBiIA/AH4A/AAkQgEBAREAC6oABdZQXkI6wuKC5iPUFxoXIOpoX/C6QFCC6IsCC6ZEDC/4XcPooXOFgoXQIgwX/C7IUFC5wsIC5ouCC6hcJC5h1DF9YwBChCPOAH4A/AH4Ap"))
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
const w = g.getWidth();
|
||||
const h = g.getHeight();
|
||||
const wid_h = 24;
|
||||
let tStart;
|
||||
let tNow;
|
||||
let counter=-1;
|
||||
|
||||
const icon = require("heatshrink").decompress(atob("mEwwkBiIA/AH4A/AAkQgEBAREAC6oABdZQXkI6wuKC5iPUFxoXIOpoX/C6QFCC6IsCC6ZEDC/4XcPooXOFgoXQIgwX/C7IUFC5wsIC5ouCC6hcJC5h1DF9YwBChCPOAH4A/AH4Ap"));
|
||||
|
||||
function timeToText(t) { // Courtesy of stopwatch app
|
||||
let hrs = Math.floor(t/3600000);
|
||||
let mins = Math.floor(t/60000)%60;
|
||||
let secs = Math.floor(t/1000)%60;
|
||||
let tnth = Math.floor(t/100)%10;
|
||||
let text;
|
||||
|
||||
if (hrs === 0)
|
||||
text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
|
||||
else
|
||||
text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
|
||||
//log_debug(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
function doCounter() {
|
||||
if (counter<0) {
|
||||
tStart = Date.now();
|
||||
tNow = tStart;
|
||||
} else {
|
||||
tNow = Date.now();
|
||||
}
|
||||
counter++;
|
||||
let dT = tNow-tStart;
|
||||
|
||||
g.clearRect(0,wid_h,w,h-wid_h);
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector",72);
|
||||
g.drawString(counter,w/2,h/2);
|
||||
g.setFont("Vector",24);
|
||||
g.drawString(timeToText(dT),w/2,h/2+50);
|
||||
}
|
||||
|
||||
setWatch(doCounter, BTN1, true);
|
||||
|
||||
g.clear(true);
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Bangle.setLCDTimeout(0);
|
||||
g.drawImage(icon,w/2-24,h/2-24);
|
||||
g.setFontAlign(0,0);
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12");
|
||||
g.drawString("Click button to count.", w/2, h/2+22);
|
||||
|
After Width: | Height: | Size: 749 B |
|
After Width: | Height: | Size: 6.4 KiB |
|
|
@ -13,3 +13,4 @@
|
|||
0.11: Added translations for nl_NL and changes one formatting
|
||||
0.12: Fixed nl_NL formatting, because the full months won't fit on the Bangle.js2's screen
|
||||
0.13: Now use shorter de_DE date format to more closely match other languages for size
|
||||
0.14: Added some first translations for Messages in nl_NL
|
||||
|
|
|
|||
|
|
@ -37,20 +37,30 @@ const codePages = {
|
|||
/* When it's not in the codepage, try and use
|
||||
these conversions */
|
||||
const charFallbacks = {
|
||||
"ą":"a",
|
||||
"ā":"a",
|
||||
"č":"c",
|
||||
"ř":"r",
|
||||
"ő":"o",
|
||||
"ć":"c",
|
||||
"ě":"e",
|
||||
"ę":"e",
|
||||
"ą":"a",
|
||||
"ē":"e",
|
||||
"ģ":"g",
|
||||
"i":"ī",
|
||||
"ķ":"k",
|
||||
"ļ":"l",
|
||||
"ł":"l",
|
||||
"ń":"n",
|
||||
"ņ":"n",
|
||||
"ő":"o",
|
||||
"ó":"o",
|
||||
"ř":"r",
|
||||
"ś":"s",
|
||||
"š":"s",
|
||||
"ū":"u",
|
||||
"ż":"z",
|
||||
"ź":"z",
|
||||
"ń":"n",
|
||||
"ł":"l",
|
||||
"ś":"s",
|
||||
"ć":"c",
|
||||
};
|
||||
"ž":"z",
|
||||
};
|
||||
|
||||
/*
|
||||
timePattern / datePattern:
|
||||
|
|
@ -198,7 +208,8 @@ var locales = {
|
|||
day: "zondag,maandag,dinsdag,woensdag,donderdag,vrijdag,zaterdag",
|
||||
abmonth: "jan,feb,mrt,apr,mei,jun,jul,aug,sep,okt,nov,dec",
|
||||
month: "januari,februari,maart,april,mei,juni,juli,augustus,september,oktober,november,december",
|
||||
trans: { yes: "ja", Yes: "Ja", no: "nee", No: "Nee", ok: "ok", on: "aan", off: "uit", "< Back": "< Terug" }
|
||||
trans: { yes: "ja", Yes: "Ja", no: "nee", No: "Nee", ok: "ok", on: "aan", off: "uit",
|
||||
"< Back": "< Terug", "Delete": "Verwijderen", "Mark Unread": "Markeer als ongelezen" }
|
||||
},
|
||||
"en_NL": { // English date units with Dutch number, currency and navigation units.
|
||||
lang: "en_NL",
|
||||
|
|
@ -630,6 +641,24 @@ var locales = {
|
|||
day: "Niedziela,Poniedziałek,Wtorek,Środa,Czwartek,Piątek,Sobota",
|
||||
trans: { yes: "tak", Yes: "Tak", no: "nie", No: "Nie", ok: "ok", on: "on", off: "off", "< Back": "< Wstecz" }
|
||||
},
|
||||
"lv_LV": { // Using charfallbacks
|
||||
lang: "lv_LV",
|
||||
decimal_point: ",",
|
||||
thousands_sep: " ",
|
||||
currency_symbol: "€",
|
||||
int_curr_symbol: "EUR",
|
||||
speed: "kmh",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2020 // 01.03.20
|
||||
abmonth: "Jan,Feb,Mar,Apr,Mai,Jūn,Jūl,Aug,Sep,Okt,Nov,Dec",
|
||||
month: "Janvāris,Februāris,Marts,Aprīlis,Maijs,Jūnijs,Jūlijs,Augusts,Septemberis,Oktobris,Novembris,Decembris",
|
||||
abday: "Pr,Ot,Tr,Ce,Pk,Se,Sv",
|
||||
day: "Pirmdiena,Otrdiena,Trešdiena,Ceturtdiena,Piektdiena,Sestdiena,Svētdiena",
|
||||
trans: { yes: "jā", Yes: "Jā", no: "nē", No: "Nē", ok: "labi", on: "Ieslēgt", off: "Izslēgt", "< Back": "< Atpakaļ" }
|
||||
},
|
||||
/*,
|
||||
"he_IL": { // This won't work until we get a font - see https://github.com/espruino/BangleApps/issues/399
|
||||
codePage : "ISO8859-8",
|
||||
|
|
|
|||
|
|
@ -13,4 +13,10 @@
|
|||
0.09: Message now disappears after 60s if no action taken and clock loads (fix 922)
|
||||
Fix phone icon (#1014)
|
||||
0.10: Respect the 'new' attribute if it was set from iOS integrations
|
||||
0.11: Open app when touching the widget (Bangle.js 2 only)
|
||||
0.11: Open app when touching the widget (Bangle.js 2 only)
|
||||
0.12: Extra app-specific notification icons
|
||||
New animated notifcationicon (instead of large blinking 'MESSAGES')
|
||||
Added screenshots
|
||||
0.13: Add /*LANG*/ comments for internationalisation
|
||||
Add 'Delete All' option to message options
|
||||
Now update correctly when 'require("messages").clearAll()' is called
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
# Messages app
|
||||
|
||||
**THIS APP IS CURRENTLY BETA**
|
||||
|
||||
This app handles the display of messages and message notifications. It stores
|
||||
a list of currently received messages and allows them to be listed, viewed,
|
||||
and responded to.
|
||||
|
|
@ -17,7 +15,17 @@ and `Messages`:
|
|||
* `Repeat` - How often should buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds
|
||||
* `Unread Timer` - when a new message is received we go into the Messages app.
|
||||
If there is no user input for this amount of time then the app will exit and return
|
||||
to the clock where `MESSAGES` will be shown in the Widget bar.
|
||||
to the clock where a ringing bell will be shown in the Widget bar.
|
||||
|
||||
## Images
|
||||
_1. Screenshot of a notification_
|
||||
|
||||

|
||||
|
||||
_2. What the notify icon looks like (it's touchable on Bangle.js2!)_
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## Requests
|
||||
|
|
@ -27,3 +35,11 @@ Please file any issues on https://github.com/espruino/BangleApps/issues/new?titl
|
|||
## Creator
|
||||
|
||||
Gordon Williams
|
||||
|
||||
## Contributors
|
||||
|
||||
[Jeroen Peters](https://github.com/jeroenpeters1986)
|
||||
|
||||
## Attributions
|
||||
|
||||
Icons used in this app are from https://icons8.com
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@ var MESSAGES = require("Storage").readJSON("messages.json",1)||[];
|
|||
if (!Array.isArray(MESSAGES)) MESSAGES=[];
|
||||
var onMessagesModified = function(msg) {
|
||||
// TODO: if new, show this new one
|
||||
if (msg.new) {
|
||||
if (msg && msg.new) {
|
||||
if (WIDGETS["messages"]) WIDGETS["messages"].buzz();
|
||||
else Bangle.buzz();
|
||||
}
|
||||
showMessage(msg.id);
|
||||
showMessage(msg&&msg.id);
|
||||
};
|
||||
function saveMessages() {
|
||||
require("Storage").writeJSON("messages.json",MESSAGES)
|
||||
|
|
@ -65,6 +65,12 @@ function saveMessages() {
|
|||
function getBackImage() {
|
||||
return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA==");
|
||||
}
|
||||
function getNotificationImage() {
|
||||
return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A==");
|
||||
}
|
||||
function getFBIcon() {
|
||||
return atob("GBiBAAAAAAAAAAAYAAD/AAP/wAf/4A/48A/g8B/g+B/j+B/n+D/n/D8A/B8A+B+B+B/n+A/n8A/n8Afn4APnwADnAAAAAAAAAAAAAA==");
|
||||
}
|
||||
function getPosImage() {
|
||||
return atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA==");
|
||||
}
|
||||
|
|
@ -74,18 +80,28 @@ function getNegImage() {
|
|||
function getMessageImage(msg) {
|
||||
if (msg.img) return atob(msg.img);
|
||||
var s = (msg.src||"").toLowerCase();
|
||||
if (s=="calendar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA==");
|
||||
if (s=="facebook") return getFBIcon();
|
||||
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
|
||||
if (s=="instagram") return atob("GBiBAf////////////////wAP/n/n/P/z/f/b/eB7/c87/d+7/d+7/d+7/d+7/c87/eB7/f/7/P/z/n/n/wAP////////////////w==");
|
||||
if (s=="gmail") return getNotificationImage();
|
||||
if (s=="google home") return atob("GBiCAAAAAAAAAAAAAAAAAAAAAoAAAAAACqAAAAAAKqwAAAAAqroAAAACquqAAAAKq+qgAAAqr/qoAACqv/6qAAKq//+qgA6r///qsAqr///6sAqv///6sAqv///6sAqv///6sA6v///6sA6v///qsA6qqqqqsA6qqqqqsA6qqqqqsAP7///vwAAAAAAAAAAAAAAAAA==");
|
||||
if (s=="mail") return getNotificationImage();
|
||||
if (s=="messenger") return getFBIcon();
|
||||
if (s=="outlook mail") return getNotificationImage();
|
||||
if (s=="phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA=");
|
||||
if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA==");
|
||||
if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA=");
|
||||
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
|
||||
if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA==");
|
||||
if (s=="slack") return atob("GBiBAAAAAAAAAABAAAHvAAHvAADvAAAPAB/PMB/veD/veB/mcAAAABzH8B3v+B3v+B3n8AHgAAHuAAHvAAHvAADGAAAAAAAAAAAAAA==");
|
||||
if (s=="sms message") return getNotificationImage();
|
||||
if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA");
|
||||
if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA==");
|
||||
if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA==");
|
||||
if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql");
|
||||
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
|
||||
if (msg.id=="back") return getBackImage();
|
||||
return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A==");
|
||||
return getNotificationImage();
|
||||
}
|
||||
|
||||
|
||||
function showMapMessage(msg) {
|
||||
var m;
|
||||
var distance, street, target, eta;
|
||||
|
|
@ -127,7 +143,7 @@ function showMapMessage(msg) {
|
|||
function showMusicMessage(msg) {
|
||||
function fmtTime(s) {
|
||||
var m = Math.floor(s/60);
|
||||
s = (s%60).toString().padStart(2,0);
|
||||
s = (parseInt(s%60)).toString().padStart(2,0);
|
||||
return m+":"+s;
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +157,7 @@ function showMusicMessage(msg) {
|
|||
{type:"h", fillx:1, bgCol:colBg, c: [
|
||||
{ type:"btn", src:getBackImage, cb:back },
|
||||
{ type:"v", fillx:1, c: [
|
||||
{ type:"txt", font:fontLarge, label:msg.artist, pad:2 },
|
||||
{ type:"txt", font:fontMedium, label:msg.artist, pad:2 },
|
||||
{ type:"txt", font:fontMedium, label:msg.album, pad:2 }
|
||||
]}
|
||||
]},
|
||||
|
|
@ -158,24 +174,33 @@ function showMusicMessage(msg) {
|
|||
}
|
||||
|
||||
function showMessageSettings(msg) {
|
||||
E.showMenu({"":{"title":"Message"},
|
||||
E.showMenu({"":{"title":/*LANG*/"Message"},
|
||||
"< Back" : () => showMessage(msg.id),
|
||||
"Delete" : () => {
|
||||
/*LANG*/"Delete" : () => {
|
||||
MESSAGES = MESSAGES.filter(m=>m.id!=msg.id);
|
||||
saveMessages();
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0});
|
||||
},
|
||||
"Mark Unread" : () => {
|
||||
/*LANG*/"Mark Unread" : () => {
|
||||
msg.new = true;
|
||||
saveMessages();
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0});
|
||||
},
|
||||
/*LANG*/"Delete all messages" : () => {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", {title:/*LANG*/"Delete All Messages"}).then(isYes => {
|
||||
if (isYes) {
|
||||
MESSAGES = [];
|
||||
saveMessages();
|
||||
}
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0});
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(msgid) {
|
||||
var msg = MESSAGES.find(m=>m.id==msgid);
|
||||
if (!msg) return checkMessages(); // go home if no message found
|
||||
if (!msg) return checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0}); // go home if no message found
|
||||
if (msg.src=="Maps") {
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
return showMapMessage(msg);
|
||||
|
|
@ -212,7 +237,6 @@ function showMessage(msgid) {
|
|||
}
|
||||
if (msg.negative) {
|
||||
buttons.push({type:"btn", src:getNegImage(), cb:()=>{
|
||||
console.log("Response");
|
||||
msg.new = false; saveMessages();
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
Bangle.messageResponse(msg,false);
|
||||
|
|
@ -223,7 +247,7 @@ function showMessage(msgid) {
|
|||
var body = (lines.length>4) ? lines.slice(0,4).join("\n")+"..." : lines.join("\n");
|
||||
layout = new Layout({ type:"v", c: [
|
||||
{type:"h", fillx:1, bgCol:colBg, c: [
|
||||
{ type:"btn", src:getMessageImage(msg), cb:()=>{
|
||||
{ type:"btn", src:getMessageImage(msg), pad: 3, cb:()=>{
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
showMessageSettings(msg);
|
||||
}},
|
||||
|
|
@ -250,10 +274,10 @@ function checkMessages(options) {
|
|||
options=options||{};
|
||||
// If no messages, just show 'no messages' and return
|
||||
if (!MESSAGES.length) {
|
||||
if (!options.clockIfNoMsg) return E.showPrompt("No Messages",{
|
||||
title:"Messages",
|
||||
if (!options.clockIfNoMsg) return E.showPrompt(/*LANG*/"No Messages",{
|
||||
title:/*LANG*/"Messages",
|
||||
img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")),
|
||||
buttons : {"Ok":1}
|
||||
buttons : {/*LANG*/"Ok":1}
|
||||
}).then(() => { load() });
|
||||
return load();
|
||||
}
|
||||
|
|
@ -281,7 +305,7 @@ function checkMessages(options) {
|
|||
var x = r.x+2, title = msg.title, body = msg.body;
|
||||
var img = getMessageImage(msg);
|
||||
if (msg.id=="music") {
|
||||
title = msg.artist || "Music";
|
||||
title = msg.artist || /*LANG*/"Music";
|
||||
body = msg.track;
|
||||
}
|
||||
if (img) {
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -2,9 +2,8 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
|||
Bangle.removeListener('touch', this.touch);
|
||||
if (!this.width) return;
|
||||
var c = (Date.now()-this.t)/1000;
|
||||
g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff");
|
||||
g.clearRect(this.x,this.y,this.x+this.width,this.y+23);
|
||||
g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12);
|
||||
g.reset().clearRect(this.x,this.y,this.x+this.width,this.y+23);
|
||||
g.drawImage((c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y);
|
||||
//if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute
|
||||
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
|
||||
if (settings.repeat===undefined) settings.repeat = 4;
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: included deployment of pebble.settings.js in apps.json
|
||||
0.03: Changed time+calendar font to LECO1976Regular, changed to slanting boot
|
||||
0.04: Fix widget hiding code (fix #1046)
|
||||
0.05: Fix typo in settings - Purple
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
var color_options = ['Green','Orange','Cyan','Perple','Red','Blue'];
|
||||
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
|
||||
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
|
||||
|
||||
E.showMenu({
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Make internal menu time out + small fixes
|
||||
0.01: Initial check-in.
|
||||
0.02: Make internal menu time out + small fixes.
|
||||
0.03: Autolight feature.
|
||||
|
|
|
|||
|
|
@ -13,9 +13,12 @@ you can alter the number of hands on the display. When the watch is unlocked, sl
|
|||
There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky, in case you want
|
||||
the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails.
|
||||
|
||||
Although we genrally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face.
|
||||
Although we generally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face.
|
||||
You can also override the system 12/24 hour setting just for this face here, since it's, well, a rather different experience than with numeric displays.
|
||||
|
||||
By default, there is a backlight that comes on when you twist your wrist. This, of course, somewhat increases power draw and could be
|
||||
annoying in an intentionally dark environment, so there is an option to disable it.
|
||||
|
||||
One other thing: there's some integration with system timers and alarms; they will show as small pips at the appropriate places
|
||||
in the day around the display. When they come within an hour, the pips turn to crosses relating to the minute hand, and the minute
|
||||
hand turns itself on. When timers are mere seconds away, the display changes again and the second hand activates itself, so you
|
||||
|
|
|
|||
|
|
@ -138,6 +138,10 @@ class RomanOptions extends Options {
|
|||
onchange: x => this.calendric = x,
|
||||
format: x => ['none', 'day', 'date'][x]
|
||||
},
|
||||
'Auto-Illum.': {
|
||||
init: _ => this.autolight,
|
||||
onchange: x => this.autolight = x
|
||||
},
|
||||
Defaults: _ => {this.reset(); this.interact();}
|
||||
};
|
||||
}
|
||||
|
|
@ -164,6 +168,7 @@ RomanOptions.defaults = {
|
|||
alarmFg: '#f00',
|
||||
timerFg: '#0f0',
|
||||
activeFg: g.theme.fg2,
|
||||
autolight: true,
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -663,10 +668,10 @@ class Clock {
|
|||
this.options.on('done', () => this.start());
|
||||
|
||||
this.listeners = {
|
||||
lcdPower: on => on ? this.active() : this.inactive(),
|
||||
charging: () => {face.doIcons('charging'); this.active();},
|
||||
lock: () => {face.doIcons('locked'); this.active();},
|
||||
charging: _ => {face.doIcons('charging'); this.active();},
|
||||
lock: _ => {face.doIcons('locked'); this.active();},
|
||||
faceUp: up => {this.conservative = !up; this.active();},
|
||||
twist: _ => this.options.autolight && Bangle.setLCDPower(true),
|
||||
drag: e => {
|
||||
if (this.t0) {
|
||||
if (e.b) {
|
||||
|
|
@ -728,7 +733,6 @@ class Clock {
|
|||
}
|
||||
const delay = rate - now % rate + 1;
|
||||
this.refresh = true;
|
||||
|
||||
if (rate !== prev) {
|
||||
this.inactive();
|
||||
this.redraw(rate);
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.00: Initial check-in.
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# pooq Round: a maximally readable, naturally 24hr, analogue watch face
|
||||
|
||||
This is a normal watch face for telling the time.
|
||||
It is unusual in that it uses a pie chart for the hour hand. This is much easier and
|
||||
more precise to read at a glance than a conventional hand, and as a bonus can distinguish
|
||||
midnight (all black) from noon (all white).
|
||||
|
||||
The day and date are optionally displayed, typographically smooshed into the corners.
|
||||
Either you'll like that, or you won't.
|
||||
|
||||
## Options
|
||||
|
||||
Because sometimes I don't want to burn what I'm cooking and others I'm lazy and just want to know if it's afternoon yet,
|
||||
you can alter the number of ‘hands’ on the display. When the watch is unlocked, slide up to add dots representing the minute and second,
|
||||
or down to remove the distraction. There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky,
|
||||
in case you want the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails.
|
||||
|
||||
Although we generally obey the system-wide theming, you can long press on the display for a menu of additional options specific to the face.
|
||||
We don't observe the system 12/24 setting, since it the design of the face is equally good in either interpretation.
|
||||
|
||||
By default, there is a backlight that comes on when you twist your wrist. This, of course, somewhat increases power draw and could be
|
||||
annoying in an intentionally dark environment, so there is an option to disable it.
|
||||
|
||||
## Limitations
|
||||
|
||||
Since this is intended as a design exercise, it does not and will probably never support the Bangle's standard widgets.
|
||||
Sorry about that, but control of all the pixels was just too important to me.
|
||||
|
||||
There's also no support for internationalisation at present. This irks me, but since every month and day name is hand-drawn,
|
||||
there's no fix other than hard work. Talk to me about it if there's a language you'd like.
|
||||
|
||||
## Feedback
|
||||
|
||||
[I'd be happy to hear your feedback](https://www.github.com/stephenPspackman) if you have comments or find any bugs, or (most especially)
|
||||
if you find this work interesting.
|
||||
|
||||
## By
|
||||
|
||||
Made by [Stephen P Spackman](https://www.github.com/stephenPspackman).
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkB/4AW+ABCgAJEE4IXMh8ADAMPCoYXUK4gXMAAJHCN4oSG+I+CC4gEBC5gNBO4wuGC44IBGASPEC5ovHIox3JL4hdIR5xdIC54uIC5wWIC5hGKC5pGJC5QKCC6YKDCxIXIBQTCBC6IKDC6QKEC6IKFh52KC4gLHC5wLIC5oLJC5gLKC5YALC/4XfQZYAKh4X/C5B4V/4XYJChGBC7JIT/4wVh/wGCouBC6vwI4hIQagQWDDB5dBC45JNEwIXHGBhFDLwgYNCQQXKDBCgEC5QZFB4oGBA4IA="))
|
||||
|
|
@ -0,0 +1,600 @@
|
|||
/* -*- mode: Javascript; c-basic-offset: 2; indent-tabs-mode: nil; coding: latin-1 -*- */
|
||||
// pooqRound
|
||||
|
||||
// Copyright (c) 2021 Stephen P Spackman
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
// Notes:
|
||||
//
|
||||
// This only works for Bangle 2.
|
||||
|
||||
const isString = x => typeof x === 'string';
|
||||
const imageWidth = i => isString(i) ? i.charCodeAt(0) : i.width;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* System integration */
|
||||
|
||||
const storage = require('Storage');
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Face-specific options */
|
||||
|
||||
class Options {
|
||||
// Protocol: subclasses must have static id and defaults fields.
|
||||
// Only fields named in the defaults will be saved.
|
||||
constructor() {
|
||||
this.id = this.constructor.id;
|
||||
this.file = `${this.id}.json`;
|
||||
this.backing = storage.readJSON(this.file, true) || {};
|
||||
Object.setPrototypeOf(this.backing, this.constructor.defaults);
|
||||
this.reactivator = _ => this.active();
|
||||
Object.keys(this.constructor.defaults).forEach(k => this.bless(k));
|
||||
}
|
||||
|
||||
writeBack(delay) {
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(
|
||||
() => {
|
||||
this.timeout = null;
|
||||
storage.writeJSON(this.file, this.backing);
|
||||
},
|
||||
delay
|
||||
);
|
||||
}
|
||||
|
||||
bless(k) {
|
||||
Object.defineProperty(this, k, {
|
||||
get: () => this.backing[k],
|
||||
set: v => {
|
||||
this.backing[k] = v;
|
||||
// Ten second writeback delay, since the user will roll values up and down.
|
||||
this.writeBack(10000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showMenu(m) {
|
||||
if (m instanceof Function) m = m();
|
||||
if (m) {
|
||||
for (const k in m) if ('init' in m[k]) m[k].value = m[k].init();
|
||||
m[''].selected = -1; // Workaround for self-selection bug.
|
||||
Bangle.on('drag', this.reactivator);
|
||||
this.active();
|
||||
} else {
|
||||
if (this.bored) clearTimeout(this.bored);
|
||||
this.bored = null;
|
||||
Bangle.removeListener('drag', this.reactivator);
|
||||
this.emit('done');
|
||||
}
|
||||
g.clear(true);
|
||||
E.showMenu(m);
|
||||
}
|
||||
|
||||
active() {
|
||||
if (this.bored) clearTimeout(this.bored);
|
||||
this.bored = setTimeout(_ => this.showMenu(), 15000);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.backing = {__proto__: this.constructor.defaults};
|
||||
this.writeBack(0);
|
||||
}
|
||||
}
|
||||
|
||||
class RoundOptions extends Options {
|
||||
constructor() {
|
||||
super();
|
||||
this.menu = () => ({
|
||||
'': {title: '* face options *'},
|
||||
'< Back': _ => this.showMenu(),
|
||||
Ticks: {
|
||||
init: _ => this.resolution,
|
||||
min: 0, max: 3,
|
||||
onchange: x => this.resolution = x,
|
||||
format: x => ['seconds', 'seconds (up)', 'minutes', 'hours'][x]
|
||||
},
|
||||
Calendar: {
|
||||
init: _ => this.calendric,
|
||||
min: 0, max: 5,
|
||||
onchange: x => this.calendric = x,
|
||||
format: x => ['none', 'day', 'date', 'both', 'month', 'full'][x],
|
||||
},
|
||||
'Auto-Illum.': {
|
||||
init: _ => this.autolight,
|
||||
onchange: x => this.autolight = x
|
||||
},
|
||||
Defaults: _ => {this.reset(); this.interact();}
|
||||
});
|
||||
}
|
||||
|
||||
interact() {this.showMenu(this.menu);}
|
||||
}
|
||||
|
||||
RoundOptions.id = 'pooqround';
|
||||
|
||||
RoundOptions.defaults = {
|
||||
resolution: 1,
|
||||
calendric: 5,
|
||||
dayFg: '#fff',
|
||||
nightFg: '#000',
|
||||
autolight: true,
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Assets (generated by resourcer.js, in this directory) */
|
||||
|
||||
const heatshrink = require('heatshrink');
|
||||
const dec = x => E.toString(heatshrink.decompress(atob(x)));
|
||||
const y10F = [
|
||||
dec(
|
||||
'g///EAh////AA4IIBgPwgE+gAOBg/AngXB+EPAYM8gfggEfgF8D4OAj4dB8EDAYI' +
|
||||
'fBBAISBAAMOAYUB4AECnEAkAuBgEQBAPgIYX8IYX/wYDCEwIiMMgUYgECCIZlBAY' +
|
||||
'N4CoRUBIoMP8AZBge8CgMB8+BCAPw+F/gf8jxDB/0D4BGBEQMPAYIeBoAfBnEwge' +
|
||||
'Ah0cB4MDx4PBgHn4EB8E7LQM8h/eJ4MDBgIpB+H+g/wnE/WwMMO4P8LwM/XAJLBT' +
|
||||
'gY7BAAN/wC9CQwV+jwDB/4pBgP/EQKYBBIIxBPQP+SATfCIYIiCO4I9BBwM//hlB' +
|
||||
'PQJlCwYGBTAPgIgM4CYM8hwKBMoODegPA8F+gZlBewP4hz/BE4QrBGgM/LAV//4+' +
|
||||
'BAYJyBPwM/KQMeGQMPFwM8H4UHBIPwGQNwn4yBnhxBGQJxBGQK5BGQKWDOwUACAM' +
|
||||
'D/BDCNYPg///8E5HwR2BIwMDSgK0FSocMAYTLBAAYpBQAPnDwJGBEwK+B/hlB+F8' +
|
||||
'TARABTAJABTAPBMoR+BMoKXBDoX5DwIuBMoUPS4THCGwJbBhAaBvh5B+EHwPAOwP' +
|
||||
'guA1BvCcB4E8nxlBn1/VoIyBwDKBO4SGCgA='
|
||||
), 48, dec('hgAI'), 34
|
||||
];const y1F = [
|
||||
dec(
|
||||
'g//AAPggE/AoX8gF/AoX+gF8CoU+gHwAoUPgAZBEIQFGCIodFFIo1FIIoADnAFEj' +
|
||||
'gFEh0AhA1EiAFCgeAFIf/4A1DFQIED/5MDGB6OEjAECHIIYDhkAuAFCjwFEj6DEn' +
|
||||
'+AAod74AFD/PgvAtC+Hwv/wgZSBvEfLwc8RISOBGAJsBVAXgggEBE4PgIgJLC8E8' +
|
||||
'I4fgXQS/B8IhBGwOA8YFCgfA9+eAoMB4H/j/ACIPA/kPCQJCB/DMDMoMBboYVBKo' +
|
||||
'IDBSYeAAoYlCAATpEg/4Xwc/QIcPFoJcBQIP8GILXCDYLXBbId//BeCL4QwDgIwD' +
|
||||
'AAIXBDAQfCEYSPBAoaPCPQKPCAoZgBAoYvBAoIXBBAIFB/ALDEoJHBAoaPDaQSPB' +
|
||||
'AoKcBJgY9DTQX/EoKmCC4SyCYYJJB+CHBj+Aj8ASYJNBBINwIIOAM4ILDAYN/wAB' +
|
||||
'BB4JBBI45vCRYgADApEHL4pHB8AECFIPhAYLCCAggFBAgaNCYwgFEbAkAwAFEc4S' +
|
||||
'PCj/+LIKPBv6PEAoRnBFIMDFYLXCKoTLDa4YRDBYIdDh4FDMoQ1DK4ZBBMQIDBJY' +
|
||||
'bWBFIMEIIQpBgxxBgZRBh8AAYN8AoQVBjgbBAoTZBvwRCvEBF4IdB+E/OIp9CJgZ' +
|
||||
'BCQQUAA='
|
||||
), 48, dec('hgAI'), 48
|
||||
];const y10sF = [
|
||||
dec(
|
||||
'j/+gP//0PgE8mEAmHwgfBBQINB8AWDgcAoEGAYMMj///H///wBwNgAQPAAQMgg8B' +
|
||||
'wE+hkA9kwg8Y+F4mP/4Fg/AVD4EBgcCg0MnEMmfgmH94PD4f+hkHIIgbBg44B/ng' +
|
||||
'h/H/H8n4IBg4QBhwUC//Bgf+FYMwAIPAjHDwPjg//gEPLgUAOYMAn/+DAM8j1gmH' +
|
||||
'h8fDBAMIHIRwDQAJtBg/8mH+gHPwEDCII/DAAM+n8B/v+h0+jkwuEw8fhV4UD8Yr' +
|
||||
'DjxDB/0Ch88CoLEB+fPwK0BKIOACoQA='
|
||||
), 48, dec('hAAI'), 22
|
||||
];const y1sF = [
|
||||
dec(
|
||||
'j///0A/4ABgfAgEPgwNBg0MAYMMjwDBvAWB//gh4DBEAUDgEgAYQeBgcDEwQSCCY' +
|
||||
'oDCiACBwFgGoOBwEAnODBwPhw/Ag+Bw/gv0Bwf/+EBwAkBgPgCYOA4EQgIeB8ASB' +
|
||||
'g/AgcGnuAg0N8fAnkfIwPwnEB/40BgE8IYX8AYN/7hDB/kcg4xBv4TBC4kcLgUcv' +
|
||||
'4ZBIgJIBHoNgHoJ8BgOGKQMHhijBnkYHoQlEv4DBRYWAv+eOgPwmEDg4mBXIXwni' +
|
||||
'SBDwRICSwIABWIM/HoM//57BEoMGv7dC/DrCLoU4eYfAv4kB8f/wPB98HLgP4TQM' +
|
||||
'B+EGh0PvE8QwN/+EP8E/LAK6CBIMAwPg+EDDwNgh8GJQP8h8Hz/gN4P+gBMBJIMA'
|
||||
), 48, dec('hEHhAAGA'), 31
|
||||
];const d10F = [
|
||||
dec(
|
||||
'AAXgjEAjkHgEDwPAgFwvEAh0f///44CB/ICB/4aDAQMcAQMDwAhBuAhBj0B4EH4E' +
|
||||
'wgP4h0Av4JBj3gnEHzkHgPjwF4/Fwh/+CQP/HwMD4E4gJLCvAuBj0ADgOGg+B8fA' +
|
||||
'uF5FoMeDQPH/l4vP8g/+vg4BzkAg/gA='
|
||||
), 49, dec('hcMhYA=='), 27
|
||||
];const d1F = [
|
||||
dec(
|
||||
'AB1/+AECj///4FCAgP/8EAgf/4F//EAg4CBgf8gEPwAUBn0AhwaCAYMeAoUPgEcA' +
|
||||
'oUHAowRFDoopFGopBFJopZGBgIKCABlAIIcA4AFDgIFEgZBCAoMHAohVBAoY6CHg' +
|
||||
'U/Aol/AogADGoQFUABEMAQM/AQN8bIRZBRgJ5BLILhBgP3LIcD84rDg/HWYcPw4F' +
|
||||
'Dj4PBAoU+Aol8Aon4PocB+CJDgfgAoXgh/ATYX4v+AU4X//w/DbYQFCCwJ3PvDIE' +
|
||||
'NYQCCdoJ6CgfAiCGCI4NwgEeFwISCLoMeJwJdCnkfHYd4v4FD+f5AoUB9/BAoUD/' +
|
||||
'4jCh8HG4IpCh5DBAIMeE4Q/BvjMCfoP8Z4Uf//wCgInB/5lCABs+AoicBAAUDAok' +
|
||||
'P9wFDv+OCAjUCHQP4AoY5BAoUHEIIFCv5JBAoLQBLQYqEApQpDArIAJv5IBnBTCV' +
|
||||
'4McJAQFBcYLvBB4IkBd4N4cYQBBeoLdBCYIFDngFECoIFDOwIdCc4QpCFwIZCjwu' +
|
||||
'BEoU8FwIxCvAIBEIPB+AUBJIP/8AmBLYWAd4RnBdx4XCcYf/Dgn//AuEP4LjBXoJ' +
|
||||
'AC//vQYT0BBIKDC+CZBOIM/wAFDVYIFCgIrBAoUDPoIdCO4QnBaQYnBGoQVBIIZI' +
|
||||
'CJoTNCLIY4CAYIaDAAKRCAASRDAAIaEYAQtDYAI5DRgZFCAAYuCQoQuBAgIFBvEH' +
|
||||
'AgIFB+CgBAAMB86lE76EBFwX/GocPNoYmBIwk/HQl8LpIAQRId/SoYDB4ZJCUoPn' +
|
||||
'VoUHwP3Y4YYBY4k+Y4h5BdILhBd4YFFCIodFFIo1FIIpNFLIplGAArMFn6oBHYMA' +
|
||||
'DYQFBgP5E4IFBgfgUgIFCwBZBEAL1BPYZbDA4Z7DLYRtCBYYlDBoIxCEYMBHoIvC' +
|
||||
'HAI7Dh5PBI4X/LIX//7+Dn52Eh4QCA=='
|
||||
), 48, dec('ikPigAGA'), 48
|
||||
];const dowF = [
|
||||
dec(
|
||||
'gf8AYNwgEP/4FBvEAj//wEAnkAn0H4EAjwNBgPgAoQZBAoMOgHwAongCIQFDDoIF' +
|
||||
'FDoPggYFBF4IFBGoI7B+AFCE4NwCIIlCuAdBIYU4gPwn5VBjEA//+M4d//AFDh4W' +
|
||||
'BB4IgBAAX/B4n/PoQACJQIcEAokHAqAXFEYhLF/6tCApIADn4ED/zFBAAX8gaGBA' +
|
||||
'AZZFQIR2GdQQYRBYgXFEYoWRKQQWCLoRrEHgoAIg7LEj7LEn4bEvk+AodwhwFD+C' +
|
||||
'5E8DFEAqIdFFIo1FIIpNFLIoEEAtShCVwQEDVwIFDKAJBvAAv/Bgn/RIjzGjwFEW' +
|
||||
'YicBAqAXFEYh6CRIgFKTYzjEAwt/AxxvDHAkf//AAgMDPIgVBGAnwAoYRBIYk/S4' +
|
||||
'kDMIgeBFIQEBBYRTBCAZ3FAggAMg4zEj7LEn7LEv++AodzxwFD+ePAofjw4FVDoo' +
|
||||
'pFv+eIImcJomYLImAAoZeEAtTyBAAQFEVYIFDSQIvhAojaCFwgABh4YEngFEuAqJ' +
|
||||
'gPAAocDApYuEgP/fgl/+B9HAAv+Aon8HQMOIAkeAokcAohaDAoM4Aol4AohmDAoJ' +
|
||||
'BDAoJsDAo7vhABbJDAo9/AojEFMYbKMArCBDFI41FWIYABggFEgbuCDYMPLIQbBj' +
|
||||
'//wBdCn0H4DZCvEBb4YZBdYZBBAofgCIQFDDoIFFDoPggYFBF4IFBGoI7B+AFCE4' +
|
||||
'NwCIIlCuAdBIYU4gPwn5VBjC7B/y0Dv/4YwcPCwMAjJlCAAM584FDufDCAUA8eBA' +
|
||||
'p/zC4n5EYj1BAoc//4RDU4IFDA=='
|
||||
), 48, dec('kElkMljsljw='), 48
|
||||
];const mF = [
|
||||
dec(
|
||||
'/AEDvEH4AFCgPAnwMDh0B+AGD8EPwAFCg8AvgMDuED8AMEj4MDDwI0DhwOB/4ACC' +
|
||||
'4M/AoX8HgIMDCoI0EAAI0EgA0DnACBGgXHL4Q0Bjn+IYXAgfOCwRpBnPHEQmcuAG' +
|
||||
'DBg3csAGDj4mCAAX/QwhkBWSEDDIp3BAoZ3BBgkeDIp9FOYQMJDIomGh5NFv/wVo' +
|
||||
'YABYIgZBYIYABgKWBHAcPHAKsCgF4VoJDD4AVCIYbtBfAnwgYDBg+Ag6bBEQM8EQ' +
|
||||
'KoCDwMDwP9EQI0Bnk9540DZ4Y/CZ4Y0BbggwBDIY0BgP8JIbcB7yBE/pjDEAOQbZ' +
|
||||
'8fRwT7DAAL7E/4zEjh9EKwLCEnB9BBhIZFgPzEwkP/jcFe4iYBdYLcEAwr5CBgYj' +
|
||||
'Hh65BAxU/AwjNCIhEH/BkEGYqTCRwYMFACE4AonHZ4kcIQkB5yOEnPHIYmcuAMK7' +
|
||||
'lgNJJQBJojkBKSB3BDIk/DIkBBgseDIpmEOYwMGDIsAOYkAgxBGGYjzBIwoMDXYI' +
|
||||
'tCaAQFCCwP8jiECCwMBBhAZGEwwzHIAxNGTY5UKTYIMEjkORwomEnEHBhQZFgPzT' +
|
||||
'gkP/hBEv+ACYivFe3adBAAfwAwoNFGYJkGh/+Axc/AwkfAoggFg/4ZgwzDj4GDiD' +
|
||||
'7CAAPxRQswNIp1FBgnH4TPE/0gC4fO8wMDnPHsAMDzl2BhXcsxpFBgZQB+xqE/4z' +
|
||||
'DAAMCLRJ3BwaWFBgvjDAkfuAGEu4MFfoYZBW4v/eIn/8CzEvEHBocB4E+BgcOgIn' +
|
||||
'DgHgh+ANAcAvgMDuED8AMEj4MDDwI0DhyECAAQXBn4FCf4MBBgYVBGggABGghrBD' +
|
||||
'gQqCGgJ0BL4QJBTYJDCBIMBJYRpCJoIAEUIoMGPIgmDVAYMFKgQAODJh3BBgkeDI' +
|
||||
'p9FnAMLDIomGh5NFv/we372/exgZDe0BpCDIbBBDIl/EwonBAogMEHIIZDD4KUBH' +
|
||||
'wYFDCAPBOwQWCjgMHDI4mGGYwcC+JNFiDAFOIswEAmDDAn8kAME8QYEjwMDAAN2Y' +
|
||||
'QtgTonmYQoMDEwP2YQoZEgECJoozEv5NEj/+LQaYB8YMDn0fM4mAu4MDnEHuAMD8' +
|
||||
'KVEIAPgEwn+WAuAK4LABj7PDwEAvhJBCwUB8EP8EffQMOgH4C4ITB+EHAYN4RwMA' +
|
||||
'ng/BE4PwDYITCnw2BF4YKBF4LwDgInBKYLoFFQIAJgZCBAAZdCTYjOE/p6DgE954' +
|
||||
'fEziUDgE544ME7gtEj/OExUP7hAEnJTKAAxuBFoa4BOokfBgkB4AzEniZBewhaEB' +
|
||||
'goZGj61BRxMHWQIADjwJCIgLICJQQABDIL9BAAKoBg4iCgYTBKoZABhwnDJoJCDg' +
|
||||
'4OCAAQXBewIABJoI5DHQSLBAAP8B4I6CcQgANgbVEOg0fEAkB8KOEnBNBVBIMMjh' +
|
||||
'yEWo0MhhSPgJoBwCZDNwp2BJor2LJpjAFAAImEJwI2BAAfwj4GEXYgMBAwKlFv4G' +
|
||||
'GFQpYFXQx0BAwx6DLQIGCIIgeCIAkHBgoAPn4FEh/8HQpPEn0fVCPhO4kfZ4hvGg' +
|
||||
'YSEgRGFngFEgf4AwkfSws/EwgtBBhQZFEw0cOwIHEuF4AocHWIL2LBgsHGoaBBn7' +
|
||||
'SD+DZEnzIFI4MPAoS1CAwbVRTYqoGWosB/p7EnvPD4mcbgk544ME7jcF5wmKh/cI' +
|
||||
'Ak5LUvhGYk4VAIfDwBaEBgsB4AZEjkOGYnA4AA=='
|
||||
), 49, dec('k0jk0kksmj0lk8lAwIA='), 52
|
||||
];
|
||||
const lockI = dec('hURwMAj0P485w1h3/4g15wFgjPmgOAs+Yg0B//AA');
|
||||
const lockSI = dec('hMNwMAjkfjHMt/8g1zgOc4FnmEf/AA==');
|
||||
const batteryI = dec('hERwMAjH/ABw');
|
||||
const chargeI = dec('g8NwMAgkYsHDh0fw8MmFhwUA');
|
||||
const HRMI = dec('iERwMAjk4l10t/29/3AIfn+ek6VTlPX9d3/U3/Ef/EP+EH8ED4EBwAA=');
|
||||
const compassI = dec('hMJwMAhEEg8Dwfh2Pc43BwA=');
|
||||
const y100I = dec('h8RwMAvk5/n6nOwm9w9lnzH+mO4sc4405xk7jE2mEssEd4EbgE+gE4A=');
|
||||
const y100sI = dec('hcKwMAsOWvHZ+c2s1s4uYmcD4EwA');
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Status */
|
||||
|
||||
const status = (p, i) => function (g, x, y, rl) { // Nested arrows are currently broken!
|
||||
if (!p()) return x;
|
||||
if (rl) x -= imageWidth(i);
|
||||
g.setColor(g.theme.fg).drawImage(i, x, y);
|
||||
return rl ? x - 1 : x + imageWidth(i) + 1;
|
||||
};
|
||||
|
||||
const doLocked = status(_ => Bangle.isLocked(), lockI);
|
||||
const doPower = (g, x, y, rl) => {
|
||||
const c = Bangle.isCharging();
|
||||
const b = E.getBattery();
|
||||
if (!c && b > 50) return x;
|
||||
if (rl) x -= imageWidth(batteryI);
|
||||
g.setColor(g.theme.fg).drawImage(batteryI, x, y);
|
||||
g.setColor(b <= 10 ? '#f00' : b <= 30 ? '#ff0' : '#0f0');
|
||||
let h = 13 * (100 - b) / 100;
|
||||
g.fillRect(x + 1, y + 2 + h, x + 6, y + 15);
|
||||
if (c) g.setColor(g.theme.bg).drawImage(chargeI, x, y + 2);
|
||||
return rl ? x - 1 : x + imageWidth(batteryI) + 1;
|
||||
};
|
||||
|
||||
const doHRM = status(_ => Bangle.isHRMOn(), HRMI); // Might show Bangle.getHRM().bpm if confident?
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Watch face */
|
||||
|
||||
class Round {
|
||||
constructor(g) {
|
||||
this.g = g;
|
||||
this.b = Graphics.createArrayBuffer(g.getWidth(), g.getHeight(), 1, {msb: true});
|
||||
this.bI = {
|
||||
width: this.b.getWidth(), height: this.b.getHeight(), bpp: this.b.getBPP(),
|
||||
buffer: this.b.buffer, transparent: 0
|
||||
};
|
||||
this.c = Graphics.createArrayBuffer(g.getWidth(), g.getHeight(), 1, {msb: true});
|
||||
this.cI = {
|
||||
width: this.c.getWidth(), height: this.c.getHeight(), bpp: this.c.getBPP(),
|
||||
buffer: this.c.buffer, transparent: 0
|
||||
};
|
||||
this.options = new RoundOptions();
|
||||
this.timescales = [1000, 0, 60000, 900000];
|
||||
this.state = {};
|
||||
// Precomputed polygons for the border areas.
|
||||
this.tl = [0, 0, 58, 0, 0, 58];
|
||||
this.tr = [176, 0, 176, 58, 119, 0];
|
||||
this.bl = [0, 176, 0, 119, 58, 176];
|
||||
this.br = [176, 176, 119, 176, 176, 119];
|
||||
this.xc = g.getWidth() / 2;
|
||||
this.yc = g.getHeight() / 2;
|
||||
this.minR = 5;
|
||||
this.secR = 3;
|
||||
this.r = this.xc - this.minR;
|
||||
}
|
||||
|
||||
reset() {this.state = {}; this.g.clear(true);}
|
||||
|
||||
doIcons(which) {
|
||||
this.state[which] = null;
|
||||
this.render(new Date()); // Not quite right, I think.
|
||||
}
|
||||
|
||||
pie(f, a0, a1, invert) {
|
||||
if (!invert) return this.pie(f, a1, a0 + 1, true);
|
||||
let t0 = Math.tan(a0 * 2 * Math.PI), t1 = Math.tan(a1 * 2 * Math.PI);
|
||||
let i0 = Math.floor(a0 * 4 + 0.5), i1 = Math.floor(a1 * 4 + 0.5);
|
||||
let x = f.getWidth() / 2, y = f.getHeight() / 2;
|
||||
let poly = [
|
||||
x + (i1 & 2 ? -x : x) * (i1 & 1 ? 1 : t1),
|
||||
y + (i1 & 2 ? y : -y) / (i1 & 1 ? t1 : 1),
|
||||
x,
|
||||
y,
|
||||
x + (i0 & 2 ? -x : x) * (i0 & 1 ? 1 : t0),
|
||||
y + (i0 & 2 ? y : -y) / (i0 & 1 ? t0 : 1),
|
||||
];
|
||||
if (i1 - i0 > 4) i1 = i0 + 4;
|
||||
for (i0++; i0 <= i1; i0++) poly.push(
|
||||
3 * i0 & 2 ? f.getWidth() : 0, i0 & 2 ? f.getHeight() : 0
|
||||
);
|
||||
f.setColor(0).fillPoly(poly);
|
||||
}
|
||||
|
||||
hand(t, d, c0, r0, c1, r1) {
|
||||
t *= Math.PI / 30;
|
||||
const r = this.r;
|
||||
const z = 2 * r0 + 1;
|
||||
const x = this.xc + r * Math.sin(t), y = this.yc - r * Math.cos(t);
|
||||
const x0 = x - r0, y0 = y - r0;
|
||||
d = d ? d[0] : Graphics.createArrayBuffer(z, z, 16, {msb: true});
|
||||
for (let i = 0; i < z; i++) for (let j = 0; j < z; j++) {
|
||||
d.setPixel(i, j, g.getPixel(x0 + i, y0 + j));
|
||||
}
|
||||
g.setColor(c0).fillCircle(x, y, r0);
|
||||
if (c1 !== undefined) g.setColor(c1).fillCircle(x, y, r1);
|
||||
return [d, x0, y0];
|
||||
}
|
||||
|
||||
render(d) {
|
||||
const g = this.g;
|
||||
const b = this.b, bI = this.bI;
|
||||
const c = this.c, cI = this.cI;
|
||||
const state = this.state;
|
||||
const options = this.options;
|
||||
const cal = options.calendric;
|
||||
const res = options.resolution;
|
||||
const dow = (cal == 1 || cal > 2) && d.getDay();
|
||||
const ts = res < 2 && d.getSeconds();
|
||||
const tm = res < 3 && d.getMinutes() + ts / 60;
|
||||
const th = d.getHours() + d.getMinutes() / 60;
|
||||
const dd = cal > 1 && d.getDate();
|
||||
const dm = cal > 3 && d.getMonth();
|
||||
const dy = cal > 4 && d.getFullYear();
|
||||
const xc = this.xc, yc = this.yc, r = this.r;
|
||||
const dlr = xc * 3/4, dlw = 8, dlhw = 4;
|
||||
|
||||
// Restore saveunders for fast-moving, overdrawing indicators.
|
||||
if (state.sd) g.drawImage.apply(g, state.sd);
|
||||
if (state.md) g.drawImage.apply(g, state.md);
|
||||
|
||||
if (dow !== state.dow) {
|
||||
g.setColor(g.theme.bg).fillPoly(this.tl);
|
||||
if (dow === +dow) {
|
||||
g.setColor(g.theme.fg).setFontCustom.apply(g, dowF).drawString(dow, 5, 5);
|
||||
}
|
||||
state.dow = dow;
|
||||
}
|
||||
|
||||
const locked = Bangle.isLocked();
|
||||
const charging = Bangle.isCharging();
|
||||
const battery = E.getBattery();
|
||||
const HRMOn = Bangle.isHRMOn();
|
||||
if (dy !== state.dy ||
|
||||
locked !== state.locked ||
|
||||
charging !== state.charging ||
|
||||
Math.abs(battery - state.battery) > 2 ||
|
||||
HRMOn !== state.HRMOn
|
||||
) {
|
||||
g.setColor(g.theme.bg).fillPoly(this.tr);
|
||||
const u = dy % 10;
|
||||
if (charging || battery < 50 || HRMOn || locked && dy !== +dy) {
|
||||
let x = 172, y = 5;
|
||||
x = doLocked(g, x, y, true);
|
||||
x = doPower(g, x, y, true);
|
||||
x = doHRM(g, x, y, true);
|
||||
if (dy === +dy) {
|
||||
g.setColor(g.theme.fg).drawImage(y100sI, 145, 23);
|
||||
g.setFontCustom.apply(g, y10sF).drawString((dy - u) / 10 % 10, 157, 23);
|
||||
g.setFontCustom.apply(g, y1sF).drawString(u, 165, 23);
|
||||
}
|
||||
} else if (dy === +dy) {
|
||||
g.setColor(g.theme.fg);
|
||||
if (locked) g.drawImage(lockSI, 136, 5);
|
||||
else g.drawImage(y100I, 130, 5);
|
||||
g.setFontCustom.apply(g, y10F).drawString((dy - u) / 10 % 10, 146, 5);
|
||||
g.setFontCustom.apply(g, y1F).drawString(u, 160, 5);
|
||||
}
|
||||
state.dy = dy;
|
||||
state.locked = Bangle.isLocked();
|
||||
state.charging = Bangle.isCharging();
|
||||
state.battery = E.getBattery() - E.getBattery() % 2;
|
||||
state.HRMOn = Bangle.isHRMOn();
|
||||
}
|
||||
if (dm !== state.dm) {
|
||||
g.setColor(g.theme.bg).fillPoly(this.bl);
|
||||
if (dm === +dm) {
|
||||
g.setColor(g.theme.fg).setFontCustom.apply(g, mF);
|
||||
g.drawString(String.fromCharCode(49 + dm), 5, 124);
|
||||
}
|
||||
state.dm = dm;
|
||||
}
|
||||
if (dd !== state.dd) {
|
||||
g.setColor(g.theme.bg).fillPoly(this.br);
|
||||
if (dd === +dd) {
|
||||
let u = dd % 10;
|
||||
g.setColor(g.theme.fg).setFontAlign(1, 1);
|
||||
g.setFontCustom.apply(g, d10F).drawString((dd - u) / 10, 152, 172);
|
||||
g.setFontAlign(-1, 1);
|
||||
g.setFontCustom.apply(g, d1F).drawString(u, 152, 172);
|
||||
g.setFontAlign(-1, -1);
|
||||
}
|
||||
}
|
||||
if (th !== state.th) {
|
||||
state.th = th;
|
||||
b.clear(true).fillCircle(88, 88, r - 1);
|
||||
g.setColor(options.nightFg).drawImage(bI);
|
||||
if (th < 12) this.pie(b, th / 12, 1, true);
|
||||
else this.pie(b, 1, th / 12, true);
|
||||
g.setColor(options.dayFg).drawImage(bI);
|
||||
}
|
||||
state.md = tm === +tm ?
|
||||
this.hand(tm, state.md, g.theme.bg, this.minR, g.theme.fg, this.minR - 1) :
|
||||
null;
|
||||
state.sd = ts === +ts ?
|
||||
this.hand(ts, state.sd, g.theme.fg2, this.secR) :
|
||||
null;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Master clock */
|
||||
|
||||
class Clock {
|
||||
constructor(face) {
|
||||
this.face = face;
|
||||
this.timescales = face.timescales;
|
||||
this.options = face.options;
|
||||
this.rates = {};
|
||||
this.faceUp = null;
|
||||
|
||||
this.options.on('done', () => this.start());
|
||||
|
||||
this.listeners = {
|
||||
lcdPower: on => on ? this.active() : this.inactive(),
|
||||
charging: () => {face.doIcons('charging'); this.active();},
|
||||
lock: () => {face.doIcons('locked'); this.active();},
|
||||
faceUp: up => {
|
||||
this.conservative = !up;
|
||||
this.faceUp = up;
|
||||
this.active();
|
||||
},
|
||||
twist: _ => this.options.autolight && Bangle.setLCDPower(true),
|
||||
drag: e => {
|
||||
if (this.t0) {
|
||||
if (e.b) {
|
||||
e.x < this.xN && (this.xN = e.x) || e.x > this.xX && (this.xX = e.x);
|
||||
e.y < this.yN && (this.yN = e.y) || e.y > this.yX && (this.yX = e.y);
|
||||
} else if (this.xX - this.xN < 20) {
|
||||
if (e.y - this.e0.y < -50) {
|
||||
this.options.resolution > 0 && this.options.resolution--;
|
||||
this.rates.clock = this.timescales[this.options.resolution];
|
||||
this.active();
|
||||
} else if (e.y - this.e0.y > 50) {
|
||||
this.options.resolution < this.timescales.length - 1 &&
|
||||
this.options.resolution++;
|
||||
this.rates.clock = this.timescales[this.options.resolution];
|
||||
this.active();
|
||||
} else if (this.yX - this.yN < 20 && Date.now() - this.t0 > 500) {
|
||||
this.stop();
|
||||
this.options.interact();
|
||||
}
|
||||
this.t0 = null;
|
||||
}
|
||||
} else if (e.b) {
|
||||
this.t0 = Date.now(); this.e0 = e;
|
||||
this.xN = this.xX = e.x; this.yN = this.yX = e.y;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
redraw(rate) {
|
||||
const now = this.updated = new Date();
|
||||
if (this.refresh) this.face.reset();
|
||||
this.refresh = false;
|
||||
rate = this.face.render(now, rate);
|
||||
if (rate !== this.rates.face) {
|
||||
this.rates.face = rate;
|
||||
this.active();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
inactive() {
|
||||
this.timeout && clearTimeout(this.timeout);
|
||||
this.exception && clearTimeout(this.exception);
|
||||
this.interval && clearInterval(this.interval);
|
||||
this.timeout = this.exception = this.interval = this.rate = null;
|
||||
this.face.reset(); // Cancel any ongoing background rendering
|
||||
return this;
|
||||
}
|
||||
|
||||
active() {
|
||||
const prev = this.rate;
|
||||
const now = Date.now();
|
||||
let rate = Infinity;
|
||||
for (const k in this.rates) {
|
||||
let r = this.rates[k];
|
||||
r === +r || (r = r[+this.conservative])
|
||||
r < rate && (rate = r);
|
||||
}
|
||||
const delay = rate - now % rate + 1;
|
||||
this.refresh = true;
|
||||
|
||||
if (rate !== prev) {
|
||||
this.inactive();
|
||||
this.redraw(rate);
|
||||
if (rate < 31622400000) { // A year!
|
||||
this.timeout = setTimeout(
|
||||
() => {
|
||||
this.inactive();
|
||||
this.interval = setInterval(() => this.redraw(rate), rate);
|
||||
if (delay > 1000) this.redraw(rate);
|
||||
this.rate = rate;
|
||||
}, delay
|
||||
);
|
||||
}
|
||||
} else if (rate > 1000) {
|
||||
if (!this.exception) this.exception = setTimeout(() => {
|
||||
this.redraw(rate);
|
||||
this.exception = null;
|
||||
}, this.updated + 1000 - Date.now());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.inactive();
|
||||
for (const l in this.listeners) {
|
||||
Bangle.removeListener(l, this.listeners[l]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.inactive(); // Reset to known state.
|
||||
this.conservative = false;
|
||||
this.rates.clock = this.timescales[this.options.resolution];
|
||||
this.active();
|
||||
for (const l in this.listeners) {
|
||||
Bangle.on(l, this.listeners[l]);
|
||||
}
|
||||
Bangle.setUI('clock');
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/* Main */
|
||||
|
||||
const clock = new Clock(new Round(g)).start();
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -1 +1,2 @@
|
|||
0.01: First release
|
||||
0.02: Fix dependancies, fix type to Purple
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
var color_options = ['Green','Orange','Cyan','Perple','Red','Blue'];
|
||||
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue'];
|
||||
var bg_code = ['#0f0','#ff0','#0ff','#f0f','#f00','#00f'];
|
||||
|
||||
E.showMenu({
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: Cloning torch and making it red :D
|
||||
0.02: Modify for setUI and Bangle 2
|
||||
|
|
|
|||
|
|
@ -2,21 +2,38 @@ Bangle.setLCDPower(1);
|
|||
Bangle.setLCDTimeout(0);
|
||||
g.reset();
|
||||
c = 1;
|
||||
|
||||
function setColor(delta){
|
||||
c+=delta;
|
||||
c = Math.max(c,0);
|
||||
c = Math.min(c,2);
|
||||
if (c<1){
|
||||
g.setColor(c,0,0);
|
||||
Bangle.setLCDBrightness(c >= 0.1 ? c : 0.1);
|
||||
}else{
|
||||
g.setColor(1,c-1,c-1);
|
||||
Bangle.setLCDBrightness(1);
|
||||
}
|
||||
g.fillRect(0,0,g.getWidth(),g.getHeight());
|
||||
}
|
||||
setColor(0)
|
||||
// BTN1 light up toward white
|
||||
// BTN3 light down to red
|
||||
// BTN2 to reset
|
||||
setWatch(()=>setColor(0.1), BTN1, { repeat:true, edge:"rising", debounce: 50 });
|
||||
setWatch(()=>load(), BTN2);
|
||||
setWatch(()=>setColor(-0.1), BTN3, { repeat:true, edge:"rising", debounce: 50 });
|
||||
|
||||
function updownHandler(direction){
|
||||
if (direction == undefined){
|
||||
c=1;
|
||||
setColor(0);
|
||||
} else {
|
||||
setColor(-direction * 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
setColor(0);
|
||||
|
||||
// Bangle 1:
|
||||
// BTN1: light up toward white
|
||||
// BTN3: light down to red
|
||||
// BTN2: reset
|
||||
// Bangle 2:
|
||||
// Swipe up: light up toward white
|
||||
// Swipe down: light down to red
|
||||
// BTN1: reset
|
||||
Bangle.setUI("updown", updownHandler);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Corrected variable initialisation
|
||||
0.03: Advertise app name, added screenshots
|
||||
0.04: Advertise bar, GPS, HRM and mag services
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Currently implements:
|
|||
- Heart Rate Monitor
|
||||
- Magnetometer
|
||||
|
||||
in the menu display but NOT YET in Bluetooth Low Energy advertising (which will be implemented in a subsequent version).
|
||||
in the menu display, and broadcasts all sensor data readings _except_ acceleration in Bluetooth Low Energy advertising packets as GATT characteristic services.
|
||||
|
||||
|
||||
## Controls
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
// Non-user-configurable constants
|
||||
const APP_ID = 'sensible';
|
||||
const ESPRUINO_COMPANY_CODE = 0x0590;
|
||||
const APP_ADVERTISING_DATA = [ 0x12, 0xff, 0x90, 0x05, 0x7b, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x3a, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x62,
|
||||
0x6c, 0x65, 0x7d ];
|
||||
|
||||
|
||||
// Global variables
|
||||
|
|
@ -20,6 +23,12 @@ let isBarEnabled = true;
|
|||
let isGpsEnabled = true;
|
||||
let isHrmEnabled = true;
|
||||
let isMagEnabled = true;
|
||||
let isNewAccData = false;
|
||||
let isNewBarData = false;
|
||||
let isNewGpsData = false;
|
||||
let isNewHrmData = false;
|
||||
let isNewMagData = false;
|
||||
|
||||
|
||||
|
||||
// Menus
|
||||
|
|
@ -91,22 +100,121 @@ let magMenu = {
|
|||
};
|
||||
|
||||
|
||||
// Transmit the app name under the Espruino company code to facilitate discovery
|
||||
function transmitAppName() {
|
||||
let options = {
|
||||
showName: false,
|
||||
manufacturer: ESPRUINO_COMPANY_CODE,
|
||||
manufacturerData: JSON.stringify({ name: APP_ID }),
|
||||
interval: 2000
|
||||
// Check for new sensor data and update the advertising sequence
|
||||
function transmitUpdatedSensorData() {
|
||||
let data = [ APP_ADVERTISING_DATA ]; // Always advertise at least app name
|
||||
|
||||
if(isNewBarData) {
|
||||
data.push(encodeBarServiceData());
|
||||
isNewBarData = false;
|
||||
}
|
||||
|
||||
NRF.setAdvertising({}, options);
|
||||
if(isNewGpsData && gps.lat && gps.lon) {
|
||||
data.push(encodeGpsServiceData());
|
||||
isNewGpsData = false;
|
||||
}
|
||||
|
||||
if(isNewHrmData) {
|
||||
data.push({ 0x2a37: [ 0, hrm.bpm ] });
|
||||
isNewHrmData = false;
|
||||
}
|
||||
|
||||
if(isNewMagData) {
|
||||
data.push(encodeMagServiceData());
|
||||
isNewMagData = false;
|
||||
}
|
||||
|
||||
NRF.setAdvertising(data, { showName: false, interval: 200 });
|
||||
}
|
||||
|
||||
|
||||
// Encode the bar service data to fit in a Bluetooth PDU
|
||||
function encodeBarServiceData() {
|
||||
let tEncoded = Math.round(bar.temperature * 100);
|
||||
let pEncoded = Math.round(bar.pressure * 100);
|
||||
let eEncoded = Math.round(bar.altitude * 100);
|
||||
|
||||
if(bar.temperature < 0) {
|
||||
tEncoded += 0x10000;
|
||||
}
|
||||
if(bar.altitude < 0) {
|
||||
eEncoded += 0x1000000;
|
||||
}
|
||||
|
||||
let t = [ tEncoded & 0xff, (tEncoded >> 8) & 0xff ];
|
||||
let p = [ pEncoded & 0xff, (pEncoded >> 8) & 0xff, (pEncoded >> 16) & 0xff,
|
||||
(pEncoded >> 24) & 0xff ];
|
||||
let e = [ eEncoded & 0xff, (eEncoded >> 8) & 0xff, (eEncoded >> 16) & 0xff ];
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
0x05, 0x16, 0x6e, 0x2a, t[0], t[1], // Temperature
|
||||
0x07, 0x16, 0x6d, 0x2a, p[0], p[1], p[2], p[3], // Pressure
|
||||
0x06, 0x16, 0x6c, 0x2a, e[0], e[1], e[2] // Elevation
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Encode the GPS service data using the Location and Speed characteristic
|
||||
function encodeGpsServiceData() {
|
||||
let latEncoded = Math.round(gps.lat * 10000000);
|
||||
let lonEncoded = Math.round(gps.lon * 10000000);
|
||||
let hEncoded = Math.round(gps.course * 100);
|
||||
let sEncoded = Math.round(1000 * gps.speed / 36);
|
||||
|
||||
if(gps.lat < 0) {
|
||||
latEncoded += 0x100000000;
|
||||
}
|
||||
if(gps.lon < 0) {
|
||||
lonEncoded += 0x100000000;
|
||||
}
|
||||
|
||||
let s = [ sEncoded & 0xff, (sEncoded >> 8) & 0xff ];
|
||||
let lat = [ latEncoded & 0xff, (latEncoded >> 8) & 0xff,
|
||||
(latEncoded >> 16) & 0xff, (latEncoded >> 24) & 0xff ];
|
||||
let lon = [ lonEncoded & 0xff, (lonEncoded >> 8) & 0xff,
|
||||
(lonEncoded >> 16) & 0xff, (lonEncoded >> 24) & 0xff ];
|
||||
let h = [ hEncoded & 0xff, (hEncoded >> 8) & 0xff ];
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
0x11, 0x16, 0x67, 0x2a, 0x95, 0x02, s[0], s[1], lat[0], lat[1], lat[2],
|
||||
lat[3], lon[0], lon[1], lon[2], lon[3], h[0], h[1] // Location and Speed
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Encode the mag service data using the magnetic flux density 3D characteristic
|
||||
function encodeMagServiceData() {
|
||||
let xEncoded = mag.x; // TODO: units???
|
||||
let yEncoded = mag.y;
|
||||
let zEncoded = mag.z;
|
||||
|
||||
if(xEncoded < 0) {
|
||||
xEncoded += 0x10000;
|
||||
}
|
||||
if(yEncoded < 0) {
|
||||
yEncoded += 0x10000;
|
||||
}
|
||||
if(yEncoded < 0) {
|
||||
yEncoded += 0x10000;
|
||||
}
|
||||
|
||||
let x = [ xEncoded & 0xff, (xEncoded >> 8) & 0xff ];
|
||||
let y = [ yEncoded & 0xff, (yEncoded >> 8) & 0xff ];
|
||||
let z = [ zEncoded & 0xff, (zEncoded >> 8) & 0xff ];
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
0x09, 0x16, 0xa1, 0x2a, x[0], x[1], y[0], y[1], z[0], z[1] // Mag 3D
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Update acceleration
|
||||
Bangle.on('accel', function(newAcc) {
|
||||
acc = newAcc;
|
||||
isNewAccData = true;
|
||||
|
||||
if(isAccMenu) {
|
||||
accMenu.x.value = acc.x.toFixed(2);
|
||||
|
|
@ -119,6 +227,7 @@ Bangle.on('accel', function(newAcc) {
|
|||
// Update barometer
|
||||
Bangle.on('pressure', function(newBar) {
|
||||
bar = newBar;
|
||||
isNewBarData = true;
|
||||
|
||||
if(isBarMenu) {
|
||||
barMenu.Altitude.value = bar.altitude.toFixed(1) + 'm';
|
||||
|
|
@ -131,6 +240,7 @@ Bangle.on('pressure', function(newBar) {
|
|||
// Update GPS
|
||||
Bangle.on('GPS', function(newGps) {
|
||||
gps = newGps;
|
||||
isNewGpsData = true;
|
||||
|
||||
if(isGpsMenu) {
|
||||
gpsMenu.Lat.value = gps.lat.toFixed(4);
|
||||
|
|
@ -145,6 +255,7 @@ Bangle.on('GPS', function(newGps) {
|
|||
// Update heart rate monitor
|
||||
Bangle.on('HRM', function(newHrm) {
|
||||
hrm = newHrm;
|
||||
isNewHrmData = true;
|
||||
|
||||
if(isHrmMenu) {
|
||||
hrmMenu.BPM.value = hrm.bpm;
|
||||
|
|
@ -156,6 +267,7 @@ Bangle.on('HRM', function(newHrm) {
|
|||
// Update magnetometer
|
||||
Bangle.on('mag', function(newMag) {
|
||||
mag = newMag;
|
||||
isNewMagData = true;
|
||||
|
||||
if(isMagMenu) {
|
||||
magMenu.x.value = mag.x;
|
||||
|
|
@ -169,9 +281,9 @@ Bangle.on('mag', function(newMag) {
|
|||
|
||||
// On start: enable sensors and display main menu
|
||||
g.clear();
|
||||
transmitAppName();
|
||||
Bangle.setBarometerPower(isBarEnabled, APP_ID);
|
||||
Bangle.setGPSPower(isGpsEnabled, APP_ID);
|
||||
Bangle.setHRMPower(isHrmEnabled, APP_ID);
|
||||
Bangle.setCompassPower(isMagEnabled, APP_ID);
|
||||
E.showMenu(mainMenu);
|
||||
E.showMenu(mainMenu);
|
||||
setInterval(transmitUpdatedSensorData, 1000);
|
||||
|
|
@ -39,3 +39,4 @@
|
|||
0.34: Remove Quiet Mode LCD settings: now handled by Quiet Mode Schedule app
|
||||
0.35: Change App/Widget settings to 'App Settings' so it fits on Bangle screen
|
||||
0.36: Added 'Utils' menu with helpful utilities for restoring Bangle.js
|
||||
0.37: Going into passkey menu now saves settings with passkey
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ function resetSettings() {
|
|||
settings = storage.readJSON('setting.json', 1);
|
||||
if (!settings) resetSettings();
|
||||
|
||||
const boolFormat = v => v ? "On" : "Off";
|
||||
const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
|
||||
|
||||
function showMainMenu() {
|
||||
var beepMenuItem;
|
||||
|
|
@ -77,7 +77,7 @@ function showMainMenu() {
|
|||
};
|
||||
} else { // Bangle.js 1
|
||||
var beepV = [false, true, "vib"];
|
||||
var beepN = ["Off", "Piezo", "Vibrate"];
|
||||
var beepN = [/*LANG*/"Off", /*LANG*/"Piezo", /*LANG*/"Vibrate"];
|
||||
beepMenuItem = {
|
||||
value: Math.max(0 | beepV.indexOf(settings.beep),0),
|
||||
min: 0, max: beepV.length-1,
|
||||
|
|
@ -95,10 +95,10 @@ function showMainMenu() {
|
|||
const mainmenu = {
|
||||
'': { 'title': 'Settings' },
|
||||
'< Back': ()=>load(),
|
||||
'App Settings': ()=>showAppSettingsMenu(),
|
||||
'BLE': ()=>showBLEMenu(),
|
||||
'Beep': beepMenuItem,
|
||||
'Vibration': {
|
||||
/*LANG*/'App Settings': ()=>showAppSettingsMenu(),
|
||||
/*LANG*/'BLE': ()=>showBLEMenu(),
|
||||
/*LANG*/'Beep': beepMenuItem,
|
||||
/*LANG*/'Vibration': {
|
||||
value: settings.vibrate,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -110,7 +110,7 @@ function showMainMenu() {
|
|||
}
|
||||
}
|
||||
},
|
||||
"Quiet Mode": {
|
||||
/*LANG*/"Quiet Mode": {
|
||||
value: settings.quiet|0,
|
||||
format: v => ["Off", "Alarms", "Silent"][v%3],
|
||||
onchange: v => {
|
||||
|
|
@ -120,13 +120,13 @@ function showMainMenu() {
|
|||
if ("qmsched" in WIDGETS) WIDGETS["qmsched"].draw();
|
||||
},
|
||||
},
|
||||
'Locale': ()=>showLocaleMenu(),
|
||||
'Select Clock': ()=>showClockMenu(),
|
||||
'Set Time': ()=>showSetTimeMenu(),
|
||||
'LCD': ()=>showLCDMenu(),
|
||||
'Theme': ()=>showThemeMenu(),
|
||||
'Utils': ()=>showUtilMenu(),
|
||||
'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() },
|
||||
/*LANG*/'Locale': ()=>showLocaleMenu(),
|
||||
/*LANG*/'Select Clock': ()=>showClockMenu(),
|
||||
/*LANG*/'Set Time': ()=>showSetTimeMenu(),
|
||||
/*LANG*/'LCD': ()=>showLCDMenu(),
|
||||
/*LANG*/'Theme': ()=>showThemeMenu(),
|
||||
/*LANG*/'Utils': ()=>showUtilMenu(),
|
||||
/*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() },
|
||||
};
|
||||
|
||||
return E.showMenu(mainmenu);
|
||||
|
|
@ -276,8 +276,10 @@ function showPasskeyMenu() {
|
|||
showBLEMenu();
|
||||
}
|
||||
};
|
||||
if (!settings.passkey || settings.passkey.length!=6)
|
||||
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],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
Displays an image. I use this app to show my vaccination certificate.
|
||||
The image is read from the file "showimage.user.img".
|
||||
Returns to watch face after 60s/button push.
|
||||
|
|
@ -0,0 +1 @@
|
|||
E.toArrayBuffer(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAf////AHP/////AH//////AP/8AAAHAP4AAAAHAOAAMAAHAOAAeAAHAOAA+cAHAOAA/+AHAOAA/+AHAOAf/+AHAOA//+AHgOA///AHgOA///gDgOA/z/gDgOAfz/gDgOA///gDgOA///gDgOA//uADgOAf3+ADgOAP/+ADgOAD/8ADwOAB+4ADwOAA8AABwOAAAABBwOA8DgPxwOB/Dg/xwOB/jh/xwOB3zj5xwOB57nzxwOA4/njhwOA4/vHhwOA8f+PB4OAef+PB4OAef8eB4OAPP58A4OAHv/4A4OAH//wA4OAD//AA4OAA/8fn4PDgP///4P//////4P////9/wD///4AAA"))
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
g.reset();
|
||||
g.clear();
|
||||
g.drawImage(require("Storage").read("showimg.user.img"),0,0);
|
||||
drawTimeout = setTimeout(function() {
|
||||
load();
|
||||
}, 60000);
|
||||
setWatch(function() {
|
||||
load();
|
||||
}, BTN, { repeat:false, edge:'falling' });
|
||||
var savedOptions=Bangle.getOptions();
|
||||
Bangle.setLCDBrightness(1);
|
||||
var newOptions={
|
||||
lockTimeout:60000,
|
||||
backlightTimeout:60000
|
||||
};
|
||||
Bangle.setOptions(newOptions);
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
|
@ -0,0 +1,2 @@
|
|||
0.01: First release
|
||||
0.02: Fixed snek.png and snek.icon.js to 64x64 to display in launcher, added screenshots, updated apps.json
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -1 +0,0 @@
|
|||
require("heatshrink").decompress(atob("pFIwkB+MRAEH/EUIA7i93u9xEUIABEf4AC+93/4CBETpDBv//gEHEf6MB9wkB/8HSDl3vwjCAAfnErIjiEQYdBAAXuAoSNXEYIdDAAoj4j/3DpN3v6NWAA3/fDYjgRgIjLu9xESj2BAAN/SQwLBEe4XDdwghDBQQjSCgN+C5D9FEebTDEZJEWEQSDVEdZpDZYPnETYhDAAhpeEbzREI0rTbEdXuETb4Bvz1BAYIj/EYxrg9yQDv/3JoS9WEcoaGAAQtBOwYABEaSMBWoYeFJKgjjiIUD9ySEEjwAJFogj0SQgAFBQ4jRABcfXoQj/TowjCOgIkeEf7lHvz+CEb93Ef4jHR8Rr/fY4jCuIifAAQj/EIojbeohGeEcQhHfLRFDeoIicEcQbDv3uEYiNXIgnu87SgEcf/DKnxBJEf/4ACESf/EcYA=="))
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("oFA4X/AAOJksvr2rmokYgWqB7sq/2AB5krgYPMgW8ioPc1X9i/oLplVqv+1BdK1OV//q9QPMv4PL1eqy/q1SRK3tVu+AgWCFxP96t+Vhn9qoPLgWr/+//wFBSBEq3/qlW+JwJ/I3eXDQIOBB5OrB5sC3xMD1WAH4+r6xsOtSpKLoYPN1fV1bpKTYf+RJAeDytXFxoPOdQYPNPpkCy1VtQPc6wvO62Vu+CbhfVN4P//+q//uMgwPH9QPH3tqqtpqoABv4wHfoOpBoP/6tVUg7uBFwIvB3xlIB4v+OpJsC1WA1fVQpiGCB52+uzlMB58A31XB5sqy4PNlYPfH50rywPN3++BxgPPgW9V5kCZ4L/HBwmq/tX1APM/4PMBwNVvxuKgW/tP/HxUq1X+1eqFxQPRAAKsLB4KqNAFY="))
|
||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 2.7 KiB |
|
|
@ -1,2 +1,3 @@
|
|||
0.02: New App!
|
||||
0.03: Improved messages and added Celsius sign
|
||||
0.04: Make temperature value readable on smaller screens
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ function onTemperature(p) {
|
|||
var x = g.getWidth()/2;
|
||||
var y = g.getHeight()/2 + 10;
|
||||
g.drawString("Temperature:", x, y - 45);
|
||||
g.setFontVector(70).setFontAlign(0,0);
|
||||
g.setFontVector(g.getWidth() > 200 ? 70 : 40).setFontAlign(0,0);
|
||||
g.drawString(p.temperature.toFixed(1) + " °C", x, y);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/nodejs
|
||||
/* Scans for strings that may be in English in each app, and
|
||||
outputs a list of strings that have been found.
|
||||
|
||||
Early work towards internationalisation.
|
||||
See https://github.com/espruino/BangleApps/issues/136
|
||||
*/
|
||||
|
||||
var BASEDIR = __dirname+"/../";
|
||||
Espruino = require(BASEDIR+"core/lib/espruinotools.js");
|
||||
var fs = require("fs");
|
||||
|
||||
var APPSDIR = BASEDIR+"apps/";
|
||||
function ERROR(s) {
|
||||
console.error("ERROR: "+s);
|
||||
process.exit(1);
|
||||
}
|
||||
function WARN(s) {
|
||||
console.log("Warning: "+s);
|
||||
}
|
||||
|
||||
var appsFile, apps;
|
||||
try {
|
||||
appsFile = fs.readFileSync(BASEDIR+"apps.json").toString();
|
||||
} catch (e) {
|
||||
ERROR("apps.json not found");
|
||||
}
|
||||
try{
|
||||
apps = JSON.parse(appsFile);
|
||||
} catch (e) {
|
||||
ERROR("apps.json not valid JSON");
|
||||
}
|
||||
|
||||
// Given a string value, work out if it's obviously not a text string
|
||||
function isNotString(s) {
|
||||
if (s.length<2) return true; // too short
|
||||
if (s.length>40) return true; // too long
|
||||
if (s[0]=="#") return true; // a color
|
||||
if (s.endsWith(".json") || s.endsWith(".img")) return true; // a filename
|
||||
if (s.endsWith("=")) return true; // probably base64
|
||||
if (s.startsWith("BTN")) return true; // button name
|
||||
return false;
|
||||
}
|
||||
|
||||
var textStrings = [];
|
||||
|
||||
console.log("Scanning...");
|
||||
apps.forEach((app,appIdx) => {
|
||||
var appDir = APPSDIR+app.id+"/";
|
||||
app.storage.forEach((file) => {
|
||||
if (!file.url || !file.name.endsWith(".js")) return;
|
||||
var fileContents = fs.readFileSync(appDir+file.url).toString();
|
||||
var lex = Espruino.Core.Utils.getLexer(fileContents);
|
||||
var tok = lex.next();
|
||||
while (tok!==undefined) {
|
||||
if (tok.type=="STRING") {
|
||||
if (!isNotString(tok.value)) {
|
||||
//console.log(tok.str);
|
||||
if (!textStrings.includes(tok.value))
|
||||
textStrings.push(tok.value);
|
||||
}
|
||||
}
|
||||
tok = lex.next();
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log("Done");
|
||||
textStrings.sort();
|
||||
console.log(textStrings.join("\n"));
|
||||
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit 23854083e0c3f83c649073a2d85e8079efc471d3
|
||||
Subproject commit 2a8e872ecb143a10e53273b4d3473164e104e1d3
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"//":"German language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarm" : "Wecker",
|
||||
"Hours" : "Stunden",
|
||||
"Minutes" : "Minuten",
|
||||
"Enabled" : "Aktiviert",
|
||||
"Settings" : "Einstellungen",
|
||||
"Save" : "Speichern",
|
||||
"Back" : "Zurück",
|
||||
"Repeat" : "Wiederholen",
|
||||
"Delete" : "Löschen",
|
||||
"Sleep" : "Schlummern",
|
||||
"Alarms" : "Wecker",
|
||||
"New Alarm" : "Neuer Wecker",
|
||||
"ALARM!" : "ALARM!"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "Wdh."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"//":"British English language translations - the default strings in apps are all english anyway, so no need to have translations for most things",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"//":"Spanish language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Alarmas",
|
||||
"Hours" : "Horas",
|
||||
"Minutes" : "Minutos",
|
||||
"Enabled" : "Activados",
|
||||
"New Alarm" : "Alarma nueva",
|
||||
"Save" : "Grabar",
|
||||
"Back" : "Atrás",
|
||||
"Repeat" : "Repetición",
|
||||
"Delete" : "Borrar",
|
||||
"ALARM!" : "ALARM",
|
||||
"Sleep" : "Dormir"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "rep."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"//":"Finnish language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Hälytykset",
|
||||
"Hours" : "Tunnit",
|
||||
"Minutes" : "Minuutit",
|
||||
"Enabled" : "Aktivoitu",
|
||||
"New Alarm" : "Uusi hälytys",
|
||||
"Save" : "Tallenna",
|
||||
"Back" : "Paluu",
|
||||
"Repeat" : "Toista",
|
||||
"Delete" : "Poista",
|
||||
"ALARM!" : "ALARM",
|
||||
"Sleep" : "Nukkuminen"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "toistaa"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"//":"French language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Réveils",
|
||||
"Hours" : "Heures",
|
||||
"Minutes" : "Minutes",
|
||||
"Enabled" : "Activé",
|
||||
"New Alarm" : "Nouveau Réveil",
|
||||
"Save" : "Sauvegarder",
|
||||
"Back" : "Retour",
|
||||
"Repeat" : "Répétition",
|
||||
"Delete" : "Supprimer",
|
||||
"ALARM!" : "ALARM!",
|
||||
"Sleep" : "Sommeil"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "rép."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"//":"Spanish language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Riasztások",
|
||||
"Hours" : "Óra",
|
||||
"Minutes" : "Perc",
|
||||
"Enabled" : "Aktiválva",
|
||||
"New Alarm" : "Új riasztás",
|
||||
"Save" : "Mentés",
|
||||
"Back" : "Vissza",
|
||||
"Repeat" : "Ismétlés",
|
||||
"Delete" : "Törlés",
|
||||
"ALARM!" : "ALARM!",
|
||||
"Sleep" : "Alvás"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "ismétlés"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{"code":"en_GB","name":"British English","url":"en_GB.json"},
|
||||
{"code":"de_DE","name":"German","url":"de_DE.json"},
|
||||
{"code":"es_ES","name":"Spanish","url":"es_ES.json"},
|
||||
{"code":"fi_FI","name":"Finnish","url":"fi_FI.json"},
|
||||
{"code":"fr_FR","name":"French","url":"fr_FR.json"},
|
||||
{"code":"hu_HU","name":"Hungarian","url":"hu_HU.json"},
|
||||
{"code":"it_IT","name":"Italian","url":"it_IT.json"},
|
||||
{"code":"nl_NL","name":"Dutch","url":"nl_NL.json"},
|
||||
{"code":"sv_SE","name":"Swedish","url":"sv_SE.json"},
|
||||
{"code":"tr_TR","name":"Turkish","url":"tr_TR.json"}
|
||||
]
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"//":"Italian language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Allarmi",
|
||||
"Hours" : "Ore",
|
||||
"Minutes" : "Minuti",
|
||||
"Enabled" : "Attivato",
|
||||
"New Alarm" : "Nuovo allarme",
|
||||
"Save" : "Salvare",
|
||||
"Back" : "Indietro",
|
||||
"Repeat" : "Ripetere",
|
||||
"Delete" : "Cancellare",
|
||||
"ALARM!" : "ALARM!",
|
||||
"Sleep" : "Dormire"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "ripetere"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"//":"Dutch language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Alarmen",
|
||||
"Hours" : "Uren",
|
||||
"Minutes" : "Minuten",
|
||||
"Enabled" : "Geactiveerd",
|
||||
"New Alarm" : "Nieuw alarm",
|
||||
"Save" : "Opslaan",
|
||||
"Back" : "Terug",
|
||||
"Repeat" : "Herhalen",
|
||||
"Delete" : "Verwijderen",
|
||||
"ALARM!" : "ALARV.",
|
||||
"Sleep" : "Stand-by"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "herhalen"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"//":"Swedish language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Larm",
|
||||
"Hours" : "Timmar",
|
||||
"Minutes" : "Minuter",
|
||||
"Enabled" : "Aktiverad",
|
||||
"New Alarm" : "Ny alarm",
|
||||
"Save" : "Spara",
|
||||
"Back" : "Tillbaka",
|
||||
"Repeat" : "Upprepning",
|
||||
"Delete" : "Radera",
|
||||
"ALARM!" : "ALURH!",
|
||||
"Sleep" : "Sömn"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "uppr."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"//":"Turkish language translations",
|
||||
"GLOBAL": {
|
||||
"//":"Translations that apply for all apps",
|
||||
"Alarms" : "Alarmlar",
|
||||
"Hours" : "Saat",
|
||||
"Minutes" : "Dakika",
|
||||
"Enabled" : "Etkinleştirildi",
|
||||
"New Alarm" : "Yeni alarm",
|
||||
"Save" : "Sakla",
|
||||
"Back" : "Geriye",
|
||||
"Repeat" : "Yineleme",
|
||||
"Delete" : "Sil",
|
||||
"ALARM!" : "ALARM!",
|
||||
"Sleep" : "Uyku"
|
||||
},
|
||||
"alarm": {
|
||||
"//":"App-specific overrides",
|
||||
"rpt" : "yineleme"
|
||||
}
|
||||
}
|
||||