Merge branch 'espruino:master' into master
126
apps.json
|
@ -16,7 +16,7 @@
|
||||||
{
|
{
|
||||||
"id": "boot",
|
"id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"version": "0.39",
|
"version": "0.40",
|
||||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||||
"icon": "bootloader.png",
|
"icon": "bootloader.png",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
{
|
{
|
||||||
"id": "messages",
|
"id": "messages",
|
||||||
"name": "Messages",
|
"name": "Messages",
|
||||||
"version": "0.16",
|
"version": "0.17",
|
||||||
"description": "App to display notifications from iOS and Gadgetbridge",
|
"description": "App to display notifications from iOS and Gadgetbridge",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
{
|
{
|
||||||
"id": "ios",
|
"id": "ios",
|
||||||
"name": "iOS Integration",
|
"name": "iOS Integration",
|
||||||
"version": "0.07",
|
"version": "0.08",
|
||||||
"description": "Display notifications/music/etc from iOS devices",
|
"description": "Display notifications/music/etc from iOS devices",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,ios,apple,messages,notifications",
|
"tags": "tool,system,ios,apple,messages,notifications",
|
||||||
|
@ -167,7 +167,7 @@
|
||||||
{
|
{
|
||||||
"id": "setting",
|
"id": "setting",
|
||||||
"name": "Settings",
|
"name": "Settings",
|
||||||
"version": "0.39",
|
"version": "0.40",
|
||||||
"description": "A menu for setting up Bangle.js",
|
"description": "A menu for setting up Bangle.js",
|
||||||
"icon": "settings.png",
|
"icon": "settings.png",
|
||||||
"tags": "tool,system",
|
"tags": "tool,system",
|
||||||
|
@ -845,7 +845,7 @@
|
||||||
{
|
{
|
||||||
"id": "weather",
|
"id": "weather",
|
||||||
"name": "Weather",
|
"name": "Weather",
|
||||||
"version": "0.14",
|
"version": "0.15",
|
||||||
"description": "Show Gadgetbridge weather report",
|
"description": "Show Gadgetbridge weather report",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
@ -936,7 +936,7 @@
|
||||||
"id": "widbatpc",
|
"id": "widbatpc",
|
||||||
"name": "Battery Level Widget (with percentage)",
|
"name": "Battery Level Widget (with percentage)",
|
||||||
"shortName": "Battery Widget",
|
"shortName": "Battery Widget",
|
||||||
"version": "0.14",
|
"version": "0.15",
|
||||||
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
|
@ -1324,7 +1324,7 @@
|
||||||
"icon": "gesture.png",
|
"icon": "gesture.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "gesture,ai",
|
"tags": "gesture,ai",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"gesture.app.js","url":"gesture.js"},
|
{"name":"gesture.app.js","url":"gesture.js"},
|
||||||
{"name":".tfnames","url":"gesture-tfnames.js","evaluate":true},
|
{"name":".tfnames","url":"gesture-tfnames.js","evaluate":true},
|
||||||
|
@ -1749,8 +1749,9 @@
|
||||||
"icon": "grocery.png",
|
"icon": "grocery.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "tool,outdoors,shopping,list",
|
"tags": "tool,outdoors,shopping,list",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"custom": "grocery.html",
|
"custom": "grocery.html",
|
||||||
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"grocery.app.js","url":"app.js"},
|
{"name":"grocery.app.js","url":"app.js"},
|
||||||
{"name":"grocery.img","url":"grocery-icon.js","evaluate":true}
|
{"name":"grocery.img","url":"grocery-icon.js","evaluate":true}
|
||||||
|
@ -2970,11 +2971,11 @@
|
||||||
{
|
{
|
||||||
"id": "cprassist",
|
"id": "cprassist",
|
||||||
"name": "CPR Assist",
|
"name": "CPR Assist",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Provides assistance while performing a CPR",
|
"description": "Provides assistance while performing a CPR",
|
||||||
"icon": "cprassist-icon.png",
|
"icon": "cprassist-icon.png",
|
||||||
"tags": "tool,firstaid",
|
"tags": "tool,firstaid",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"screenshots": [{"url":"bangle1-CPR-assist-screenshot.png"}],
|
"screenshots": [{"url":"bangle1-CPR-assist-screenshot.png"}],
|
||||||
|
@ -3532,7 +3533,7 @@
|
||||||
"id": "mclockplus",
|
"id": "mclockplus",
|
||||||
"name": "Morph Clock+",
|
"name": "Morph Clock+",
|
||||||
"shortName": "Morph Clock+",
|
"shortName": "Morph Clock+",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "Morphing Clock with more readable seconds and date and additional stopwatch",
|
"description": "Morphing Clock with more readable seconds and date and additional stopwatch",
|
||||||
"icon": "mclockplus.png",
|
"icon": "mclockplus.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
@ -3986,11 +3987,12 @@
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"doztime.app.js","url":"app.js"},
|
{"name":"doztime.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]},
|
||||||
|
{"name":"doztime.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]},
|
||||||
{"name":"doztime.img","url":"app-icon.js","evaluate":true}
|
{"name":"doztime.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -4210,13 +4212,13 @@
|
||||||
"id": "pastel",
|
"id": "pastel",
|
||||||
"name": "Pastel Clock",
|
"name": "Pastel Clock",
|
||||||
"shortName": "Pastel",
|
"shortName": "Pastel",
|
||||||
"version": "0.09",
|
"version": "0.10",
|
||||||
"description": "A Configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
|
"description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
|
||||||
"icon": "pastel.png",
|
"icon": "pastel.png",
|
||||||
"dependencies": {"mylocation":"app", "widpedom":"app"},
|
"dependencies": {"mylocation":"app", "widpedom":"app","weather":"app"},
|
||||||
"screenshots": [{"url":"screenshot_pastel.png"}],
|
"screenshots": [{"url":"screenshot_pastel.png"}, {"url":"weather_icons.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock, weather, tool",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
@ -4382,7 +4384,7 @@
|
||||||
{
|
{
|
||||||
"id": "gpstouch",
|
"id": "gpstouch",
|
||||||
"name": "GPS Touch",
|
"name": "GPS Touch",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "A touch based GPS watch, shows OS map reference",
|
"description": "A touch based GPS watch, shows OS map reference",
|
||||||
"icon": "gpstouch.png",
|
"icon": "gpstouch.png",
|
||||||
"screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}],
|
"screenshots": [{"url":"screenshot4.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot1.png"}],
|
||||||
|
@ -4487,7 +4489,7 @@
|
||||||
"name": "LCARS Clock",
|
"name": "LCARS Clock",
|
||||||
"shortName":"LCARS",
|
"shortName":"LCARS",
|
||||||
"icon": "lcars.png",
|
"icon": "lcars.png",
|
||||||
"version":"0.08",
|
"version":"0.09",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||||
|
@ -4621,7 +4623,7 @@
|
||||||
"shortName":"93 Dub",
|
"shortName":"93 Dub",
|
||||||
"icon": "93dub.png",
|
"icon": "93dub.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"version":"0.05",
|
"version":"0.06",
|
||||||
"description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo",
|
"description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
@ -4710,7 +4712,7 @@
|
||||||
"icon": "mylocation.png",
|
"icon": "mylocation.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"screenshots": [{"url":"screenshot_1.png"}],
|
"screenshots": [{"url":"screenshot_1.png"}],
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
|
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"tags": "tool,utility",
|
"tags": "tool,utility",
|
||||||
|
@ -4727,7 +4729,7 @@
|
||||||
"id": "pebble",
|
"id": "pebble",
|
||||||
"name": "Pebble Clock",
|
"name": "Pebble Clock",
|
||||||
"shortName": "Pebble",
|
"shortName": "Pebble",
|
||||||
"version": "0.06",
|
"version": "0.07",
|
||||||
"description": "A pebble style clock to keep the rebellion going",
|
"description": "A pebble style clock to keep the rebellion going",
|
||||||
"dependencies": {"widpedom":"app"},
|
"dependencies": {"widpedom":"app"},
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
@ -4735,7 +4737,7 @@
|
||||||
"screenshots": [{"url":"pebble_screenshot.png"}],
|
"screenshots": [{"url":"pebble_screenshot.png"}],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"pebble.app.js","url":"pebble.app.js"},
|
{"name":"pebble.app.js","url":"pebble.app.js"},
|
||||||
{"name":"pebble.settings.js","url":"pebble.settings.js"},
|
{"name":"pebble.settings.js","url":"pebble.settings.js"},
|
||||||
|
@ -4769,7 +4771,7 @@
|
||||||
"screenshots": [{"url":"screenshot_widbata_1.png"}],
|
"screenshots": [{"url":"screenshot_widbata_1.png"}],
|
||||||
"version":"0.01",
|
"version":"0.01",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"description": "Shows the current battery level status in the top right using the clocks colour theme",
|
"description": "Shows the current battery level status in the top right using the clocks colour theme",
|
||||||
"tags": "widget,battery",
|
"tags": "widget,battery",
|
||||||
|
@ -4914,7 +4916,7 @@
|
||||||
"id": "rebble",
|
"id": "rebble",
|
||||||
"name": "Rebble Clock",
|
"name": "Rebble Clock",
|
||||||
"shortName": "Rebble",
|
"shortName": "Rebble",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
|
"description": "A Pebble style clock, with configurable background, three sidebars including steps, day, date, sunrise, sunset, long live the rebellion",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "rebble.png",
|
"icon": "rebble.png",
|
||||||
|
@ -5099,7 +5101,7 @@
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"contourclock.app.js","url":"app.js"},
|
{"name":"contourclock.app.js","url":"app.js"},
|
||||||
{"name":"contourclock.img","url":"app-icon.js","evaluate":true}
|
{"name":"contourclock.img","url":"app-icon.js","evaluate":true}
|
||||||
|
@ -5121,6 +5123,24 @@
|
||||||
{"name":"ltherm.img","url":"icon.js","evaluate":true}
|
{"name":"ltherm.img","url":"icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "presentor",
|
||||||
|
"name": "Presentor",
|
||||||
|
"version": "3.0",
|
||||||
|
"description": "Use your Bangle to present!",
|
||||||
|
"icon": "app.png",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "tool,bluetooth",
|
||||||
|
"interface": "interface.html",
|
||||||
|
"readme":"README.md",
|
||||||
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"presentor.app.js","url":"app.js"},
|
||||||
|
{"name":"presentor.img","url":"app-icon.js","evaluate":true},
|
||||||
|
{"name":"presentor.json","url":"settings.json"}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "slash",
|
"id": "slash",
|
||||||
"name": "Slash Watch",
|
"name": "Slash Watch",
|
||||||
|
@ -5335,7 +5355,7 @@
|
||||||
"icon": "andark_icon.png",
|
"icon": "andark_icon.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"andark.app.js","url":"app.js"},
|
{"name":"andark.app.js","url":"app.js"},
|
||||||
|
@ -5358,5 +5378,55 @@
|
||||||
{ "name": "diract.app.js", "url": "diract.js" },
|
{ "name": "diract.app.js", "url": "diract.js" },
|
||||||
{ "name": "diract.img", "url": "diract-icon.js", "evaluate": true }
|
{ "name": "diract.img", "url": "diract-icon.js", "evaluate": true }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sonicclk",
|
||||||
|
"name": "Sonic Clock",
|
||||||
|
"version": "1.01",
|
||||||
|
"description": "A classic sonic clock featuring run, stop and wait animations.",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"sonicclk.app.js","url":"app.js"},
|
||||||
|
{"name":"sonicclk.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "touchmenu",
|
||||||
|
"name": "TouchMenu",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Redesigned menu that uses the full touchscreen on the Bangle.js 2",
|
||||||
|
"screenshots": [{"url":"touchmenu.gif"}],
|
||||||
|
"icon": "touchmenu.png",
|
||||||
|
"type": "bootloader",
|
||||||
|
"tags": "tool",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"touchmenu.boot.js","url":"touchmenu.boot.js"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "puzzle15",
|
||||||
|
"name": "15 puzzle",
|
||||||
|
"version": "0.05",
|
||||||
|
"description": "A 15 puzzle game with drag gesture interface",
|
||||||
|
"readme":"README.md",
|
||||||
|
"icon": "puzzle15.app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "app",
|
||||||
|
"tags": "game",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"puzzle15.app.js","url":"puzzle15.app.js"},
|
||||||
|
{"name":"puzzle15.settings.js","url":"puzzle15.settings.js"},
|
||||||
|
{"name":"puzzle15.img","url":"puzzle15.app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [{"name":"puzzle15.json"}]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Code style cleanup
|
0.03: Code style cleanup
|
||||||
0.04: Set 00:00 to 12:00 for 12 hour time
|
0.04: Set 00:00 to 12:00 for 12 hour time
|
||||||
0.05: Display time, even on Thursday
|
0.05: Display time, even on Thursday
|
||||||
|
0.06: Fix light theme issue, where widgets would end up on a light strip
|
||||||
|
|
|
@ -122,7 +122,13 @@ function draw(){
|
||||||
queueDraw();
|
queueDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This watch is mostly dark, it does not make sense to respect the
|
||||||
|
* light theme as you end up with a white strip at the top for the
|
||||||
|
* widgets and black watch. So set the colours to the dark theme.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
//the following section is also from waveclk
|
//the following section is also from waveclk
|
||||||
|
|
|
@ -43,3 +43,4 @@
|
||||||
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
|
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.38: Option to log to file if settings.log==2
|
||||||
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
|
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
|
||||||
|
0.40: Bootloader now rebuilds for new firmware versions
|
||||||
|
|
|
@ -6,11 +6,11 @@ var s = require('Storage').readJSON('setting.json',1)||{};
|
||||||
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||||
var boot = "";
|
var boot = "";
|
||||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/);
|
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)!=${CRC})`;
|
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||||
} else {
|
} else {
|
||||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
|
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))!=${CRC})`;
|
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||||
}
|
}
|
||||||
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
||||||
boot += `E.setFlags({pretokenise:1});\n`;
|
boot += `E.setFlags({pretokenise:1});\n`;
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Ported to Banglejs2
|
||||||
|
|
|
@ -35,23 +35,24 @@ function provideFeedback() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawHeart() {
|
function drawHeart() {
|
||||||
g.fillCircle(40, 92, 12);
|
var lowestPoint = g.getHeight()*3/5;
|
||||||
g.fillCircle(60, 92, 12);
|
g.fillCircle(40, lowestPoint-29, 12);
|
||||||
g.fillPoly([29, 98, 50, 120, 71, 98]);
|
g.fillCircle(60, lowestPoint-29, 12);
|
||||||
|
g.fillPoly([29, lowestPoint-22, 50, lowestPoint, 71, lowestPoint-22]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateScreen() {
|
function updateScreen() {
|
||||||
const colors = [0xFFFF, 0x9492];
|
const colors = [0xFFFF-g.getBgColor(), 0x9492];
|
||||||
g.reset().clearRect(0, 50, 250, 150);
|
g.reset().clearRect(0, 24, g.getWidth(), g.getHeight()*5/6);
|
||||||
if (counter > 0) {
|
if (counter > 0) {
|
||||||
g.setFont("Vector", 40).setFontAlign(0, 0);
|
g.setFont("Vector", 40).setFontAlign(0, 0);
|
||||||
g.setColor(colors[counter%2]);
|
g.setColor(colors[counter%2]);
|
||||||
drawHeart();
|
drawHeart();
|
||||||
g.drawString(counter + "", g.getWidth()/2, 100);
|
g.drawString(counter, 120, g.getHeight()*3/5-20);
|
||||||
} else {
|
} else {
|
||||||
g.setFont("Vector", 20).setFontAlign(0, 0);
|
g.setFont("Vector", 20).setFontAlign(0, 0);
|
||||||
g.drawString("RESCUE", g.getWidth()/2, 70);
|
g.drawString("RESCUE", g.getWidth()/2, g.getHeight()/3);
|
||||||
g.drawString("BREATHS", g.getWidth()/2, 120);
|
g.drawString("BREATHS", g.getWidth()/2, g.getHeight()*3/5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ function tick() {
|
||||||
interval = setInterval(tick, 60000/setting('compression_rpm'));
|
interval = setInterval(tick, 60000/setting('compression_rpm'));
|
||||||
|
|
||||||
g.clear(1).setFont("6x8");
|
g.clear(1).setFont("6x8");
|
||||||
g.drawString(setting('compression_count') + ' / ' + setting('breath_count'), 30, 200);
|
g.drawString(setting('compression_count') + ' / ' + setting('breath_count'), 30, g.getHeight()*5/6);
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
// Positioning values for graphics buffers
|
||||||
|
const g_height = 80; // total graphics height
|
||||||
|
const g_x_off = 0; // position from left was 16, then 8 here
|
||||||
|
const g_y_off = (184 - g_height)/2; // vertical center for graphics region was 240
|
||||||
|
const g_width = 240 - 2 * g_x_off; // total graphics width
|
||||||
|
const g_height_d = 28; // height of date region was 32
|
||||||
|
const g_y_off_d = 0; // y position of date region within graphics region
|
||||||
|
const spacing = 0; // space between date and time in graphics region
|
||||||
|
const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region
|
||||||
|
const g_height_t = 44; // height of time region was 48
|
||||||
|
|
||||||
|
// Other vars
|
||||||
|
const A1 = [30,30,30,30,31,31,31,31,31,31,30,30];
|
||||||
|
const B1 = [30,30,30,30,30,31,31,31,31,31,30,30];
|
||||||
|
const B2 = [30,30,30,30,31,31,31,31,31,30,30,30];
|
||||||
|
const timeColour = "#ffffff";
|
||||||
|
const dateColours = ["#ff0000","#ff8000","#ffff00","#00ff00","#0080ff","#ff00ff","#ffffff"];
|
||||||
|
const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line ft w 32, 32-g, step 20
|
||||||
|
const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line ft w 32, 62-g, step 20
|
||||||
|
const time5 = {"size":36,"pt0":[46-g_x_off,24],"step":[22,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line ft w 48, 64-g, step 30
|
||||||
|
const time6 = {"size":36,"pt0":[36-g_x_off,24],"step":[22,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line ft w 48, 48-g, step 30
|
||||||
|
const baseYear = 11584;
|
||||||
|
const baseDate = Date(2020,11,21); // month values run from 0 to 11
|
||||||
|
let accum = new Date(baseDate.getTime());
|
||||||
|
let sequence = [];
|
||||||
|
let timeActiveUntil;
|
||||||
|
let addTimeDigit = false;
|
||||||
|
let dateFormat = false;
|
||||||
|
let lastX = 999999999;
|
||||||
|
let res = {};
|
||||||
|
//var last_time_log = 0;
|
||||||
|
|
||||||
|
var drawtime_timeout;
|
||||||
|
|
||||||
|
// Date and time graphics buffers
|
||||||
|
var dateColour = "#ffffff"; // override later
|
||||||
|
var timeColour2 = timeColour;
|
||||||
|
var g_d = Graphics.createArrayBuffer(g_width,g_height_d,1,{'msb':true});
|
||||||
|
var g_t = Graphics.createArrayBuffer(g_width,g_height_t,1,{'msb':true});
|
||||||
|
// Set screen mode and function to write graphics buffers
|
||||||
|
//Bangle.setLCDMode();
|
||||||
|
g.clear(); // start with blank screen
|
||||||
|
g.flip = function()
|
||||||
|
{
|
||||||
|
g.setBgColor(0,0,0);
|
||||||
|
g.setColor(dateColour);
|
||||||
|
g.drawImage(
|
||||||
|
{
|
||||||
|
width:g_width,
|
||||||
|
height:g_height_d,
|
||||||
|
buffer:g_d.buffer
|
||||||
|
}, g_x_off, g_y_off + g_y_off_d);
|
||||||
|
g.setColor(timeColour2);
|
||||||
|
g.drawImage(
|
||||||
|
{
|
||||||
|
width:g_width,
|
||||||
|
height:g_height_t,
|
||||||
|
buffer:g_t.buffer
|
||||||
|
}, g_x_off, g_y_off + g_y_off_t);
|
||||||
|
};
|
||||||
|
|
||||||
|
setWatch(function(){ modeTime(); }, BTN, {repeat:true} ); //was BTN1
|
||||||
|
setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" }); //was BTN2
|
||||||
|
//setWatch(function(){ modeWeather(); }, BTN3, {repeat:true});
|
||||||
|
//setWatch(function(){ toggleTimeDigits(); }, BTN4, {repeat:true});
|
||||||
|
//setWatch(function(){ toggleDateFormat(); }, BTN5, {repeat:true});
|
||||||
|
|
||||||
|
Bangle.on('touch', function(button, xy) { //from Gordon Williams
|
||||||
|
if (button==1) toggleTimeDigits();
|
||||||
|
if (button==2) toggleDateFormat();
|
||||||
|
});
|
||||||
|
|
||||||
|
function buildSequence(targ){
|
||||||
|
for(let i=0;i<targ.length;++i){
|
||||||
|
sequence.push(new Date(accum.getTime()));
|
||||||
|
accum.setDate(accum.getDate()+targ[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildSequence(B2);
|
||||||
|
buildSequence(B2);
|
||||||
|
buildSequence(A1);
|
||||||
|
buildSequence(B1);
|
||||||
|
buildSequence(B2);
|
||||||
|
buildSequence(B2);
|
||||||
|
buildSequence(A1);
|
||||||
|
buildSequence(B1);
|
||||||
|
buildSequence(B2);
|
||||||
|
buildSequence(B2);
|
||||||
|
buildSequence(A1);
|
||||||
|
buildSequence(B1);
|
||||||
|
buildSequence(B2);
|
||||||
|
|
||||||
|
function getDate(dt){
|
||||||
|
let index = sequence.findIndex(n => n > dt)-1;
|
||||||
|
let year = baseYear+parseInt(index/12);
|
||||||
|
let month = index % 12;
|
||||||
|
let day = parseInt((dt-sequence[index])/86400000);
|
||||||
|
let colour = dateColours[day % 6];
|
||||||
|
if(day==30){ colour=dateColours[6]; }
|
||||||
|
return({"year":year,"month":month,"day":day,"colour":colour});
|
||||||
|
}
|
||||||
|
function toggleTimeDigits(){
|
||||||
|
addTimeDigit = !addTimeDigit;
|
||||||
|
modeTime();
|
||||||
|
}
|
||||||
|
function toggleDateFormat(){
|
||||||
|
dateFormat = !dateFormat;
|
||||||
|
modeTime();
|
||||||
|
}
|
||||||
|
function formatDate(res,dateFormat){
|
||||||
|
let yyyy = res.year.toString(12);
|
||||||
|
calenDef = calen10;
|
||||||
|
if(!dateFormat){ //ordinal format
|
||||||
|
let mm = ("0"+(res.month+1).toString(12)).substr(-2);
|
||||||
|
let dd = ("0"+(res.day+1).toString(12)).substr(-2);
|
||||||
|
if(res.day==30){
|
||||||
|
calenDef = calen7;
|
||||||
|
let m = ((res.month+1).toString(12)).substr(-2);
|
||||||
|
return(yyyy+"-"+"S"+m); // ordinal format
|
||||||
|
}
|
||||||
|
return(yyyy+"-"+mm+"-"+dd);
|
||||||
|
}
|
||||||
|
let m = res.month.toString(12); // cardinal format
|
||||||
|
let w = parseInt(res.day/6);
|
||||||
|
let d = res.day%6;
|
||||||
|
//return(yyyy+"-"+res.month+"-"+w+"-"+d);
|
||||||
|
return(yyyy+"-"+m+"-"+w+"-"+d);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeDozTime(text,def){
|
||||||
|
let pts = def.pts;
|
||||||
|
let x=def.pt0[0];
|
||||||
|
let y=def.pt0[1];
|
||||||
|
g_t.clear();
|
||||||
|
g_t.setFont("Vector",def.size);
|
||||||
|
for(let i in text){
|
||||||
|
if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s are new
|
||||||
|
else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s are new
|
||||||
|
else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); }
|
||||||
|
x = x+def.step[0];
|
||||||
|
y = y+def.step[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function writeDozDate(text,def,colour){
|
||||||
|
|
||||||
|
dateColour = colour;
|
||||||
|
let pts = def.pts;
|
||||||
|
let x=def.pt0[0];
|
||||||
|
let y=def.pt0[1];
|
||||||
|
g_d.clear();
|
||||||
|
g_d.setFont("Vector",def.size);
|
||||||
|
for(let i in text){
|
||||||
|
if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s new
|
||||||
|
else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s new
|
||||||
|
else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); }
|
||||||
|
x = x+def.step[0];
|
||||||
|
y = y+def.step[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions for time mode
|
||||||
|
function drawTime()
|
||||||
|
{
|
||||||
|
let dt = new Date();
|
||||||
|
let date = "";
|
||||||
|
let timeDef;
|
||||||
|
let x = 0;
|
||||||
|
dt.setDate(dt.getDate());
|
||||||
|
if(addTimeDigit){
|
||||||
|
x =
|
||||||
|
10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds();
|
||||||
|
let msg = "00000"+Math.floor(x).toString(12);
|
||||||
|
let time = msg.substr(-5,3)+"."+msg.substr(-2);
|
||||||
|
let wait = 347*(1-(x%1));
|
||||||
|
timeDef = time6;
|
||||||
|
} else {
|
||||||
|
x =
|
||||||
|
864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds();
|
||||||
|
let msg = "0000"+Math.floor(x).toString(12);
|
||||||
|
let time = msg.substr(-4,3)+"."+msg.substr(-1);
|
||||||
|
let wait = 4167*(1-(x%1));
|
||||||
|
timeDef = time5;
|
||||||
|
}
|
||||||
|
if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day
|
||||||
|
date = formatDate(res,dateFormat);
|
||||||
|
if(dt<timeActiveUntil)
|
||||||
|
{
|
||||||
|
// Write to background buffers, then display on screen
|
||||||
|
writeDozDate(date,calenDef,res.colour);
|
||||||
|
writeDozTime(time,timeDef);
|
||||||
|
g.flip();
|
||||||
|
// Ready next interval
|
||||||
|
drawtime_timeout = setTimeout(drawTime,wait);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Clear screen
|
||||||
|
g_d.clear();
|
||||||
|
g_t.clear();
|
||||||
|
g.flip();
|
||||||
|
|
||||||
|
}
|
||||||
|
lastX = x;
|
||||||
|
}
|
||||||
|
function modeTime()
|
||||||
|
{
|
||||||
|
timeActiveUntil = new Date();
|
||||||
|
timeActiveUntil.setDate(timeActiveUntil.getDate());
|
||||||
|
timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+15);
|
||||||
|
if (typeof drawtime_timeout !== 'undefined')
|
||||||
|
{
|
||||||
|
clearTimeout(drawtime_timeout);
|
||||||
|
}
|
||||||
|
drawTime();
|
||||||
|
}
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
// Functions for weather mode - TODO
|
||||||
|
// function drawWeather() {}
|
||||||
|
// function modeWeather() {}
|
||||||
|
|
||||||
|
// Start time on twist
|
||||||
|
Bangle.on('twist',function() {
|
||||||
|
modeTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Time fix with GPS
|
||||||
|
function fixTime() {
|
||||||
|
Bangle.on("GPS",function cb(g) {
|
||||||
|
Bangle.setGPSPower(0,"time");
|
||||||
|
Bangle.removeListener("GPS",cb);
|
||||||
|
if (!g.time || (g.time.getFullYear()<2000) ||
|
||||||
|
(g.time.getFullYear()>2200)) {
|
||||||
|
} else {
|
||||||
|
// We have a GPS time. Set time
|
||||||
|
setTime(g.time.getTime()/1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Bangle.setGPSPower(1,"time");
|
||||||
|
setTimeout(fixTime, 10*60*1000); // every 10 minutes
|
||||||
|
}
|
||||||
|
// Start time fixing with GPS on next 10 minute interval
|
||||||
|
setTimeout(fixTime, ((60-(new Date()).getMinutes()) % 10) * 60 * 1000);
|
|
@ -1 +1,2 @@
|
||||||
0.01: First version
|
0.01: First version
|
||||||
|
0.02: Enchanced contrast of icon image
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA="))
|
require("heatshrink").decompress(atob("mEw4UA///iADCn+EqoAWqAuJgoLcn/8BZENGwNwBY/VBYNXBY0DJ4fABYoiCEggLDmtX1Wq6tcBYvVrQLB0owCBYdVtQLB1NVBYg6BBQIABHgQLCgIuCGAVABYcNqwtBGIOVJAILFyoCCBY5eBBdo7IgIIB1t6BYJfENZaDB9QKB1aDFBYKbEBYizBrwLB2qnFdwSmCX401cYdUBZTjGfYgHCBZB2BBYhUBAARSBBYhICAAIGCBYkVBQJSCBYpICIwQLFHgQ6CBYo8CHQQLFHgQFDBYsVQIQLHgo6DBY0BHQYLGgY6DBYwAFBbCjDACY"))
|
||||||
|
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -2,9 +2,7 @@
|
||||||
0.02: Remove messages on disconnect
|
0.02: Remove messages on disconnect
|
||||||
0.03: Handling of message actions (ok/clear)
|
0.03: Handling of message actions (ok/clear)
|
||||||
0.04: Added common bundleId's
|
0.04: Added common bundleId's
|
||||||
0.05: Added more bundleId's (app-id's which can be used to
|
0.05: Added more bundleId's (app-id's which can be used to determine a friendly app name in the notifications)
|
||||||
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)
|
0.07: Added more details from music (instead of Undefined), added more app identifiers
|
||||||
Added more app identifiers
|
0.08: Added more app identifiers, added 'cannot display' in case a message goes empty because of replacements
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@ E.on('notify',msg=>{
|
||||||
"com.apple.mobilecal": "Calendar",
|
"com.apple.mobilecal": "Calendar",
|
||||||
"com.apple.mobilemail": "Mail",
|
"com.apple.mobilemail": "Mail",
|
||||||
"com.apple.mobilephone": "Phone",
|
"com.apple.mobilephone": "Phone",
|
||||||
|
"com.apple.mobileslideshow": "Pictures",
|
||||||
"com.apple.MobileSMS": "SMS Message",
|
"com.apple.MobileSMS": "SMS Message",
|
||||||
"com.apple.Passbook": "iOS Wallet",
|
"com.apple.Passbook": "iOS Wallet",
|
||||||
"com.apple.podcasts": "Podcasts",
|
"com.apple.podcasts": "Podcasts",
|
||||||
|
@ -83,6 +84,7 @@ E.on('notify',msg=>{
|
||||||
"com.ifttt.ifttt" : "IFTTT",
|
"com.ifttt.ifttt" : "IFTTT",
|
||||||
"com.jumbo.app" : "Jumbo",
|
"com.jumbo.app" : "Jumbo",
|
||||||
"com.linkedin.LinkedIn" : "LinkedIn",
|
"com.linkedin.LinkedIn" : "LinkedIn",
|
||||||
|
"com.marktplaats.iphone": "Marktplaats",
|
||||||
"com.microsoft.Office.Outlook" : "Outlook Mail",
|
"com.microsoft.Office.Outlook" : "Outlook Mail",
|
||||||
"com.nestlabs.jasper.release" : "Nest",
|
"com.nestlabs.jasper.release" : "Nest",
|
||||||
"com.netflix.Netflix" : "Netflix",
|
"com.netflix.Netflix" : "Netflix",
|
||||||
|
@ -90,6 +92,7 @@ E.on('notify',msg=>{
|
||||||
"com.skype.skype": "Skype",
|
"com.skype.skype": "Skype",
|
||||||
"com.skype.SkypeForiPad": "Skype",
|
"com.skype.SkypeForiPad": "Skype",
|
||||||
"com.spotify.client": "Spotify",
|
"com.spotify.client": "Spotify",
|
||||||
|
"com.storytel.iphone": "Storytel",
|
||||||
"com.strava.stravaride": "Strava",
|
"com.strava.stravaride": "Strava",
|
||||||
"com.tinyspeck.chatlyio": "Slack",
|
"com.tinyspeck.chatlyio": "Slack",
|
||||||
"com.toyopagroup.picaboo": "Snapchat",
|
"com.toyopagroup.picaboo": "Snapchat",
|
||||||
|
@ -98,6 +101,8 @@ E.on('notify',msg=>{
|
||||||
"com.vilcsak.bitcoin2": "Coinbase",
|
"com.vilcsak.bitcoin2": "Coinbase",
|
||||||
"com.wordfeud.free": "WordFeud",
|
"com.wordfeud.free": "WordFeud",
|
||||||
"com.zhiliaoapp.musically": "TikTok",
|
"com.zhiliaoapp.musically": "TikTok",
|
||||||
|
"io.robbie.HomeAssistant": "Home Assistant",
|
||||||
|
"net.weks.prowl": "Prowl",
|
||||||
"net.whatsapp.WhatsApp": "WhatsApp",
|
"net.whatsapp.WhatsApp": "WhatsApp",
|
||||||
"nl.ah.Appie": "Albert Heijn",
|
"nl.ah.Appie": "Albert Heijn",
|
||||||
"nl.postnl.TrackNTrace": "PostNL",
|
"nl.postnl.TrackNTrace": "PostNL",
|
||||||
|
@ -118,7 +123,7 @@ E.on('notify',msg=>{
|
||||||
new : msg.new,
|
new : msg.new,
|
||||||
title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer),
|
title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer),
|
||||||
subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer),
|
subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer),
|
||||||
body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer)
|
body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer) || "Cannot display"
|
||||||
});
|
});
|
||||||
// TODO: posaction/negaction?
|
// TODO: posaction/negaction?
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,4 +5,5 @@
|
||||||
0.05: Additional icons for (1) charging and (2) bat < 30%.
|
0.05: Additional icons for (1) charging and (2) bat < 30%.
|
||||||
0.06: Fix - Alarm disabled, if clock was closed.
|
0.06: Fix - Alarm disabled, if clock was closed.
|
||||||
0.07: Added settings to adjust data that is shown for each row.
|
0.07: Added settings to adjust data that is shown for each row.
|
||||||
0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
|
0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
|
||||||
|
0.09: Tab anywhere to open the launcher.
|
|
@ -8,13 +8,15 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda
|
||||||
* LCARS Style watch face.
|
* LCARS Style watch face.
|
||||||
* Full screen mode - widgets are still loaded.
|
* Full screen mode - widgets are still loaded.
|
||||||
* Supports multiple screens with different data.
|
* Supports multiple screens with different data.
|
||||||
|
* Tab anywhere to open the launcher.
|
||||||
* [Screen 1] Date + Time + Lock status.
|
* [Screen 1] Date + Time + Lock status.
|
||||||
* [Screen 1] Shows randomly images of real planets.
|
* [Screen 1] Shows randomly images of real planets.
|
||||||
* [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.)
|
* [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.)
|
||||||
* [Screen 1] Swipe up/down to activate an alarm.
|
* [Screen 1] Swipe up/down to activate an alarm.
|
||||||
* [Screen 1] Shows 3 customizable datapoints on the first screen.
|
* [Screen 1] Shows 3 customizable datapoints on the first screen.
|
||||||
* [Screen 1] The lower orange line indicates the battery level.
|
* [Screen 1] The lower orange line indicates the battery level.
|
||||||
* [Screen 2] Display month graphs for steps + hrm on the second screen.
|
* [Screen 2] Display graphs for steps + hrm on the second screen.
|
||||||
|
* [Screen 2] Switch between day/month via swipe up/down.
|
||||||
|
|
||||||
|
|
||||||
## Multiple screens support
|
## Multiple screens support
|
||||||
|
|
|
@ -24,6 +24,7 @@ let cOrange = "#FF9900";
|
||||||
let cPurple = "#FF00DC";
|
let cPurple = "#FF00DC";
|
||||||
let cWhite = "#FFFFFF";
|
let cWhite = "#FFFFFF";
|
||||||
let cBlack = "#000000";
|
let cBlack = "#000000";
|
||||||
|
let cGrey = "#9E9E9E";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Global lcars variables
|
* Global lcars variables
|
||||||
|
@ -147,14 +148,17 @@ function printData(key, y, c){
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setColor(c);
|
g.setColor(c);
|
||||||
|
g.fillRect(79, y-2, 87 ,y+18);
|
||||||
|
|
||||||
|
g.setFontAlign(1,-1,0);
|
||||||
|
g.drawString(value, 131, y);
|
||||||
|
|
||||||
|
g.setColor(c);
|
||||||
|
g.setFontAlign(-1,-1,0);
|
||||||
g.fillRect(133, y-2, 165 ,y+18);
|
g.fillRect(133, y-2, 165 ,y+18);
|
||||||
g.fillCircle(161, y+8, 10);
|
g.fillCircle(161, y+8, 10);
|
||||||
g.setColor(cBlack);
|
g.setColor(cBlack);
|
||||||
g.drawString(text, 135, y);
|
g.drawString(text, 135, y);
|
||||||
|
|
||||||
g.setColor(c);
|
|
||||||
g.setFontAlign(1,-1,0);
|
|
||||||
g.drawString(value, 130, y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawHorizontalBgLine(color, x1, x2, y, h){
|
function drawHorizontalBgLine(color, x1, x2, y, h){
|
||||||
|
@ -191,13 +195,14 @@ function drawState(){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g.clearRect(20, 93, 77, 170);
|
g.clearRect(20, 93, 75, 170);
|
||||||
g.setColor(cWhite);
|
g.setFontAlign(0, 0, 0);
|
||||||
var bat = E.getBattery();
|
g.setFontAntonioMedium();
|
||||||
var current = new Date();
|
|
||||||
var hours = current.getHours();
|
|
||||||
|
|
||||||
if(!isAlarmEnabled()){
|
if(!isAlarmEnabled()){
|
||||||
|
var bat = E.getBattery();
|
||||||
|
var current = new Date();
|
||||||
|
var hours = current.getHours();
|
||||||
var iconImg =
|
var iconImg =
|
||||||
Bangle.isCharging() ? iconCharging :
|
Bangle.isCharging() ? iconCharging :
|
||||||
bat < 30 ? iconNoBattery :
|
bat < 30 ? iconNoBattery :
|
||||||
|
@ -206,16 +211,16 @@ function drawState(){
|
||||||
hours % 4 == 1 ? iconMars :
|
hours % 4 == 1 ? iconMars :
|
||||||
hours % 4 == 2 ? iconMoon :
|
hours % 4 == 2 ? iconMoon :
|
||||||
iconEarth;
|
iconEarth;
|
||||||
g.drawImage(iconImg, 29, 104);
|
g.drawImage(iconImg, 24, 118);
|
||||||
|
g.setColor(cWhite);
|
||||||
|
g.drawString("STATUS", 24+25, 108);
|
||||||
} else {
|
} else {
|
||||||
// Alarm within symbol
|
// Alarm within symbol
|
||||||
g.setFontAntonioMedium();
|
|
||||||
g.setFontAlign(0, 0, 0);
|
|
||||||
g.setColor(cOrange);
|
g.setColor(cOrange);
|
||||||
g.drawString("ALARM", 29+25, 107);
|
g.drawString("ALARM", 24+25, 108);
|
||||||
g.setColor(cWhite);
|
g.setColor(cWhite);
|
||||||
g.setFontAntonioLarge();
|
g.setFontAntonioLarge();
|
||||||
g.drawString(getAlarmMinutes(), 29+25, 107+35);
|
g.drawString(getAlarmMinutes(), 24+25, 108+35);
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setFontAlign(-1, -1, 0);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
@ -236,7 +241,7 @@ function drawPosition0(){
|
||||||
var bat = E.getBattery() / 100.0;
|
var bat = E.getBattery() / 100.0;
|
||||||
var batX2 = parseInt((172 - 35) * bat + 35);
|
var batX2 = parseInt((172 - 35) * bat + 35);
|
||||||
drawHorizontalBgLine(cOrange, 35, batX2, 171, 5);
|
drawHorizontalBgLine(cOrange, 35, batX2, 171, 5);
|
||||||
drawHorizontalBgLine(cPurple, batX2+10, 172, 171, 5);
|
drawHorizontalBgLine(cGrey, batX2+10, 172, 171, 5);
|
||||||
|
|
||||||
// Draw logo
|
// Draw logo
|
||||||
drawLock();
|
drawLock();
|
||||||
|
@ -247,7 +252,7 @@ function drawPosition0(){
|
||||||
var currentDate = new Date();
|
var currentDate = new Date();
|
||||||
var timeStr = locale.time(currentDate,1);
|
var timeStr = locale.time(currentDate,1);
|
||||||
g.setFontAntonioLarge();
|
g.setFontAntonioLarge();
|
||||||
g.drawString(timeStr, 28, 10);
|
g.drawString(timeStr, 29, 10);
|
||||||
|
|
||||||
// Write date
|
// Write date
|
||||||
g.setColor(cWhite);
|
g.setColor(cWhite);
|
||||||
|
@ -255,7 +260,7 @@ function drawPosition0(){
|
||||||
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
var dayStr = locale.dow(currentDate, true).toUpperCase();
|
||||||
dayStr += " " + currentDate.getDate();
|
dayStr += " " + currentDate.getDate();
|
||||||
dayStr += " " + currentDate.getFullYear();
|
dayStr += " " + currentDate.getFullYear();
|
||||||
g.drawString(dayStr, 29, 56);
|
g.drawString(dayStr, 32, 56);
|
||||||
|
|
||||||
// Draw data
|
// Draw data
|
||||||
g.setFontAlign(-1, -1, 0);
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
@ -401,7 +406,7 @@ function draw(){
|
||||||
* Step counter via widget
|
* Step counter via widget
|
||||||
*/
|
*/
|
||||||
function getSteps() {
|
function getSteps() {
|
||||||
var steps = 0
|
var steps = 0;
|
||||||
try {
|
try {
|
||||||
health = require("health");
|
health = require("health");
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
|
@ -553,6 +558,10 @@ Bangle.on("drag", e => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Bangle.on("touch", e => {
|
||||||
|
Bangle.showLauncher();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lets start widgets, listen for btn etc.
|
* Lets start widgets, listen for btn etc.
|
||||||
|
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.4 KiB |
|
@ -1,2 +1,3 @@
|
||||||
0.01: Created app
|
0.01: Created app
|
||||||
0.02: Use Bangle.setUI for button/launcher handling
|
0.02: Use Bangle.setUI for button/launcher handling
|
||||||
|
0.03: Allow widgets to detect this is a clock
|
||||||
|
|
|
@ -304,15 +304,14 @@ Bangle.on('lcdPower',function(on) {
|
||||||
});
|
});
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
|
// Show launcher when button pressed
|
||||||
|
Bangle.setUI("clock");
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
// Update time once a second
|
// Update time once a second
|
||||||
timeInterval = setInterval(showTime, 1000);
|
timeInterval = setInterval(showTime, 1000);
|
||||||
showTime();
|
showTime();
|
||||||
|
|
||||||
// Show launcher when button pressed
|
|
||||||
Bangle.setUI("clock");
|
|
||||||
|
|
||||||
// Start stopwatch when BTN3 is pressed
|
// Start stopwatch when BTN3 is pressed
|
||||||
setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"});
|
setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"});
|
||||||
B3 = 1; // BTN3 is bound to start the stopwatch
|
B3 = 1; // BTN3 is bound to start the stopwatch
|
||||||
|
|
|
@ -23,3 +23,4 @@
|
||||||
0.14: Hide widget when all unread notifications are dismissed from phone
|
0.14: Hide widget when all unread notifications are dismissed from phone
|
||||||
0.15: Don't buzz when Quiet Mode is active
|
0.15: Don't buzz when Quiet Mode is active
|
||||||
0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147)
|
0.16: Fix text wrapping so it fits the screen even if title is big (fix #1147)
|
||||||
|
0.17: Fix: Get dynamic dimensions of notify icon, fixed notification font
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
WIDGETS["messages"]={area:"tl", width:0, iconwidth:23,
|
||||||
|
draw:function() {
|
||||||
Bangle.removeListener('touch', this.touch);
|
Bangle.removeListener('touch', this.touch);
|
||||||
if (!this.width) return;
|
if (!this.width) return;
|
||||||
var c = (Date.now()-this.t)/1000;
|
var c = (Date.now()-this.t)/1000;
|
||||||
g.reset().clearRect(this.x,this.y,this.x+this.width,this.y+23);
|
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+this.iconwidth);
|
||||||
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);
|
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
|
//if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute
|
||||||
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
|
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
|
||||||
|
@ -17,7 +18,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
||||||
WIDGETS["messages"].t=Date.now(); // first time
|
WIDGETS["messages"].t=Date.now(); // first time
|
||||||
WIDGETS["messages"].l=Date.now()-10000; // last buzz
|
WIDGETS["messages"].l=Date.now()-10000; // last buzz
|
||||||
if (quiet) WIDGETS["messages"].t -= 500000; // if quiet, set last time in the past so there is no buzzing
|
if (quiet) WIDGETS["messages"].t -= 500000; // if quiet, set last time in the past so there is no buzzing
|
||||||
WIDGETS["messages"].width=64;
|
WIDGETS["messages"].width=this.iconwidth;
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
Bangle.setLCDPower(1);// turns screen on
|
Bangle.setLCDPower(1);// turns screen on
|
||||||
},hide:function() {
|
},hide:function() {
|
||||||
|
@ -37,7 +38,7 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() {
|
||||||
b();
|
b();
|
||||||
},touch:function(b,c) {
|
},touch:function(b,c) {
|
||||||
var w=WIDGETS["messages"];
|
var w=WIDGETS["messages"];
|
||||||
if (!w||!w.width||c.x<w.x||c.x>w.x+w.width||c.y<w.y||c.y>w.y+23) return;
|
if (!w||!w.width||c.x<w.x||c.x>w.x+w.width||c.y<w.y||c.y>w.y+w.iconwidth) return;
|
||||||
load("messages.app.js");
|
load("messages.app.js");
|
||||||
}};
|
}};
|
||||||
/* We might have returned here if we were in the Messages app for a
|
/* We might have returned here if we were in the Messages app for a
|
||||||
|
@ -46,4 +47,4 @@ want to buzz but should still show that there are unread messages. */
|
||||||
if (global.MESSAGES===undefined) (function() {
|
if (global.MESSAGES===undefined) (function() {
|
||||||
var messages = require("Storage").readJSON("messages.json",1)||[];
|
var messages = require("Storage").readJSON("messages.json",1)||[];
|
||||||
if (messages.some(m=>m.new)) WIDGETS["messages"].show(true);
|
if (messages.some(m=>m.new)) WIDGETS["messages"].show(true);
|
||||||
})();
|
})();
|
|
@ -1 +1,2 @@
|
||||||
0.01: First release
|
0.01: First release
|
||||||
|
0.02: Enhanced icon, make it bolder
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEw4UA///t/7j/P3/vB4cBqtVoAbHBQIABBQ0FBYdQBYsVBYdUERIkGHIQADHoguEGAwuEGAwKFBZg8DHQw8EBYNf/1Vq3/8oLDIwNf/Wpv//0oLG9Wq3/qBYJUCBYuqBaBqBBYW+BepHEBbybCBYP+BYSnErYLDyoLFAANq/r8Ga5T7MBZZUBAAhSCfhA6DBZhIGBQg8FHQg8GHQgwGFwowFBQwwDFwwLMlS7Bqta1AKEn2q1K1C1WgBYf/1WqBYIDB1QKCgYLC0taBYoXB/QICBY0//7vBAAQ8EEgIABCwwME9QVEA"))
|
require("heatshrink").decompress(atob("mEw4UA///gH4AYPO/QPDgNVqtADY/1BYNfBQ0PBQIAB+ALFmoLDrgLF6oLDq4KEgYKDBYPABYcNBYlVuAuIGAwuEAANUBYYKFHgg6Bq4ZCr4DBHgQLBvWq2te1WlBYZGBBYOr1Wq1qSDBYNqBIILDKgQLLgoLHqBqDBfJHLBZBrOgKPCBYiPCU4NaBYe1WYrABBQLCCfgYGCrwVBa4kAirvKNgIAErgLDKgIAEKQQ8EAAY6DBZhIDIww8GHQg8GHQgwGFwowEFwx5EOog8GHQ0AlWpBYNq1AKFWIILBAYOgBYbICytWAgQKCgTgDcwYXGAAgvGAAY8EEgYWGBgoVEA=="))
|
||||||
|
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -7,3 +7,4 @@
|
||||||
0.07: Added info line that cycles on BTN1/BTN3 (or vitual buttons on a bangle 2)
|
0.07: Added info line that cycles on BTN1/BTN3 (or vitual buttons on a bangle 2)
|
||||||
0.08: Added dependancy on MyLocation
|
0.08: Added dependancy on MyLocation
|
||||||
0.09: Added dependancy on Pedometer Widget
|
0.09: Added dependancy on Pedometer Widget
|
||||||
|
0.10: Added Weather line, fixed issues on a Bangle 1, update every minute
|
||||||
|
|
|
@ -1,45 +1,83 @@
|
||||||
# Pastel Clock
|
# Pastel Clock
|
||||||
|
|
||||||
*a configurable clock with custom fonts and background. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times*
|
*a configurable clock with custom fonts, background and optional weather icons. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times*
|
||||||
|
|
||||||
* Designed specifically for Bangle 1 and Bangle 2
|
* Designed specifically for Bangle 1 and Bangle 2
|
||||||
* A choice of 7 different custom fonts
|
* A choice of 7 different custom fonts
|
||||||
* Supports the Light and Dark themes
|
* Supports the Light and Dark themes
|
||||||
* Has a settings menu, change font, enable/disable the grid
|
* Has a settings menu, change font, enable/disable the grid, weather icons
|
||||||
* On Bangle 1 use BTN1,BTN3 to cycle through the info display (Date, ID, Batt %, Ram % etc)
|
* On Bangle 1 use BTN1,BTN3 to cycle through the info display (Date, ID, Batt %, Ram % etc)
|
||||||
* On Bangle 2 touch the top right/top left to cycle through the info display (Date, ID, Batt %, Ram % etc)
|
* On Bangle 2 touch the top right/top left to cycle through the info display (Date, ID, Batt %, Ram % etc)
|
||||||
|
* The information display will cycle on each screen update
|
||||||
* Uses mylocation.json from MyLocation app to calculate sunrise and sunset times for your location
|
* Uses mylocation.json from MyLocation app to calculate sunrise and sunset times for your location
|
||||||
* Uses pedometer widget to get latest step count
|
* Uses pedometer widget to get latest step count
|
||||||
|
* Use the weather widget to get weather status
|
||||||
* Dependant apps are installed when Pastel installs
|
* Dependant apps are installed when Pastel installs
|
||||||
|
* The screen is updated every minute to save battery power
|
||||||
|
* The weather display will display temperature and wind speed on alternate screen refreshes
|
||||||
|
|
||||||
I came up with the name Pastel due to the shade of the grid background.
|
I came up with the name Pastel due to the shade of the grid background.
|
||||||
|
|
||||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)
|
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)
|
||||||
|
|
||||||
## Lato
|
|
||||||
|
## Weather Support
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Pastel installs the weather app and weather widget. You may want to
|
||||||
|
hide the weather widget display, this can be done through the weather
|
||||||
|
widget settings. You should first get the weather app working. If
|
||||||
|
the weather App is not working, then it is not going to work for
|
||||||
|
Pastel.
|
||||||
|
|
||||||
|
The following weather icons are supported.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Mostly cloudy, Sunny, Mostly Sunny, Snow, Rain.
|
||||||
|
|
||||||
|
The triangle icon shows there is a problem connecting to GadgetBridge and the weather service.
|
||||||
|
You should follow the setup and trouble shooting guide for the Weather App.
|
||||||
|
|
||||||
|
If you find the weather / gadgetbridge service unreliable you can
|
||||||
|
disable weather updates to pastel through the settings app.
|
||||||
|
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
### Lato
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Architect
|
### Architect
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Gochihand
|
### Gochihand
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Monoton
|
### Monoton
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Elite
|
### Elite
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Cabin Sketch
|
### Cabin Sketch
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Orbitron
|
### Orbitron
|
||||||

|

|
||||||
|
|
||||||
|
### The Grid
|
||||||
|
|
||||||
|
Setting the grid on provides a graph paper style background to the App.
|
||||||
|
The grid is not supported on a Bangle 1 due to flicker issues.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||||
require("f_latosmall").add(Graphics);
|
require("f_latosmall").add(Graphics);
|
||||||
|
const storage = require('Storage');
|
||||||
|
const locale = require("locale");
|
||||||
const SETTINGS_FILE = "pastel.json";
|
const SETTINGS_FILE = "pastel.json";
|
||||||
const LOCATION_FILE = "mylocation.json";
|
const LOCATION_FILE = "mylocation.json";
|
||||||
let settings;
|
let settings;
|
||||||
let location;
|
let location;
|
||||||
|
|
||||||
|
// cloud, sun, partSun, snow, rain, storm, error
|
||||||
|
// create 1 bit, max contrast, brightness set to 85
|
||||||
|
var cloudIcon = require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA=="));
|
||||||
|
var sunIcon = require("heatshrink").decompress(atob("kEggILIgOAAZkDAYPAgeBwPAgIFBBgPhw4TBp/yAYMcnADBnEcAYMwhgDBsEGgE/AYP8AYYLDCYgbDEYYrD8fHIwI7CIYZLDL54AHA=="));
|
||||||
|
var sunPartIcon = require("heatshrink").decompress(atob("kEggIHEmADJjEwsEAjkw8EAh0B4EAg35wEAgP+CYMDwv8AYMDBAP2g8HgH+g0DBYMMgPwAYX8gOMEwMG3kAg8OvgSBjg2BgcYGQIcBAY5CBg0Av//HAM///4MYgNBEIMOCoUMDoUAnBwGkEA"));
|
||||||
|
var snowIcon = require("heatshrink").decompress(atob("kEggITQj/AAYM98ADBsEwAYPAjADCj+AgOAj/gAYMIuEHwEAjEPAYQVChk4AYQhCAYcYBYQTDnEPgEB+EH///IAQACE4IAB8EICIPghwDB4EeBYNAjgDBg8EAYQYCg4bCgZuFA=="));
|
||||||
|
var rainIcon = require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA=="));
|
||||||
|
var errIcon = require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A=="));
|
||||||
|
|
||||||
|
|
||||||
function loadSettings() {
|
function loadSettings() {
|
||||||
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
||||||
settings.grid = settings.grid||false;
|
settings.grid = settings.grid||false;
|
||||||
|
@ -93,7 +105,49 @@ function prevInfo() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mm_prev = "xx";
|
|
||||||
|
/**
|
||||||
|
Choose weather icon to display based on condition.
|
||||||
|
Based on function from the Bangle weather app so it should handle all of the conditions
|
||||||
|
sent from gadget bridge.
|
||||||
|
*/
|
||||||
|
function chooseIcon(condition) {
|
||||||
|
condition = condition.toLowerCase();
|
||||||
|
if (condition.includes("thunderstorm")) return stormIcon;
|
||||||
|
if (condition.includes("freezing")||condition.includes("snow")||
|
||||||
|
condition.includes("sleet")) {
|
||||||
|
return snowIcon;
|
||||||
|
}
|
||||||
|
if (condition.includes("drizzle")||
|
||||||
|
condition.includes("shower")) {
|
||||||
|
return rainIcon;
|
||||||
|
}
|
||||||
|
if (condition.includes("rain")) return rainIcon;
|
||||||
|
if (condition.includes("clear")) return sunIcon;
|
||||||
|
if (condition.includes("few clouds")) return partSunIcon;
|
||||||
|
if (condition.includes("scattered clouds")) return cloudIcon;
|
||||||
|
if (condition.includes("clouds")) return cloudIcon;
|
||||||
|
if (condition.includes("mist") ||
|
||||||
|
condition.includes("smoke") ||
|
||||||
|
condition.includes("haze") ||
|
||||||
|
condition.includes("sand") ||
|
||||||
|
condition.includes("dust") ||
|
||||||
|
condition.includes("fog") ||
|
||||||
|
condition.includes("ash") ||
|
||||||
|
condition.includes("squalls") ||
|
||||||
|
condition.includes("tornado")) {
|
||||||
|
return cloudIcon;
|
||||||
|
}
|
||||||
|
return cloudIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get weather stored in json file by weather app.
|
||||||
|
*/
|
||||||
|
function getWeather() {
|
||||||
|
let jsonWeather = storage.readJSON('weather.json');
|
||||||
|
return jsonWeather;
|
||||||
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
var d = new Date();
|
var d = new Date();
|
||||||
|
@ -114,20 +168,28 @@ function draw() {
|
||||||
var h = g.getHeight();
|
var h = g.getHeight();
|
||||||
var x = (g.getWidth()/2);
|
var x = (g.getWidth()/2);
|
||||||
var y = (g.getHeight()/3);
|
var y = (g.getHeight()/3);
|
||||||
|
|
||||||
g.reset();
|
|
||||||
|
|
||||||
if (process.env.HWVERSION == 1) {
|
var weatherJson = getWeather();
|
||||||
// avoid flicker on a bangle 1 by comparing with previous minute
|
var w_temp;
|
||||||
if (mm_prev != mm) {
|
var w_icon;
|
||||||
mm_prev = mm;
|
var w_wind;
|
||||||
g.clearRect(0, 30, w, h - 24);
|
|
||||||
}
|
if (settings.weather && weatherJson && weatherJson.weather) {
|
||||||
|
var currentWeather = weatherJson.weather;
|
||||||
|
const temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||||
|
w_temp = temp[1] + " " + temp[2];
|
||||||
|
w_icon = chooseIcon(currentWeather.txt);
|
||||||
|
const wind = locale.speed(currentWeather.wind).match(/^(\D*\d*)(.*)$/);
|
||||||
|
w_wind = wind[1] + " " + wind[2] + " " + (currentWeather.wrose||'').toUpperCase();
|
||||||
} else {
|
} else {
|
||||||
// on a b2 safe to just clear anyway as there is no flicker
|
w_temp = "Err";
|
||||||
g.clearRect(0, 30, w, h - 24);
|
w_wind = "???";
|
||||||
|
w_icon = errIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.reset();
|
||||||
|
g.clearRect(0, 30, w, h - 24);
|
||||||
|
|
||||||
// draw a grid like graph paper
|
// draw a grid like graph paper
|
||||||
if (settings.grid && process.env.HWVERSION !=1) {
|
if (settings.grid && process.env.HWVERSION !=1) {
|
||||||
g.setColor("#0f0");
|
g.setColor("#0f0");
|
||||||
|
@ -139,6 +201,18 @@ function draw() {
|
||||||
|
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
|
|
||||||
|
// draw weather line
|
||||||
|
if (settings.weather) {
|
||||||
|
g.drawImage(w_icon, (w/2) - 40, 24);
|
||||||
|
g.setFontLatoSmall();
|
||||||
|
g.setFontAlign(-1,0); // left aligned
|
||||||
|
if (drawCount % 2 == 0)
|
||||||
|
g.drawString(w_temp, (w/2) + 6, 24 + ((y - 24)/2));
|
||||||
|
else
|
||||||
|
g.drawString( (w_wind.split(' ').slice(0, 2).join(' ')), (w/2) + 6, 24 + ((y - 24)/2));
|
||||||
|
// display first 2 words of the wind string eg '4 mph'
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.font == "Architect")
|
if (settings.font == "Architect")
|
||||||
g.setFontArchitect();
|
g.setFontArchitect();
|
||||||
else if (settings.font == "GochiHand")
|
else if (settings.font == "GochiHand")
|
||||||
|
@ -161,36 +235,39 @@ function draw() {
|
||||||
|
|
||||||
// for the colon
|
// for the colon
|
||||||
g.setFontAlign(0,-1); // centre aligned
|
g.setFontAlign(0,-1); // centre aligned
|
||||||
|
g.drawString(":", x,y);
|
||||||
if (d.getSeconds()&1) {
|
|
||||||
g.drawString(":", x,y);
|
|
||||||
} else {
|
|
||||||
// on bangle 1, we are not using clearRect(), hide : by printing over it in reverse color
|
|
||||||
if (process.env.HWVERSION == 1) {
|
|
||||||
g.setColor(g.theme.bg);
|
|
||||||
g.drawString(":", x,y);
|
|
||||||
g.setColor(g.theme.fg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.setFontLatoSmall();
|
g.setFontLatoSmall();
|
||||||
g.setFontAlign(0, -1);
|
g.setFontAlign(0, -1);
|
||||||
g.drawString((infoData[infoMode].calc()), w/2, h - 24 - 24);
|
g.drawString((infoData[infoMode].calc()), w/2, h - 24 - 24);
|
||||||
|
|
||||||
if (drawCount % 3600 == 0)
|
// recalc sunrise / sunset every hour
|
||||||
|
if (drawCount % 60 == 0)
|
||||||
updateSunRiseSunSet(new Date(), location.lat, location.lon);
|
updateSunRiseSunSet(new Date(), location.lat, location.lon);
|
||||||
drawCount++;
|
drawCount++;
|
||||||
|
queueDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only update when display turns on
|
// timeout used to update every minute
|
||||||
if (process.env.BOARD!="SMAQ3") // hack for Q3 which is always-on
|
var drawTimeout;
|
||||||
Bangle.on('lcdPower', function(on) {
|
|
||||||
if (secondInterval)
|
// schedule a draw for the next minute
|
||||||
clearInterval(secondInterval);
|
function queueDraw() {
|
||||||
secondInterval = undefined;
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
if (on)
|
drawTimeout = setTimeout(function() {
|
||||||
secondInterval = setInterval(draw, 1000);
|
drawTimeout = undefined;
|
||||||
draw();
|
nextInfo();
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop updates when LCD is off, restart when on
|
||||||
|
Bangle.on('lcdPower',on=>{
|
||||||
|
if (on) {
|
||||||
|
draw(); // draw immediately, queue redraw
|
||||||
|
} else { // stop draw timer
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.setUI("clockupdown", btn=> {
|
Bangle.setUI("clockupdown", btn=> {
|
||||||
|
@ -204,8 +281,6 @@ loadFonts();
|
||||||
loadLocation();
|
loadLocation();
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
var secondInterval = setInterval(draw, 1000);
|
|
||||||
draw();
|
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
draw();
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
// initialize with default settings...
|
// initialize with default settings...
|
||||||
let s = {
|
let s = {
|
||||||
'grid': false,
|
'grid': false,
|
||||||
|
'weather': false,
|
||||||
'font': "Lato"
|
'font': "Lato"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +40,16 @@
|
||||||
value: s.grid,
|
value: s.grid,
|
||||||
format: () => (s.grid ? 'Yes' : 'No'),
|
format: () => (s.grid ? 'Yes' : 'No'),
|
||||||
onchange: () => {
|
onchange: () => {
|
||||||
s.grid = !s.grid
|
s.grid = !s.grid;
|
||||||
save()
|
save();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Show Weather': {
|
||||||
|
value: s.weather,
|
||||||
|
format: () => (s.weather ? 'Yes' : 'No'),
|
||||||
|
onchange: () => {
|
||||||
|
s.weather = !s.weather;
|
||||||
|
save();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.5 KiB |
|
@ -4,3 +4,4 @@
|
||||||
0.04: Fix widget hiding code (fix #1046)
|
0.04: Fix widget hiding code (fix #1046)
|
||||||
0.05: Fix typo in settings - Purple
|
0.05: Fix typo in settings - Purple
|
||||||
0.06: Added dependancy on Pedometer Widget
|
0.06: Added dependancy on Pedometer Widget
|
||||||
|
0.07: Fixed icon and ong file to 48x48
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("oFAwgNKiIAIFqofegIf/DAUzAAMyAwUQD60T/4ACD7Q/cPxIf/YCofcDhYiSXYYfuUZgf/D/4f/D6USkUgD/4fuogAID6vtDw/UD6vu6geF73kb6vuEAtN9wfYMIneD7JADDwIfaIAJdBD7YgBHwQfbAAgfkf6Qf/D/4feogAID6oAND/4f/iAdJD/4f/D/4fUDxYABD74iODiAftTZgfnYYczAAMyD7UT/4ACH/S+bD8DAKD9Y="))
|
require("heatshrink").decompress(atob("mEw4UA///ssp4XthFCBwUBqoABqAaGBZcFBZdX1W1qgLHrwLKqv/6oLJAAILHioLJn5qBAAYLEBQoLeHQQABv4LjGAgLYq2qAAOlBbBHFBdPAKcQLdWcb7jAAoLcn4LKgEVHQVUBQsAgoLLq//6oLIr2q2oXJBZQvCqALGgILTA="))
|
||||||
|
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,8 @@
|
||||||
|
0.1: Start of app.
|
||||||
|
0.5: BLE keyboard functionality.
|
||||||
|
1.0: BLE mouse functionality to scroll back/forward.
|
||||||
|
1.5: Added accelerator style mouse.
|
||||||
|
2.0: Added touchpad style mouse.
|
||||||
|
2.1: Initial internal git(hub) release. Added icon and such.
|
||||||
|
2.2: Begin work on presentation parts.
|
||||||
|
3.0: Presentation parts!
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Presentor
|
||||||
|
Use your Bangle to present!
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+AH4ASlgADGmIxwLV4wqGQowfWZQwjKw4wJF7ghBmVWmQlELYoweFwYABGAwrHF7IuFGAwrIF7AuHMJADGF0AwHAYovWFxaSHADQuDEgIADYZYucAQOB1fQ1eBmQwiFwlX6AAE1gqBGD6MEmQqBwIICwIGB0rDeWYksFAIuBz+fvQHC6D0dcQssEwIuB4fC4V0M4VXF7YuFDYLqBlnDF4WeHAYugL5N6L4I4BF0IvB0vQvdQGAJeBY4YucmQxEAgJgBvYOBdwYQDFzUzmZ/EllXFIKKBAYVXBwSMaller7fFAoKSBAAOBFQK7dPoJfFEoYADRjgmEeIzFFdUQAOdUIumdRLtDAAIufdRQKCr8zCQjqmE4NeF4YudWxpqCFyovBK5LqiF6DqdR6D0BdTYwJGg5sBdTQwKEAIwHdTQwMSpQueYaAufGBouiGBYukGBIumGA4uoGAouqGAYABF1QAl"))
|
|
@ -0,0 +1,471 @@
|
||||||
|
// Presentor by 7kasper (Kasper Müller)
|
||||||
|
// Version 3.0
|
||||||
|
|
||||||
|
const SpecialReport = new Uint8Array([
|
||||||
|
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
|
||||||
|
0x09, 0x02, // USAGE (Mouse)
|
||||||
|
0xa1, 0x01, // COLLECTION (Application)
|
||||||
|
0x85, 0x01, // REPORT_ID (1)
|
||||||
|
0x09, 0x01, // USAGE (Pointer)
|
||||||
|
0xa1, 0x00, // COLLECTION (Physical)
|
||||||
|
0x05, 0x09, // USAGE_PAGE (Button)
|
||||||
|
0x19, 0x01, // USAGE_MINIMUM (Button 1)
|
||||||
|
0x29, 0x05, // USAGE_MAXIMUM (Button 5)
|
||||||
|
0x15, 0x00, // LOGICAL_MINIMUM (0)
|
||||||
|
0x25, 0x01, // LOGICAL_MAXIMUM (1)
|
||||||
|
0x95, 0x05, // REPORT_COUNT (5)
|
||||||
|
0x75, 0x01, // REPORT_SIZE (1)
|
||||||
|
0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||||
|
0x95, 0x01, // REPORT_COUNT (1)
|
||||||
|
0x75, 0x03, // REPORT_SIZE (3)
|
||||||
|
0x81, 0x03, // INPUT (Cnst,Var,Abs)
|
||||||
|
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
|
||||||
|
0x09, 0x30, // USAGE (X)
|
||||||
|
0x09, 0x31, // USAGE (Y)
|
||||||
|
0x09, 0x38, // USAGE (Wheel)
|
||||||
|
0x15, 0x81, // LOGICAL_MINIMUM (-127)
|
||||||
|
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
|
||||||
|
0x75, 0x08, // REPORT_SIZE (8)
|
||||||
|
0x95, 0x03, // REPORT_COUNT (3)
|
||||||
|
0x81, 0x06, // INPUT (Data,Var,Rel)
|
||||||
|
0x05, 0x0c, // USAGE_PAGE (Consumer Devices)
|
||||||
|
0x0a, 0x38, 0x02, // USAGE (AC Pan)
|
||||||
|
0x15, 0x81, // LOGICAL_MINIMUM (-127)
|
||||||
|
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
|
||||||
|
0x75, 0x08, // REPORT_SIZE (8)
|
||||||
|
0x95, 0x01, // REPORT_COUNT (1)
|
||||||
|
0x81, 0x06, // INPUT (Data,Var,Rel)
|
||||||
|
0xc0, // END_COLLECTION
|
||||||
|
0xc0, // END_COLLECTION
|
||||||
|
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
|
||||||
|
0x09, 0x06, // USAGE (Keyboard)
|
||||||
|
0xa1, 0x01, // COLLECTION (Application)
|
||||||
|
0x85, 0x02, // REPORT_ID (2)
|
||||||
|
0x05, 0x07, // USAGE_PAGE (Keyboard)
|
||||||
|
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
|
||||||
|
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
|
||||||
|
0x15, 0x00, // LOGICAL_MINIMUM (0)
|
||||||
|
0x25, 0x01, // LOGICAL_MAXIMUM (1)
|
||||||
|
0x75, 0x01, // REPORT_SIZE (1)
|
||||||
|
0x95, 0x08, // REPORT_COUNT (8)
|
||||||
|
0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||||
|
0x75, 0x08, // REPORT_SIZE (8)
|
||||||
|
0x95, 0x01, // REPORT_COUNT (1)
|
||||||
|
0x81, 0x01, // INPUT (Cnst,Ary,Abs)
|
||||||
|
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
|
||||||
|
0x29, 0x73, // USAGE_MAXIMUM (Keyboard F24)
|
||||||
|
0x15, 0x00, // LOGICAL_MINIMUM (0)
|
||||||
|
0x25, 0x73, // LOGICAL_MAXIMUM (115)
|
||||||
|
0x95, 0x05, // REPORT_COUNT (5)
|
||||||
|
0x75, 0x08, // REPORT_SIZE (8)
|
||||||
|
0x81, 0x00, // INPUT (Data,Ary,Abs)
|
||||||
|
0xc0 // END_COLLECTION
|
||||||
|
]);
|
||||||
|
|
||||||
|
const MouseButton = {
|
||||||
|
NONE : 0,
|
||||||
|
LEFT : 1,
|
||||||
|
RIGHT : 2,
|
||||||
|
MIDDLE : 4,
|
||||||
|
BACK : 8,
|
||||||
|
FORWARD: 16
|
||||||
|
};
|
||||||
|
|
||||||
|
const kb = require("ble_hid_keyboard");
|
||||||
|
|
||||||
|
const Layout = require("Layout");
|
||||||
|
const Locale = require("locale");
|
||||||
|
let mainLayout = new Layout({
|
||||||
|
'type': 'v',
|
||||||
|
filly: 1,
|
||||||
|
c: [
|
||||||
|
{
|
||||||
|
type: 'txt',
|
||||||
|
font: '6x8',
|
||||||
|
label: 'Presentor',
|
||||||
|
valign: -1,
|
||||||
|
halign: 0,
|
||||||
|
col: g.theme.fg1,
|
||||||
|
// bgCol: g.theme.bg2,
|
||||||
|
bgCol: '#00F',
|
||||||
|
fillx: 1,
|
||||||
|
}, {
|
||||||
|
type: 'h',
|
||||||
|
fillx: 1,
|
||||||
|
c: [
|
||||||
|
{
|
||||||
|
type: 'txt',
|
||||||
|
font: '15%',
|
||||||
|
label: '00:00',
|
||||||
|
id: 'Time',
|
||||||
|
halign: -1,
|
||||||
|
pad: 3
|
||||||
|
}, {
|
||||||
|
fillx: 1
|
||||||
|
}, {
|
||||||
|
type: 'txt',
|
||||||
|
font: '15%',
|
||||||
|
label: '00:00',
|
||||||
|
id: 'Timer',
|
||||||
|
halign: 1,
|
||||||
|
pad: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
type: 'txt',
|
||||||
|
font: '10%',
|
||||||
|
label: '+00:00',
|
||||||
|
id: 'RestTime',
|
||||||
|
col: '#fff'
|
||||||
|
}, {
|
||||||
|
type: 'txt',
|
||||||
|
font: '10%',
|
||||||
|
label: '--------------'
|
||||||
|
}, {
|
||||||
|
type: 'txt',
|
||||||
|
font: '15%',
|
||||||
|
label: 'Presenting',
|
||||||
|
id: 'Subject'
|
||||||
|
}, {
|
||||||
|
type: 'txt',
|
||||||
|
font: '6x8',
|
||||||
|
label: 'Swipe up to start the time.',
|
||||||
|
id: 'Notes',
|
||||||
|
col: '#ff0',
|
||||||
|
fillx: 1,
|
||||||
|
filly: 1,
|
||||||
|
valign: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {lazy:true});
|
||||||
|
|
||||||
|
let settings = {pparts: [], sversion: 0};
|
||||||
|
let HIDenabled = true;
|
||||||
|
|
||||||
|
// Application variables
|
||||||
|
let pparti = -1;
|
||||||
|
let ppartBuzzed = false;
|
||||||
|
let restBuzzed = false;
|
||||||
|
|
||||||
|
let lastx = 0;
|
||||||
|
let lasty = 0;
|
||||||
|
|
||||||
|
// Mouse states
|
||||||
|
let holding = false;
|
||||||
|
let trackPadMode = false;
|
||||||
|
|
||||||
|
// Timeout IDs.
|
||||||
|
let timeoutId = -1;
|
||||||
|
let timeoutHolding = -1;
|
||||||
|
let timeoutDraw = -1;
|
||||||
|
|
||||||
|
|
||||||
|
let homeRoll = 0;
|
||||||
|
let homePitch = 0;
|
||||||
|
let mCal = 0;
|
||||||
|
let mttl = 0;
|
||||||
|
let cttl = 0;
|
||||||
|
|
||||||
|
// BT helper.
|
||||||
|
let clearToSend = true;
|
||||||
|
|
||||||
|
// Presentation Timers
|
||||||
|
let ptimers = [];
|
||||||
|
|
||||||
|
function delay(t, v) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, t)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimePart(time) {
|
||||||
|
time = Math.floor(Math.abs(time));
|
||||||
|
return time < 10 ? `0${time}` : `${time}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(time, doPlus) {
|
||||||
|
if (time == Infinity) return ' --:-- ';
|
||||||
|
return `${time < 0 ? '-' : (doPlus ? '+' : '')}${formatTimePart(time/60)}:${formatTimePart(time%60)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
settings = require("Storage").readJSON('presentor.json');
|
||||||
|
for (let i = 0; i < settings.pparts.length; i++) {
|
||||||
|
ptimers[i] = {
|
||||||
|
active: false,
|
||||||
|
tracked: -1,
|
||||||
|
left: settings.pparts[i].minutes * 60 + settings.pparts[i].seconds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentTimer() {
|
||||||
|
if (pparti < 0) return Infinity;
|
||||||
|
if (!settings.pparts || pparti >= settings.pparts.length) return Infinity;
|
||||||
|
if (ptimers[pparti].tracked == -1) return Infinity;
|
||||||
|
ptimers[pparti].left -= (getTime() - ptimers[pparti].tracked);
|
||||||
|
ptimers[pparti].tracked = getTime();
|
||||||
|
// if we haven't buzzed yet and timer became negative just buzz here.
|
||||||
|
// TODO better place?
|
||||||
|
if (ptimers[pparti].left <= 0 && !ppartBuzzed) {
|
||||||
|
Bangle.buzz(400)
|
||||||
|
.then(() => delay(400))
|
||||||
|
.then(() => Bangle.buzz(400));
|
||||||
|
ppartBuzzed = true;
|
||||||
|
}
|
||||||
|
return ptimers[pparti].left;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRestTime() {
|
||||||
|
let rem = 0;
|
||||||
|
// Add all remaining time from previous presentation parts.
|
||||||
|
for (let i = 0; i < pparti; i++) {
|
||||||
|
rem += ptimers[i].left;
|
||||||
|
}
|
||||||
|
if (pparti >= 0 && pparti < ptimers.length && ptimers[pparti].left < 0) {
|
||||||
|
rem += ptimers[pparti].left;
|
||||||
|
}
|
||||||
|
// if we haven't buzzed yet and timer became negative just buzz here.
|
||||||
|
// TODO better place?
|
||||||
|
if (rem < 0 && !restBuzzed) {
|
||||||
|
Bangle.buzz(200)
|
||||||
|
.then(() => delay(400))
|
||||||
|
.then(() => Bangle.buzz(200))
|
||||||
|
.then(() => delay(400))
|
||||||
|
.then(() => Bangle.buzz(200));
|
||||||
|
restBuzzed = true;
|
||||||
|
}
|
||||||
|
return rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawMainFrame() {
|
||||||
|
var d = new Date();
|
||||||
|
// update time
|
||||||
|
mainLayout.Time.label = Locale.time(d,1);
|
||||||
|
// update timer
|
||||||
|
mainLayout.Timer.label = formatTime(getCurrentTimer());
|
||||||
|
let restTime = getRestTime();
|
||||||
|
mainLayout.RestTime.label = formatTime(restTime, true);
|
||||||
|
mainLayout.RestTime.col = restTime < 0 ? '#f00' : (restTime > 0 ? '#0f0' : '#fff');
|
||||||
|
mainLayout.render();
|
||||||
|
// schedule a draw for the next minute
|
||||||
|
if (timeoutDraw != -1) clearTimeout(timeoutDraw);
|
||||||
|
timeoutDraw = setTimeout(function() {
|
||||||
|
timeoutDraw = -1;
|
||||||
|
drawMainFrame();
|
||||||
|
}, 1000 - (Date.now() % 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawMain() {
|
||||||
|
g.clear();
|
||||||
|
mainLayout.forgetLazyState();
|
||||||
|
drawMainFrame();
|
||||||
|
// mainLayout.render();
|
||||||
|
// E.showMessage('Presentor');
|
||||||
|
}
|
||||||
|
|
||||||
|
function doPPart(r) {
|
||||||
|
pparti += r;
|
||||||
|
if (pparti < 0) {
|
||||||
|
pparti = -1;
|
||||||
|
mainLayout.Subject.label = 'PAUSED';
|
||||||
|
mainLayout.Notes.label = 'Swipe up to start again.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!settings.pparts || pparti >= settings.pparts.length) {
|
||||||
|
pparti = settings.pparts.length;
|
||||||
|
mainLayout.Subject.label = 'FINISHED';
|
||||||
|
mainLayout.Notes.label = 'Good Job!';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ppart = settings.pparts[pparti];
|
||||||
|
mainLayout.Subject.label = ppart.subject;
|
||||||
|
mainLayout.Notes.label = ppart.notes;
|
||||||
|
ptimers[pparti].tracked = getTime();
|
||||||
|
// We haven't buzzed if there was time left.
|
||||||
|
ppartBuzzed = ptimers[pparti].left <= 0;
|
||||||
|
// Always reset buzzstate for the rest timer.
|
||||||
|
restBuzzed = getRestTime() < 0;
|
||||||
|
drawMainFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
NRF.setServices(undefined, { hid : SpecialReport });
|
||||||
|
// TODO: figure out how to detect HID.
|
||||||
|
NRF.on('HID', function() {
|
||||||
|
HIDenabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
function moveMouse(x,y,b,wheel,hwheel,callback) {
|
||||||
|
if (!HIDenabled) return;
|
||||||
|
if (!b) b = 0;
|
||||||
|
if (!wheel) wheel = 0;
|
||||||
|
if (!hwheel) hwheel = 0;
|
||||||
|
NRF.sendHIDReport([1,b,x,y,wheel,hwheel,0,0], function() {
|
||||||
|
if (callback) callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// function getSign(x) {
|
||||||
|
// return ((x > 0) - (x < 0)) || +x;
|
||||||
|
// }
|
||||||
|
|
||||||
|
function scroll(wheel,hwheel,callback) {
|
||||||
|
moveMouse(0,0,0,wheel,hwheel,callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single click a certain button (immidiatly release).
|
||||||
|
function clickMouse(b, callback) {
|
||||||
|
if (!HIDenabled) return;
|
||||||
|
NRF.sendHIDReport([1,b,0,0,0,0,0,0], function() {
|
||||||
|
NRF.sendHIDReport([1,0,0,0,0,0,0,0], function() {
|
||||||
|
if (callback) callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function pressKey(keyCode, modifiers, callback) {
|
||||||
|
if (!HIDenabled) return;
|
||||||
|
if (!modifiers) modifiers = 0;
|
||||||
|
NRF.sendHIDReport([2, modifiers,0,keyCode,0,0,0,0], function() {
|
||||||
|
NRF.sendHIDReport([2,0,0,0,0,0,0,0], function() {
|
||||||
|
if (callback) callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAcc(acc) {
|
||||||
|
let rRoll = acc.y * -50;
|
||||||
|
let rPitch = acc.x * -100;
|
||||||
|
if (mCal > 10) {
|
||||||
|
//console.log("x: " + (rRoll - homeRoll) + " y:" + (rPitch - homePitch));
|
||||||
|
moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch);
|
||||||
|
} else {
|
||||||
|
//console.log("homeroll: " +homeRoll +"homepitch: " + homePitch);
|
||||||
|
homeRoll = rRoll * 0.7 + homeRoll * 0.3;
|
||||||
|
homePitch = rPitch * 0.7 + homePitch * 0.3;
|
||||||
|
mCal = mCal + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Bangle.on('lock', function(on) {
|
||||||
|
if (on && holding) {
|
||||||
|
Bangle.setLocked(false);
|
||||||
|
Bangle.setLCDPower(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function startHolding() {
|
||||||
|
pressKey(kb.KEY.F10);
|
||||||
|
holding = true;
|
||||||
|
Bangle.buzz();
|
||||||
|
E.showMessage('Holding');
|
||||||
|
Bangle.on('accel', handleAcc);
|
||||||
|
Bangle.setLCDPower(1);
|
||||||
|
}
|
||||||
|
function stopHolding() {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
if (holding) {
|
||||||
|
pressKey(kb.KEY.F10);
|
||||||
|
homePitch = 0;
|
||||||
|
homeRoll = 0;
|
||||||
|
holding = false;
|
||||||
|
mCal = 0;
|
||||||
|
Bangle.removeListener('accel', handleAcc);
|
||||||
|
Bangle.buzz();
|
||||||
|
drawMain();
|
||||||
|
} else {
|
||||||
|
timeoutId = setTimeout(drawMain, 1000);
|
||||||
|
}
|
||||||
|
clearTimeout(timeoutHolding);
|
||||||
|
timeoutHolding = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on('drag', function(e) {
|
||||||
|
if (cttl == 0) { cttl = getTime(); }
|
||||||
|
if (trackPadMode) {
|
||||||
|
if (lastx + lasty == 0) {
|
||||||
|
lastx = e.x;
|
||||||
|
lasty = e.y;
|
||||||
|
mttl = getTime();
|
||||||
|
}
|
||||||
|
if (clearToSend) {
|
||||||
|
clearToSend = false;
|
||||||
|
let difX = e.x - lastx, difY = e.y - lasty;
|
||||||
|
let dT = getTime() - mttl;
|
||||||
|
let vX = difX / dT, vY = difY / dT;
|
||||||
|
//let qX = getSign(difX) * Math.pow(Math.abs(difX), 1.2);
|
||||||
|
//let qY = getSign(difY) * Math.pow(Math.abs(difY), 1.2);
|
||||||
|
let qX = difX + 0.02 * vX, qY = difY + 0.02 * vY;
|
||||||
|
moveMouse(qX, qY, 0, 0, 0, function() {
|
||||||
|
setTimeout(function() {clearToSend = true;}, 50);
|
||||||
|
});
|
||||||
|
lastx = e.x;
|
||||||
|
lasty = e.y;
|
||||||
|
mttl = getTime();
|
||||||
|
console.log("Dx: " + (qX) + " Dy: " + (qY));
|
||||||
|
}
|
||||||
|
if (!e.b) {
|
||||||
|
// short press
|
||||||
|
if (getTime() - cttl < 0.2) {
|
||||||
|
clickMouse(MouseButton.LEFT);
|
||||||
|
console.log("click left");
|
||||||
|
}
|
||||||
|
// longer press in center
|
||||||
|
else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) {
|
||||||
|
clickMouse(MouseButton.RIGHT);
|
||||||
|
console.log("click right");
|
||||||
|
}
|
||||||
|
cttl = 0;
|
||||||
|
lastx = 0;
|
||||||
|
lasty = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(!e.b){
|
||||||
|
Bangle.buzz(100);
|
||||||
|
if(lasty > 40){
|
||||||
|
doPPart(-1);
|
||||||
|
// E.showMessage('down');
|
||||||
|
} else if(lasty < -40){
|
||||||
|
doPPart(1);
|
||||||
|
// E.showMessage('up');
|
||||||
|
} else if(lastx > 40){
|
||||||
|
// E.showMessage('right');
|
||||||
|
//kb.tap(kb.KEY.RIGHT, 0);
|
||||||
|
scroll(-1);
|
||||||
|
} else if(lastx < -40){
|
||||||
|
// E.showMessage('left');
|
||||||
|
//kb.tap(kb.KEY.LEFT, 0);
|
||||||
|
scroll(1);
|
||||||
|
} else if(lastx==0 && lasty==0 && holding == false){
|
||||||
|
// E.showMessage('press');
|
||||||
|
clickMouse(MouseButton.LEFT);
|
||||||
|
}
|
||||||
|
stopHolding();
|
||||||
|
lastx = 0;
|
||||||
|
lasty = 0;
|
||||||
|
} else{
|
||||||
|
lastx = lastx + e.dx;
|
||||||
|
lasty = lasty + e.dy;
|
||||||
|
if (timeoutHolding == -1) {
|
||||||
|
timeoutHolding = setTimeout(startHolding, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function onBtn() {
|
||||||
|
if (trackPadMode) {
|
||||||
|
trackPadMode = false;
|
||||||
|
stopHolding();
|
||||||
|
drawMain();
|
||||||
|
} else {
|
||||||
|
clearToSend = true;
|
||||||
|
trackPadMode = true;
|
||||||
|
E.showMessage('Mouse');
|
||||||
|
}
|
||||||
|
Bangle.buzz();
|
||||||
|
}
|
||||||
|
setWatch(onBtn, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat: true});
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
drawMain();
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,285 @@
|
||||||
|
<!-- Presentor by 7kasper (Kasper Müller) -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.qcent {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.iwrap {
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
.iwrap img {
|
||||||
|
height: 100%;
|
||||||
|
padding: 10%;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
height: 2px;
|
||||||
|
background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
|
||||||
|
background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
|
||||||
|
background-image: -ms-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
|
||||||
|
background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
|
||||||
|
}
|
||||||
|
.fullbtn button {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
.ppartrow {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
.timerselector {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
inset: 1px solid black;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
.timerselector input {
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
.pp-order {
|
||||||
|
width: calc(150% / 14);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.pp-order-r {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
.pp-subject {
|
||||||
|
width: calc(300% / 14);
|
||||||
|
}
|
||||||
|
.pp-timer {
|
||||||
|
width: calc(250% / 14);
|
||||||
|
}
|
||||||
|
.pp-notes {
|
||||||
|
width: calc(700% / 14);
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
.icon-cross {
|
||||||
|
color: #e85600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.icon-cross:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
.draghandle {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="qcent">
|
||||||
|
<h1>Presentor</h1>
|
||||||
|
<div class="iwrap"><img src="app.png"/></div>
|
||||||
|
</header>
|
||||||
|
<hr/>
|
||||||
|
<div class="form-group" id="subber">
|
||||||
|
<div id="pparthdr" class="ppartrow">
|
||||||
|
<div class="pp-order">#</div>
|
||||||
|
<div class="pp-subject">Subject</div>
|
||||||
|
<div class="pp-timer">Time</div>
|
||||||
|
<div class="pp-notes">Notes</div>
|
||||||
|
</div>
|
||||||
|
<div id="loader">Loading...</div>
|
||||||
|
<div id="subber-data"></div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<div class="qcent fullbtn">
|
||||||
|
<button class="btn btn-default" id="btnSave">Save</button>
|
||||||
|
<button class="btn btn-error" id="btnClear">Clear</button>
|
||||||
|
</div>
|
||||||
|
<footer>Presentor by <a href="https://kaspermuller.nl" target="_blank">Kasper Müller</a></footer>
|
||||||
|
|
||||||
|
<script src="../../core/lib/interface.js"></script>
|
||||||
|
<script>
|
||||||
|
const subber = document.getElementById('subber-data');
|
||||||
|
let cmpStr = ''; //compare string to see if there are changes with previous save.
|
||||||
|
|
||||||
|
function ppartsAreFilled() {
|
||||||
|
let pparts = subber.children;
|
||||||
|
for (let i = 0; i < pparts.length; i++) {
|
||||||
|
if (!pparts[i].getElementsByClassName('pp-subject')[0].value) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getJSON() {
|
||||||
|
let ret = {
|
||||||
|
pparts: [],
|
||||||
|
sversion: 2.2
|
||||||
|
}
|
||||||
|
let jparts = [];
|
||||||
|
let pparts = subber.children;
|
||||||
|
for (let i = 0; i < pparts.length; i++) {
|
||||||
|
let rpart = {};
|
||||||
|
let ppart = pparts[i];
|
||||||
|
rpart.subject = ppart.getElementsByClassName('pp-subject')[0].value;
|
||||||
|
if (!rpart.subject) continue;
|
||||||
|
rpart.minutes = ppart.getElementsByClassName('pp-timer')[0].children[0].value | 0;
|
||||||
|
rpart.seconds = ppart.getElementsByClassName('pp-timer')[0].children[2].value | 0;
|
||||||
|
rpart.notes = ppart.getElementsByClassName('pp-notes')[0].value;
|
||||||
|
jparts.push(rpart);
|
||||||
|
}
|
||||||
|
ret.pparts = jparts;
|
||||||
|
return JSON.stringify(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
let savestr = getJSON();
|
||||||
|
Util.showModal('Saving...');
|
||||||
|
Puck.write(`\x10require('Storage').writeJSON(${JSON.stringify('presentor.json')},${savestr})\n`,()=>{
|
||||||
|
Util.hideModal();
|
||||||
|
});
|
||||||
|
cmpStr = savestr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadJSON(str) {
|
||||||
|
cmpStr = str;
|
||||||
|
let settings = JSON.parse(str);
|
||||||
|
let jparts = settings.pparts;
|
||||||
|
for (let i = 0; i < jparts.length; i++) {
|
||||||
|
addFormPPart(jparts[i]);
|
||||||
|
}
|
||||||
|
addFormPPart(); // add empty element on startup
|
||||||
|
}
|
||||||
|
|
||||||
|
function load() {
|
||||||
|
Util.showModal('Loading...');
|
||||||
|
Puck.eval(`require('Storage').read(${JSON.stringify('presentor.json')})`,data => {
|
||||||
|
Util.hideModal();
|
||||||
|
loadJSON(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFormPPart(partData = {}) {
|
||||||
|
let part = document.createElement('div');
|
||||||
|
part.classList.add('ppartrow');
|
||||||
|
|
||||||
|
let orderThing = document.createElement('div');
|
||||||
|
orderThing.classList.add('pp-order', 'pp-order-r');
|
||||||
|
let orderItem = document.createElement('i');
|
||||||
|
orderItem.classList.add('icon', 'icon-menu', 'draghandle');
|
||||||
|
orderThing.appendChild(orderItem);
|
||||||
|
let deleteItem = document.createElement('i');
|
||||||
|
deleteItem.classList.add('icon', 'icon-cross');
|
||||||
|
deleteItem.onclick = () => {
|
||||||
|
subber.removeChild(part);
|
||||||
|
// Make sure there stays one form thing left.
|
||||||
|
if (!subber.hasChildNodes()) addFormPPart();
|
||||||
|
}
|
||||||
|
orderThing.appendChild(deleteItem);
|
||||||
|
part.appendChild(orderThing);
|
||||||
|
|
||||||
|
let subjectField = document.createElement('input');
|
||||||
|
subjectField.classList.add('pp-subject');
|
||||||
|
subjectField.type = 'text';
|
||||||
|
subjectField.placeholder = 'Subject';
|
||||||
|
if (partData.subject) subjectField.value = partData.subject;
|
||||||
|
part.appendChild(subjectField);
|
||||||
|
|
||||||
|
let timeField = document.createElement('div');
|
||||||
|
timeField.classList.add('pp-timer', 'timerselector');
|
||||||
|
let minSelector = document.createElement('input');
|
||||||
|
minSelector.type = 'number';
|
||||||
|
minSelector.min = 0;
|
||||||
|
minSelector.step = 1
|
||||||
|
minSelector.placeholder = 'mm';
|
||||||
|
if (partData.minutes !== undefined) minSelector.value = partData.minutes;
|
||||||
|
let colon = document.createElement('p');
|
||||||
|
colon.innerText = ':';
|
||||||
|
let secSelector = document.createElement('input');
|
||||||
|
secSelector.type = 'number';
|
||||||
|
secSelector.min = 0;
|
||||||
|
secSelector.max = 59;
|
||||||
|
secSelector.step = 1
|
||||||
|
secSelector.placeholder = 'ss';
|
||||||
|
if (partData.seconds !== undefined) secSelector.value = partData.seconds;
|
||||||
|
timeField.appendChild(minSelector);
|
||||||
|
timeField.appendChild(colon);
|
||||||
|
timeField.appendChild(secSelector);
|
||||||
|
part.appendChild(timeField);
|
||||||
|
|
||||||
|
let notesField = document.createElement('input');
|
||||||
|
notesField.classList.add('pp-notes');
|
||||||
|
notesField.type = 'text';
|
||||||
|
notesField.placeholder = 'Notes (optional)';
|
||||||
|
if (partData.notes !== undefined) notesField.value = partData.notes;
|
||||||
|
part.appendChild(notesField);
|
||||||
|
|
||||||
|
subber.appendChild(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sortable.create(subber, {
|
||||||
|
handle: '.draghandle',
|
||||||
|
animation: 150
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btnSave').onclick = () => {
|
||||||
|
save();
|
||||||
|
alert(getJSON());
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('btnClear').onclick = () => {
|
||||||
|
while (subber.firstChild) {
|
||||||
|
subber.removeChild(subber.lastChild);
|
||||||
|
}
|
||||||
|
addFormPPart();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keyup', () => {
|
||||||
|
if (ppartsAreFilled()) {
|
||||||
|
addFormPPart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simple thing to check if we need to save (TODO optimise me in the future?)
|
||||||
|
setInterval(() => {
|
||||||
|
if (cmpStr == getJSON()) {
|
||||||
|
document.getElementById('btnSave').classList.remove('btn-primary');
|
||||||
|
document.getElementById('btnSave').classList.add('btn-default');
|
||||||
|
} else {
|
||||||
|
document.getElementById('btnSave').classList.remove('btn-default');
|
||||||
|
document.getElementById('btnSave').classList.add('btn-primary');
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
document.getElementById('loader').style.display = 'none';
|
||||||
|
|
||||||
|
function onInit() {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
// load from watch first.
|
||||||
|
// let qq = `[{"subject":"Hello","minutes":55,"seconds":4,"notes":""},{"subject":"dsfafds","minutes":4,"seconds":33,"notes":"fdasdfsafasfsd"},{"subject":"dsadsf","minutes":0,"seconds":4,"notes":""},{"subject":"sdasf","minutes":0,"seconds":0,"notes":""}]`;
|
||||||
|
// loadJSON(qq);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
{"pparts":[{"subject":"#1","minutes":10,"seconds":0,"notes":"This is a note."},{"subject":"#2","minutes":2,"seconds":50,"notes":"Change in the app!"}],"sversion":2.2}
|
|
@ -0,0 +1,5 @@
|
||||||
|
0.01: Initial version, UI mechanics ready, no real game play so far
|
||||||
|
0.02: Lots of enhancements, menu system not yet functional, but packaging should be now...
|
||||||
|
0.03: Menu logic now generally functioning, splash screen added. The first really playable version!
|
||||||
|
0.04: Settings dialog, about screen
|
||||||
|
0.05: Central game end function
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Puzzle15 - A 15-puzzle for the Bangle.js 2
|
||||||
|
|
||||||
|
This is a Bangle.js 2 adoption of the famous 15 puzzle.
|
||||||
|
|
||||||
|
## The game
|
||||||
|
|
||||||
|
A board of _n_ by _n_ fields is filled with _n^2-1_ numbered stones. So, one field, the "gap", is left free.
|
||||||
|
|
||||||
|
Bring them in the correct order so that the gap is finally at the bottom right of the playing field.
|
||||||
|
The less moves you need, the better you are.
|
||||||
|
|
||||||
|
If _n_ is 4, the number of stones is _16-1=15_. Hence the name of the game.
|
||||||
|
|
||||||
|
## How to play
|
||||||
|
|
||||||
|
If you start the game, it shows a splash screen and then generates a shuffled 4x4 board with a 15 puzzle.
|
||||||
|
Move the stones with drag gestures on the screen.
|
||||||
|
If you want to move the stone below the gap upward, drag from the bottom of the screen upward.
|
||||||
|
The drag gestures can be performed anywhere on the screen, there is no need to start or end them on the stone to be moved.
|
||||||
|
|
||||||
|
If you managed to order the stones correctly, a success message appears.
|
||||||
|
You can continue with another game, go to the game's main menu, or quit the game entirely.
|
||||||
|
|
||||||
|
There is a grey menu button right of the board containing the well-known three-bar menu symbol ("Hamburger menu").
|
||||||
|
It opens the game's main menu directly from within the game.
|
||||||
|
|
||||||
|
## The main menu
|
||||||
|
|
||||||
|
Puzzle15 has a main menu which can be reached from the in-game menu button or the end-of-game message window.
|
||||||
|
It features the following options:
|
||||||
|
|
||||||
|
* **Continue** - Continue the currently running game. _This option is only shown if the main menu is opened during an open game._
|
||||||
|
* **Start 3x3**, **Start 4x4**, **Start 5x5** - Start a new game on a board with the respective dimension. Any currently open game is dropped.
|
||||||
|
* **About** Show a small "About" info box.
|
||||||
|
* **Exit** Exit Puzzle15 and return to the default watch face.
|
||||||
|
|
||||||
|
## Game settings
|
||||||
|
|
||||||
|
The game has some global settings which can be accessed on the usual way through the Bangle.js' app settings user interface.
|
||||||
|
Currently it has the following options:
|
||||||
|
|
||||||
|
* **Splash** - Define whether the game should open with a splash screen. **long** shows the splash screen for five seconds, **short** shows it for two seconds. **off** starts the app _without_ a splash screen, it directly comes up with whatever the "Start with" option says.
|
||||||
|
* **Start with** - What should happen after the splash screen (or, if it is disabled, directly at app start): **3x3**, **4x4** and **5x5** start the game with a board of the respective dimension, **menu** shows the main menu which allows to select the board size.
|
||||||
|
|
||||||
|
## Implementation notes
|
||||||
|
|
||||||
|
The game engine always generates puzzles which can be solved.
|
||||||
|
|
||||||
|
Solvability is detected by counting inversions,
|
||||||
|
i.e. pairs of stones where the stone at the earlier field (row-wise, left to right, top to bottom) has a number _greater than_ the stone on the later field, with all pairs of stones compared.
|
||||||
|
The algorithm is described at https://www.geeksforgeeks.org/check-instance-15-puzzle-solvable/ .
|
||||||
|
|
||||||
|
## The splash screen
|
||||||
|
|
||||||
|
The Splash screen shows a part of the illustration "The 14-15-puzzle in puzzleland" from Sam Loyd. Other than Puzzle15, it depicts a 15 puzzle with the stones "14" and "15" swapped. This puzzle is indeed *not* solvable.
|
||||||
|
|
||||||
|
Have fun!
|
After Width: | Height: | Size: 3.6 KiB |
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwgn/AC3+7oAD7e7AAW8BQndBQe79/9DomgHocH74KD/RJE34Xax4XDtvoC4fJ54XDluAC4f2z4XDzm/C4ett4XD34OBF4e/I4m+C4f8r4XChHuC5U98oXEF4cP7/AC5O9mYXC/2/F4cGtwvE/SsBC4Ws7gvD7YCBL4ULO4i/u1QAD7QED1e6AoetCAnf/YeE1wpD/lgBQcKIAgXG14LD/twC5kL3Z+BC4P+LgIXBg272wXD7wXEh7eCC4PWzIXChHtOoIXB/WX54XDh3KmAXC1oLBI4UD+AXC+/rdIIvD5wvD3O4C4cJ4AXC/dUI4kJhgMBC4Ov+AXDh9QC4X2/gvEhvvoAXC81dC4duR4f8wSncC6v8u4AD3ndAAXcy4KDtYKD7vf/oGE2wRDvPNBQfLFAnP/o2EVIIACg7yBAATZBAAe/C7P9g4XCx+wn/6C4Op//AC4MK+cI/+QC4X2/fPC4PM2HKh8H7vpewIXBhvThV5+AXC+/5C4UL2HHC4Pf/P/AIJHB6cAj2wC4X+3AXPhADBF4fX94XB1va1vOC4PXAIX6hfrxvb0CPD7p3C1e6hW2C4LOBAIIXB3eJ3YXEX78GM4IAC9QXG1QAD7QEDJYIFD14oE//7DwgME/twBQcPC70G6EG5dQ1/8VYPtC4ObgfM5IXHr/whvO4Gvy6LBtX9vfugnr3AXHkXggGOC4P97/43X9ukOgnv6BfIC4Oe2AXC6+nI4MOgfI9QXJhssF4f91AXCgnA9IXHr3u1HusGv3Ob//s/t693l3xHJX9v+3YAD7oAE5YKD34XFAC4="))
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,50 @@
|
||||||
|
// Settings menu for the Puzzle15 app
|
||||||
|
|
||||||
|
(function(back) {
|
||||||
|
var FILE = "puzzle15.json";
|
||||||
|
// Load settings
|
||||||
|
var settings = Object.assign({
|
||||||
|
splashMode: "long",
|
||||||
|
startWith: "4x4"
|
||||||
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method which uses int-based menu item for set of string values
|
||||||
|
function stringItems(startvalue, writer, values) {
|
||||||
|
return {
|
||||||
|
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||||
|
format: v => values[v],
|
||||||
|
min: 0,
|
||||||
|
max: values.length - 1,
|
||||||
|
wrap: true,
|
||||||
|
step: 1,
|
||||||
|
onchange: v => {
|
||||||
|
writer(values[v]);
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method which breaks string set settings down to local settings object
|
||||||
|
function stringInSettings(name, values) {
|
||||||
|
return stringItems(settings[name], v => settings[name] = v, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mainmenu = {
|
||||||
|
"": {
|
||||||
|
"title": "15 Puzzle"
|
||||||
|
},
|
||||||
|
"< Back": () => back(),
|
||||||
|
"Splash": stringInSettings("splashMode", ["long", "short", "off"]),
|
||||||
|
"Start with": stringInSettings("startWith", ["3x3", "4x4", "5x5", "menu"])
|
||||||
|
};
|
||||||
|
|
||||||
|
// Actually display the menu
|
||||||
|
E.showMenu(mainmenu);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// end of file
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -1,3 +1,4 @@
|
||||||
0.01: First release
|
0.01: First release
|
||||||
0.02: Fix typo to Purple
|
0.02: Fix typo to Purple
|
||||||
0.03: Added dependancy on Pedometer Widget
|
0.03: Added dependancy on Pedometer Widget
|
||||||
|
0.04: Fixed icon and png to 48x48 pixels
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
* Uses pedometer widget to get latest step count
|
* Uses pedometer widget to get latest step count
|
||||||
* Dependant apps are installed when Rebble installs
|
* Dependant apps are installed when Rebble installs
|
||||||
* Uses the whole screen, widgets are made invisible but still run in the background
|
* Uses the whole screen, widgets are made invisible but still run in the background
|
||||||
|
* The icon is James Dean - 'Rebel Without a Cause'
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("oFA4X/AAIHBw3Aiv3HmE/HQQAF/gPEnWqAAOpy2VqoFB3gPIBoIABtQPJ1PVqv1q3qB5OlrNVEIQPK2tlBwOptQPIyvdH4VtrQPI3tbqtdB4OaB5FVH4NV0pgBB5F13//MIIPJ1O2TgWV/o/I1fbB4WpqoPI1NvB4REBJ5APD/wPBD5JOBB4WVqwPH0oPE0oPJ/NX//6AoNVF5HZq3pq2qSYIPI6tX+pNBB5Ol6v6B4IABH5P7//b1oPBN5GlLwPr9IPK1IPC/SvK1QPCOAIPL6te//5B5lW/5ABL5APB/wPB3IPJ1Y/C/yuBF5APC9X+yo/K34LB3QPBtQPJ//23SPB1QPI3eVs2qJwIPJ1flqyeBtQPJtZPBLwIPKzf/1ROCB5OWAQJOBB5QsBAAQGBf5FlB5tVvoPMNQO9B4daB5O+B4aPIqtX35tBB5M1qtbB4i/HB4WvOAjvGB4IpBIQIADB46aBB4t8B49VB54AFB6zrB1Wm1RTBywPI0oPCeQOaB4+ltOlq2V02VqwPOrQPIF5w/PFQIvPB71pH4uqX8g"))
|
require("heatshrink").decompress(atob("mEw4X/AoOG4EV+/I+dVAAVUCgcFBIYABpIJBgcFoIKEqkQgEH6EH0ILEqAhCgkBqEVBYdAhUBBoU9GAlAlw5CgERgILDIocEgEGoALDlEHwEAlkUg8EBYfAFwVA+BgEqmQjWrBgMQhgvDqmA9Wq1WsNoMALweDBQIAB4E8BYdTpwLD/kA4AXDjwKC1f/IAILDnQLC1//4ALEHQQLCKgILDFwYLB6EATgVABYe///MNgdA3kQEoILGqCNBlfQh//4NAPAVQ+YLBQYM/ocABYfAiEqgE0g6DBF4eAlFrYQZHDoOu1Xo8lgBYtCKIOo9aOBAAJrCBYWv9X/+gXEqSZC/f//4LHz/6DQIjEBYOhgG6BY1a1WggDCB3ojErYTBoEOa4QLF1X9jWrXwILGKYOvBYtfKYX+17iBHYdX1WQgf/34LBUwQLB1cLWIJqCBYdV9W+1+//oLBWQVVqnuD4M/KQoAB/+kBYJGBCwYLCI4P/DQILFnwLCEQ1Vp+q/46CBYtDXgJ1FAAVwfI4ABqAUCBY8A9gLIqEA9ALEKYYLB9YLERwQ="))
|
||||||
|
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.0 KiB |
|
@ -42,3 +42,4 @@
|
||||||
0.37: Going into passkey menu now saves settings with passkey
|
0.37: Going into passkey menu now saves settings with passkey
|
||||||
0.38: Restructed menus as per forum discussion
|
0.38: Restructed menus as per forum discussion
|
||||||
0.39: Fix misbehaving debug info option
|
0.39: Fix misbehaving debug info option
|
||||||
|
0.40: Moved off into Utils, put System after Apps
|
||||||
|
|
|
@ -66,11 +66,10 @@ function showMainMenu() {
|
||||||
'': { 'title': 'Settings' },
|
'': { 'title': 'Settings' },
|
||||||
'< Back': ()=>load(),
|
'< Back': ()=>load(),
|
||||||
/*LANG*/'Apps': ()=>showAppSettingsMenu(),
|
/*LANG*/'Apps': ()=>showAppSettingsMenu(),
|
||||||
/*LANG*/'Bluetooth': ()=>showBLEMenu(),
|
|
||||||
/*LANG*/'System': ()=>showSystemMenu(),
|
/*LANG*/'System': ()=>showSystemMenu(),
|
||||||
|
/*LANG*/'Bluetooth': ()=>showBLEMenu(),
|
||||||
/*LANG*/'Alerts': ()=>showAlertsMenu(),
|
/*LANG*/'Alerts': ()=>showAlertsMenu(),
|
||||||
/*LANG*/'Utils': ()=>showUtilMenu(),
|
/*LANG*/'Utils': ()=>showUtilMenu()
|
||||||
/*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return E.showMenu(mainmenu);
|
return E.showMenu(mainmenu);
|
||||||
|
@ -537,7 +536,8 @@ function showUtilMenu() {
|
||||||
setTimeout(showMainMenu, 50);
|
setTimeout(showMainMenu, 50);
|
||||||
} else showUtilMenu();
|
} else showUtilMenu();
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
/*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }
|
||||||
};
|
};
|
||||||
if (Bangle.factoryReset) {
|
if (Bangle.factoryReset) {
|
||||||
menu['Factory Reset'] = ()=>{
|
menu['Factory Reset'] = ()=>{
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
1.00 Added sonic clock app
|
||||||
|
1.01 Fixed text alignment issue; Increased acceleration required to activate twist;
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Sonic Clock
|
||||||
|
|
||||||
|
A classic sonic clock featuring run, stop and wait animations.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- Sonic will run when the screen is unlocked
|
||||||
|
- Sonic will stop when the screen is locked
|
||||||
|
- Sonic will wait when looking at your watch face (when `Bangle.on("twist", fn)` is fired).
|
||||||
|
|
||||||
|
### Made with love by [Joseph](https://github.com/Johoseph) 🤗
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkBiIA/AE0ZzIACBIgFFC7oTCylEzOIDYeZogX6LwWd7oYCAAOJC82UpoXFAAKnMC6x2CpIVFC/gSCxOYAYP///4C4X/DBIXWIIwWBDAWPAYIXedQJwG/8AgEP//wgAX2CwIADRoQXmSIoXtJAeEoi+C+lEoAcBogX7zOUondolEpvdAAQXgYIgXCAAwXlAAIYC6ENLx4XtAYMZDAvd6gWJC7IKJABgX/C74A/ADY"))
|
After Width: | Height: | Size: 372 B |
After Width: | Height: | Size: 6.4 KiB |
|
@ -0,0 +1 @@
|
||||||
|
0.01: App launched
|
|
@ -0,0 +1,40 @@
|
||||||
|
# TouchMenu
|
||||||
|
|
||||||
|
A redesign of the built-in `E.showMenu()` to take advantage of the full touch screen on the Bangle.js 2.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- All of the features of the built-in `E.showMenu()`
|
||||||
|
- Icon support for menu items:
|
||||||
|
```javascript
|
||||||
|
menu.items[0].icon = Graphics.createImage(...);
|
||||||
|
```
|
||||||
|
- Custom accent colors:
|
||||||
|
```javascript
|
||||||
|
E.showMenu({
|
||||||
|
"": {
|
||||||
|
cAB: g.theme.bg2, // Accent background
|
||||||
|
cAF: g.theme.fg2 // Accent foreground
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
- Automatic back button detection - name a button `< Back` and it will be given a special position and icon
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
- Scroll through the options
|
||||||
|
- Tap on an option to select it
|
||||||
|
- Tap on a button again to use it
|
||||||
|
- Tap on a selected Boolean to toggle it
|
||||||
|
- Tap on a selected number to change - tap the right side of the screen to decrease, left side to increase
|
||||||
|
- If detected, tap on the back button in the upper left to go back
|
||||||
|
|
||||||
|
## Requests
|
||||||
|
|
||||||
|
Contact information is on my website: [kyleplo](https://kyleplo.com)
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
[kyleplo](https://kyleplo.com)
|
|
@ -0,0 +1,197 @@
|
||||||
|
E.showMenu = function(items) {
|
||||||
|
const gw = g.getWidth();
|
||||||
|
const gh = g.getHeight();
|
||||||
|
Bangle.removeAllListeners("drag");
|
||||||
|
if(!items){
|
||||||
|
delete m;
|
||||||
|
g.clearRect(0, 30, gw, gh - 30);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var loc = require("locale");
|
||||||
|
var m = {
|
||||||
|
info: {
|
||||||
|
title: "Menu",
|
||||||
|
cB: g.theme.bg,
|
||||||
|
cF: g.theme.fg,
|
||||||
|
cHB: g.theme.bgH,
|
||||||
|
cHF: g.theme.fgH,
|
||||||
|
cAB: g.theme.bg2,
|
||||||
|
cAF: g.theme.fg2,
|
||||||
|
predraw : () => {},
|
||||||
|
preflip : () => {}
|
||||||
|
},
|
||||||
|
scroll: 0,
|
||||||
|
items: [],
|
||||||
|
selected: -1,
|
||||||
|
draw: () => {
|
||||||
|
g.reset().setFont('12x20');
|
||||||
|
m.info.predraw(g);
|
||||||
|
g.setColor(m.info.cB).fillRect(0, 50, gw, gh - 30).setColor(m.info.cF);
|
||||||
|
m.items.forEach((e, i) => {
|
||||||
|
const s = (i * 48) - m.scroll + 50;
|
||||||
|
if(s < 30 || s > gh - 74){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(i == m.selected){
|
||||||
|
g.setColor(m.info.cHB).fillRect(0, s, gw, Math.min(s + 48, gh - 30)).setColor(m.info.cHF);
|
||||||
|
}else{
|
||||||
|
g.setColor(m.info.cF);
|
||||||
|
}
|
||||||
|
g.drawString(e.title, (e.icon ? 30 : 10), s + 5);
|
||||||
|
if(e.icon){
|
||||||
|
g.drawImage(e.icon, 5, s + 5);
|
||||||
|
}
|
||||||
|
if(e.type && s < gh - 72){
|
||||||
|
if(e.format){
|
||||||
|
g.setFontAlign(1, -1, 0).drawString(e.format(e.value), gw - 10, s + 25).setFontAlign(-1, -1, 0);
|
||||||
|
}else{
|
||||||
|
g.setFontAlign(1, -1, 0).drawString(e.value, gw - 10, s + 25).setFontAlign(-1, -1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
g.setColor(m.info.cAB).fillRect(0, 30, gw, 50);
|
||||||
|
g.setColor(m.info.cAF).drawString(m.info.title, (m.back ? 30 : 10), 32);
|
||||||
|
if(m.back){
|
||||||
|
g.drawLine(5, 40, 20, 40);
|
||||||
|
g.drawLine(5, 40, 15, 33);
|
||||||
|
g.drawLine(5, 40, 15, 47);
|
||||||
|
}
|
||||||
|
m.info.preflip(g, m.scroll > 0, m.scroll < (m.items.length - 1) * 48);
|
||||||
|
},
|
||||||
|
select: (x, y) => {
|
||||||
|
if(m.selected == -1 || m.selected !== Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0)){
|
||||||
|
if(y){
|
||||||
|
if(y < 50 || y > gh - 30){
|
||||||
|
return false;
|
||||||
|
}else{
|
||||||
|
m.selected = Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
m.selected = Math.floor(m.scroll / 48);
|
||||||
|
}
|
||||||
|
m.draw();
|
||||||
|
}else{
|
||||||
|
if(m.items[m.selected].type && m.items[m.selected].type === "boolean"){
|
||||||
|
m.items[m.selected].value = !m.items[m.selected].value;
|
||||||
|
m.items[m.selected].onchange(m.items[m.selected].value);
|
||||||
|
m.draw();
|
||||||
|
}else if(m.items[m.selected].type && m.items[m.selected].type === "number"){
|
||||||
|
if(x && x < (gw / 2)){
|
||||||
|
m.items[m.selected].value = m.items[m.selected].value - (m.items[m.selected].step ? m.items[m.selected].step : 1);
|
||||||
|
}else{
|
||||||
|
m.items[m.selected].value = m.items[m.selected].value + (m.items[m.selected].step ? m.items[m.selected].step : 1);
|
||||||
|
}
|
||||||
|
if(m.items[m.selected].value > (m.items[m.selected].max ? m.items[m.selected].max : Infinity)){
|
||||||
|
m.items[m.selected].value = m.items[m.selected].min ? m.items[m.selected].min : 0;
|
||||||
|
}
|
||||||
|
if(m.items[m.selected].value < (m.items[m.selected].min ? m.items[m.selected].min : 0)){
|
||||||
|
m.items[m.selected].value = m.items[m.selected].max ? m.items[m.selected].max : 10;
|
||||||
|
}
|
||||||
|
m.items[m.selected].onchange(m.items[m.selected].value);
|
||||||
|
m.draw();
|
||||||
|
}else{
|
||||||
|
if(m.items[m.selected]){
|
||||||
|
m.items[m.selected]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
move: d => {
|
||||||
|
m.scroll += (d * 48);
|
||||||
|
m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48);
|
||||||
|
m.selected = Math.max(Math.min(Math.floor((m.scroll - 50) / 48), m.items.length - 1), 0);
|
||||||
|
m.draw();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Object.keys(items).forEach(i => {
|
||||||
|
if(i == ""){
|
||||||
|
m.info = Object.assign(m.info, items[i]);
|
||||||
|
}else if(i === "< Back" && items[i]){
|
||||||
|
m.back = items[i];
|
||||||
|
}else if(items[i]){
|
||||||
|
m.items.push(items[i]);
|
||||||
|
m.items[m.items.length - 1].title = loc.translate(i);
|
||||||
|
if(items[i].hasOwnProperty("value")){
|
||||||
|
if(typeof items[i].value === "boolean"){
|
||||||
|
m.items[m.items.length - 1].type = "boolean";
|
||||||
|
}else{
|
||||||
|
m.items[m.items.length - 1].type = "number";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m.info.title = loc.translate(m.info.title);
|
||||||
|
m.draw();
|
||||||
|
Bangle.on("drag", d => {
|
||||||
|
if(!d.b){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(d.dx == 0 && d.dy == 0){
|
||||||
|
if(d.x < 30 && d.y < 50){
|
||||||
|
m.back();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m.select(d.x, d.y);
|
||||||
|
}else{
|
||||||
|
m.selected = -1;
|
||||||
|
m.scroll -= d.dy;
|
||||||
|
m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48);
|
||||||
|
m.draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
|
||||||
|
E.showAlert = function (e, t){
|
||||||
|
if(!e){
|
||||||
|
E.showMenu();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return new Promise(r => {
|
||||||
|
const menu = {
|
||||||
|
"": {
|
||||||
|
"title": (t ? t : "Alert")
|
||||||
|
},
|
||||||
|
Ok: () => {
|
||||||
|
E.showMenu();
|
||||||
|
r();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
menu[e] = () => {};
|
||||||
|
E.showMenu(menu);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
E.showMessage = E.showAlert;
|
||||||
|
|
||||||
|
E.showPrompt = function (e, t){
|
||||||
|
if(!e){
|
||||||
|
E.showMenu();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return new Promise(r => {
|
||||||
|
const menu = {
|
||||||
|
"": {
|
||||||
|
"title": (t && t.title ? t.title : "Choose")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
menu[e] = () => {};
|
||||||
|
if(t && t.buttons){
|
||||||
|
Object.keys(t.buttons).forEach(b => {
|
||||||
|
menu[b] = () => {
|
||||||
|
E.showMenu();
|
||||||
|
r(t.buttons[b]);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
menu.Yes = () => {
|
||||||
|
E.showMenu();
|
||||||
|
r(true);
|
||||||
|
};
|
||||||
|
menu.No = () => {
|
||||||
|
E.showMenu();
|
||||||
|
r(false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
E.showMenu(menu);
|
||||||
|
});
|
||||||
|
};
|
After Width: | Height: | Size: 602 KiB |
After Width: | Height: | Size: 1.3 KiB |
|
@ -11,3 +11,4 @@
|
||||||
0.12: Allow hiding the widget
|
0.12: Allow hiding the widget
|
||||||
0.13: Tweak Bangle.js 2 light theme colors
|
0.13: Tweak Bangle.js 2 light theme colors
|
||||||
0.14: Use weather condition code for icon selection
|
0.14: Use weather condition code for icon selection
|
||||||
|
0.15: Fix widget icon
|
||||||
|
|
|
@ -53,6 +53,16 @@ exports.get = function() {
|
||||||
|
|
||||||
scheduleExpiry(storage.readJSON('weather.json')||{});
|
scheduleExpiry(storage.readJSON('weather.json')||{});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param cond Weather condition, as one of:
|
||||||
|
* {number} code: (Preferred form) https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
||||||
|
* {string} weather description (in English: breaks for other languages!)
|
||||||
|
* {object} use cond.code if present, or fall back to cond.txt
|
||||||
|
* @param x Left
|
||||||
|
* @param y Top
|
||||||
|
* @param r Icon Size
|
||||||
|
*/
|
||||||
exports.drawIcon = function(cond, x, y, r) {
|
exports.drawIcon = function(cond, x, y, r) {
|
||||||
var palette;
|
var palette;
|
||||||
|
|
||||||
|
@ -249,32 +259,35 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6);
|
g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6);
|
||||||
}
|
}
|
||||||
|
|
||||||
function chooseIcon(condition) {
|
/*
|
||||||
if (!condition) return () => {};
|
* Choose weather icon to display based on weather description
|
||||||
condition = condition.toLowerCase();
|
*/
|
||||||
if (condition.includes("thunderstorm")) return drawThunderstorm;
|
function chooseIconByTxt(txt) {
|
||||||
if (condition.includes("freezing")||condition.includes("snow")||
|
if (!txt) return () => {};
|
||||||
condition.includes("sleet")) {
|
txt = txt.toLowerCase();
|
||||||
|
if (txt.includes("thunderstorm")) return drawThunderstorm;
|
||||||
|
if (txt.includes("freezing")||txt.includes("snow")||
|
||||||
|
txt.includes("sleet")) {
|
||||||
return drawSnow;
|
return drawSnow;
|
||||||
}
|
}
|
||||||
if (condition.includes("drizzle")||
|
if (txt.includes("drizzle")||
|
||||||
condition.includes("shower")) {
|
txt.includes("shower")) {
|
||||||
return drawRain;
|
return drawRain;
|
||||||
}
|
}
|
||||||
if (condition.includes("rain")) return drawShowerRain;
|
if (txt.includes("rain")) return drawShowerRain;
|
||||||
if (condition.includes("clear")) return drawSun;
|
if (txt.includes("clear")) return drawSun;
|
||||||
if (condition.includes("few clouds")) return drawFewClouds;
|
if (txt.includes("few clouds")) return drawFewClouds;
|
||||||
if (condition.includes("scattered clouds")) return drawCloud;
|
if (txt.includes("scattered clouds")) return drawCloud;
|
||||||
if (condition.includes("clouds")) return drawBrokenClouds;
|
if (txt.includes("clouds")) return drawBrokenClouds;
|
||||||
if (condition.includes("mist") ||
|
if (txt.includes("mist") ||
|
||||||
condition.includes("smoke") ||
|
txt.includes("smoke") ||
|
||||||
condition.includes("haze") ||
|
txt.includes("haze") ||
|
||||||
condition.includes("sand") ||
|
txt.includes("sand") ||
|
||||||
condition.includes("dust") ||
|
txt.includes("dust") ||
|
||||||
condition.includes("fog") ||
|
txt.includes("fog") ||
|
||||||
condition.includes("ash") ||
|
txt.includes("ash") ||
|
||||||
condition.includes("squalls") ||
|
txt.includes("squalls") ||
|
||||||
condition.includes("tornado")) {
|
txt.includes("tornado")) {
|
||||||
return drawMist;
|
return drawMist;
|
||||||
}
|
}
|
||||||
return drawUnknown;
|
return drawUnknown;
|
||||||
|
@ -298,7 +311,6 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
case 531: return drawShowerRain;
|
case 531: return drawShowerRain;
|
||||||
default: return drawRain;
|
default: return drawRain;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case 6: return drawSnow;
|
case 6: return drawSnow;
|
||||||
case 7: return drawMist;
|
case 7: return drawMist;
|
||||||
case 8:
|
case 8:
|
||||||
|
@ -308,16 +320,21 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
case 802: return drawCloud;
|
case 802: return drawCloud;
|
||||||
default: return drawBrokenClouds;
|
default: return drawBrokenClouds;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
default: return drawUnknown;
|
default: return drawUnknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cond.code && cond.code > 0) {
|
function chooseIcon(cond) {
|
||||||
chooseIconByCode(cond.code)(x, y, r);
|
if (typeof (cond)==="object") {
|
||||||
} else {
|
if ("code" in cond) return chooseIconByCode(cond.code);
|
||||||
chooseIcon(cond.txt)(x, y, r);
|
if ("txt" in cond) return chooseIconByTxt(cond.txt);
|
||||||
|
} else if (typeof (cond)==="number") {
|
||||||
|
return chooseIconByCode(cond.code);
|
||||||
|
} else if (typeof (cond)==="string") {
|
||||||
|
return chooseIconByTxt(cond.txt);
|
||||||
|
}
|
||||||
|
return drawUnknown;
|
||||||
}
|
}
|
||||||
|
chooseIcon(cond)(x, y, r);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,8 +52,8 @@
|
||||||
if (!w) return;
|
if (!w) return;
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23);
|
g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23);
|
||||||
if (w.txt) {
|
if (w.code||w.txt) {
|
||||||
weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5);
|
weather.drawIcon(w, this.x+10, this.y+8, 7.5);
|
||||||
}
|
}
|
||||||
if (w.temp) {
|
if (w.temp) {
|
||||||
let t = require('locale').temp(w.temp-273.15); // applies conversion
|
let t = require('locale').temp(w.temp-273.15); // applies conversion
|
||||||
|
|
|
@ -11,3 +11,4 @@
|
||||||
0.12: Fixed for Bangle 2
|
0.12: Fixed for Bangle 2
|
||||||
0.13: Fillbar setting added, see README
|
0.13: Fillbar setting added, see README
|
||||||
0.14: Fix drawing the bar when charging
|
0.14: Fix drawing the bar when charging
|
||||||
|
0.15: Added option to always display the icon when charging (useful if 'hide if charge greater than' is enabled)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
'fillbar': false,
|
'fillbar': false,
|
||||||
'charger': true,
|
'charger': true,
|
||||||
'hideifmorethan': 100,
|
'hideifmorethan': 100,
|
||||||
|
'alwaysoncharge': false,
|
||||||
}
|
}
|
||||||
// ...and overwrite them with any saved values
|
// ...and overwrite them with any saved values
|
||||||
// This way saved values are preserved if a new version adds more settings
|
// This way saved values are preserved if a new version adds more settings
|
||||||
|
@ -68,6 +69,11 @@
|
||||||
format: x => x+"%",
|
format: x => x+"%",
|
||||||
onchange: save('hideifmorethan'),
|
onchange: save('hideifmorethan'),
|
||||||
},
|
},
|
||||||
|
'Show on charge': { // Not sure if this is readable enough in the 'big' menu
|
||||||
|
value: s.alwaysoncharge,
|
||||||
|
format: onOffFormat,
|
||||||
|
onchange: save('alwaysoncharge'),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
E.showMenu(menu)
|
E.showMenu(menu)
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
'percentage': true,
|
'percentage': true,
|
||||||
'charger': true,
|
'charger': true,
|
||||||
'hideifmorethan': 100,
|
'hideifmorethan': 100,
|
||||||
|
'alwaysoncharge': false,
|
||||||
};
|
};
|
||||||
Object.keys(DEFAULTS).forEach(k=>{
|
Object.keys(DEFAULTS).forEach(k=>{
|
||||||
if (settings[k]===undefined) settings[k]=DEFAULTS[k]
|
if (settings[k]===undefined) settings[k]=DEFAULTS[k]
|
||||||
|
@ -67,8 +68,11 @@
|
||||||
var w = 40;
|
var w = 40;
|
||||||
if (Bangle.isCharging() && setting('charger'))
|
if (Bangle.isCharging() && setting('charger'))
|
||||||
w += 16;
|
w += 16;
|
||||||
if (E.getBattery() > setting('hideifmorethan'))
|
if (E.getBattery() > setting('hideifmorethan')) {
|
||||||
w = 0;
|
w = 0;
|
||||||
|
if( Bangle.isCharging() && setting('alwaysoncharge') === true)
|
||||||
|
w = 56;
|
||||||
|
}
|
||||||
var changed = WIDGETS["batpc"].width != w;
|
var changed = WIDGETS["batpc"].width != w;
|
||||||
WIDGETS["batpc"].width = w;
|
WIDGETS["batpc"].width = w;
|
||||||
return changed;
|
return changed;
|
||||||
|
|
2
core
|
@ -1 +1 @@
|
||||||
Subproject commit 5a5957714d4aa04413329f57c03e6de0cfb74caf
|
Subproject commit b05af96b2522a7a7225a56d804faf9383f8a8f97
|
|
@ -40,7 +40,7 @@ function onFoundDeviceInfo(deviceId, deviceVersion) {
|
||||||
if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") {
|
if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") {
|
||||||
showToast(`You're using ${deviceId}, not a Bangle.js. Did you want <a href="https://espruino.com/apps">espruino.com/apps</a> instead?` ,"warning", 20000);
|
showToast(`You're using ${deviceId}, not a Bangle.js. Did you want <a href="https://espruino.com/apps">espruino.com/apps</a> instead?` ,"warning", 20000);
|
||||||
} else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) {
|
} else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) {
|
||||||
showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can <a href="${fwURL}" target="_blank">update with the instructions here</a>` ,"warning", 20000);
|
showToast(`You're using an old Bangle.js firmware (${deviceVersion}) and ${RECOMMENDED_VERSION} is available (<a href="http://www.espruino.com/ChangeLog" target="_blank">see changes</a>). You can <a href="${fwURL}" target="_blank">update with the instructions here</a>` ,"warning", 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,8 @@ layoutObject has:
|
||||||
and `fillx`/`filly` to be set. Not compatible with text rotation.
|
and `fillx`/`filly` to be set. Not compatible with text rotation.
|
||||||
* A `col` field, eg `#f00` for red
|
* A `col` field, eg `#f00` for red
|
||||||
* A `bgCol` field for background color (will automatically fill on render)
|
* A `bgCol` field for background color (will automatically fill on render)
|
||||||
* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center
|
* A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center
|
||||||
* A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center
|
* A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center
|
||||||
* A `pad` integer field to set pixels padding
|
* A `pad` integer field to set pixels padding
|
||||||
* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
|
* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
|
||||||
* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space
|
* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space
|
||||||
|
|