1
0
Fork 0

Merge branch 'espruino:master' into master

master
Ronin0000 2022-01-05 08:49:44 -08:00 committed by GitHub
commit 91896bf9d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 2857 additions and 168 deletions

126
apps.json
View File

@ -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"}]
} }
] ]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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`;

View File

@ -1 +1,2 @@
0.01: New App! 0.01: New App!
0.02: Ported to Banglejs2

View File

@ -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();

244
apps/doztime/app-bangle2.js Normal file
View File

@ -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);

View File

@ -1 +1,2 @@
0.01: First version 0.01: First version
0.02: Enchanced contrast of icon image

View File

@ -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"))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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

View File

@ -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?
}); });

View File

@ -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.

View File

@ -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

View File

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
})(); })();

View File

@ -1 +1,2 @@
0.01: First release 0.01: First release
0.02: Enhanced icon, make it bolder

View File

@ -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=="))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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

View File

@ -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
![](screenshot_pastel.png)
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.
![](weather_icons.png)
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
![](screenshot_lato.png) ![](screenshot_lato.png)
## Architect ### Architect
![](screenshot_architect.png) ![](screenshot_architect.png)
## Gochihand ### Gochihand
![](screenshot_gochihand.png) ![](screenshot_gochihand.png)
## Monoton ### Monoton
![](screenshot_monoton.png) ![](screenshot_monoton.png)
## Elite ### Elite
![](screenshot_elite.png) ![](screenshot_elite.png)
## Cabin Sketch ### Cabin Sketch
![](screenshot_cabinsketch.png) ![](screenshot_cabinsketch.png)
## Orbitron ### Orbitron
![](screenshot_orbitron.png) ![](screenshot_orbitron.png)
### 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.
![](screenshot_grid.png)

View File

@ -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();

View File

@ -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();
}, },
} }
}) })

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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

View File

@ -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="))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

8
apps/presentor/ChangeLog Normal file
View File

@ -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!

2
apps/presentor/README.md Normal file
View File

@ -0,0 +1,2 @@
# Presentor
Use your Bangle to present!

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4ASlgADGmIxwLV4wqGQowfWZQwjKw4wJF7ghBmVWmQlELYoweFwYABGAwrHF7IuFGAwrIF7AuHMJADGF0AwHAYovWFxaSHADQuDEgIADYZYucAQOB1fQ1eBmQwiFwlX6AAE1gqBGD6MEmQqBwIICwIGB0rDeWYksFAIuBz+fvQHC6D0dcQssEwIuB4fC4V0M4VXF7YuFDYLqBlnDF4WeHAYugL5N6L4I4BF0IvB0vQvdQGAJeBY4YucmQxEAgJgBvYOBdwYQDFzUzmZ/EllXFIKKBAYVXBwSMaller7fFAoKSBAAOBFQK7dPoJfFEoYADRjgmEeIzFFdUQAOdUIumdRLtDAAIufdRQKCr8zCQjqmE4NeF4YudWxpqCFyovBK5LqiF6DqdR6D0BdTYwJGg5sBdTQwKEAIwHdTQwMSpQueYaAufGBouiGBYukGBIumGA4uoGAouqGAYABF1QAl"))

471
apps/presentor/app.js Normal file
View File

@ -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();

BIN
apps/presentor/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -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>

View File

@ -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}

5
apps/puzzle15/ChangeLog Normal file
View File

@ -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

57
apps/puzzle15/README.md Normal file
View File

@ -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!

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -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="))

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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

View File

@ -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'
![](screenshot_rebble.png) ![](screenshot_rebble.png)
![](screenshot_rebble2.png) ![](screenshot_rebble2.png)

View File

@ -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="))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -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

View File

@ -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'] = ()=>{

2
apps/sonicclk/Changelog Normal file
View File

@ -0,0 +1,2 @@
1.00 Added sonic clock app
1.01 Fixed text alignment issue; Increased acceleration required to activate twist;

13
apps/sonicclk/README.md Normal file
View File

@ -0,0 +1,13 @@
# Sonic Clock
A classic sonic clock featuring run, stop and wait animations.
![Sonic Clock screenshot](screenshot.png)
## 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) 🤗

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkBiIA/AE0ZzIACBIgFFC7oTCylEzOIDYeZogX6LwWd7oYCAAOJC82UpoXFAAKnMC6x2CpIVFC/gSCxOYAYP///4C4X/DBIXWIIwWBDAWPAYIXedQJwG/8AgEP//wgAX2CwIADRoQXmSIoXtJAeEoi+C+lEoAcBogX7zOUondolEpvdAAQXgYIgXCAAwXlAAIYC6ENLx4XtAYMZDAvd6gWJC7IKJABgX/C74A/ADY"))

284
apps/sonicclk/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/sonicclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

1
apps/touchmenu/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: App launched

40
apps/touchmenu/README.md Normal file
View File

@ -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.
![screenshot](touchmenu.gif)
## 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)

View File

@ -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);
});
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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

View File

@ -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);
}; };

View File

@ -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

View File

@ -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)

View File

@ -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)
}) })

View File

@ -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

View File

@ -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);
} }

View File

@ -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