Merge branch 'master' of https://github.com/dapgo/BangleApps
|
@ -14,3 +14,4 @@ _site
|
||||||
.owncloudsync.log
|
.owncloudsync.log
|
||||||
Desktop.ini
|
Desktop.ini
|
||||||
.sync_*.db*
|
.sync_*.db*
|
||||||
|
*.swp
|
||||||
|
|
|
@ -7,3 +7,5 @@
|
||||||
0.07: Clkinfo improvements.
|
0.07: Clkinfo improvements.
|
||||||
0.08: Fix error in clkinfo (didn't require Storage & locale)
|
0.08: Fix error in clkinfo (didn't require Storage & locale)
|
||||||
Fix clkinfo icon
|
Fix clkinfo icon
|
||||||
|
0.09: Ensure Agenda supplies an image for clkinfo items
|
||||||
|
0.10: Update clock_info to avoid a redraw
|
||||||
|
|
|
@ -19,8 +19,8 @@
|
||||||
|
|
||||||
agendaItems.items.push({
|
agendaItems.items.push({
|
||||||
name: "Agenda "+i,
|
name: "Agenda "+i,
|
||||||
get: () => ({ text: title + "\n" + dateStr, img: null}),
|
get: () => ({ text: title + "\n" + dateStr, img: agendaItems.img }),
|
||||||
show: function() { agendaItems.items[i].emit("redraw"); },
|
show: function() {},
|
||||||
hide: function () {}
|
hide: function () {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "agenda",
|
"id": "agenda",
|
||||||
"name": "Agenda",
|
"name": "Agenda",
|
||||||
"version": "0.08",
|
"version": "0.10",
|
||||||
"description": "Simple agenda",
|
"description": "Simple agenda",
|
||||||
"icon": "agenda.png",
|
"icon": "agenda.png",
|
||||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: Create astrocalc app
|
0.01: Create astrocalc app
|
||||||
0.02: Store last GPS lock, can be used instead of waiting for new GPS on start
|
0.02: Store last GPS lock, can be used instead of waiting for new GPS on start
|
||||||
0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||||
|
0.04: Compatibility with Bangle.js 2, get location from My Location
|
||||||
|
|
|
@ -11,8 +11,7 @@
|
||||||
|
|
||||||
const SunCalc = require("suncalc"); // from modules folder
|
const SunCalc = require("suncalc"); // from modules folder
|
||||||
const storage = require("Storage");
|
const storage = require("Storage");
|
||||||
const LAST_GPS_FILE = "astrocalc.gps.json";
|
const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
|
||||||
let lastGPS = (storage.readJSON(LAST_GPS_FILE, 1) || null);
|
|
||||||
|
|
||||||
function drawMoon(phase, x, y) {
|
function drawMoon(phase, x, y) {
|
||||||
const moonImgFiles = [
|
const moonImgFiles = [
|
||||||
|
@ -73,7 +72,7 @@ function drawTitle(key) {
|
||||||
*/
|
*/
|
||||||
function drawPoint(angle, radius, color) {
|
function drawPoint(angle, radius, color) {
|
||||||
const pRad = Math.PI / 180;
|
const pRad = Math.PI / 180;
|
||||||
const faceWidth = 80; // watch face radius
|
const faceWidth = g.getWidth()/3; // watch face radius
|
||||||
const centerPx = g.getWidth() / 2;
|
const centerPx = g.getWidth() / 2;
|
||||||
|
|
||||||
const a = angle * pRad;
|
const a = angle * pRad;
|
||||||
|
@ -141,6 +140,7 @@ function drawData(title, obj, startX, startY) {
|
||||||
|
|
||||||
function drawMoonPositionPage(gps, title) {
|
function drawMoonPositionPage(gps, title) {
|
||||||
const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon);
|
const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon);
|
||||||
|
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
Azimuth: pos.azimuth.toFixed(2),
|
Azimuth: pos.azimuth.toFixed(2),
|
||||||
|
@ -150,59 +150,61 @@ function drawMoonPositionPage(gps, title) {
|
||||||
};
|
};
|
||||||
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
|
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
|
||||||
|
|
||||||
drawData(title, pageData, null, 80);
|
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20);
|
||||||
drawPoints();
|
drawPoints();
|
||||||
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
drawPoint(azimuthDegrees, 8, moonColor);
|
||||||
|
|
||||||
let m = setWatch(() => {
|
let m = setWatch(() => {
|
||||||
let m = moonIndexPageMenu(gps);
|
let m = moonIndexPageMenu(gps);
|
||||||
}, BTN3, {repeat: false, edge: "falling"});
|
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawMoonIlluminationPage(gps, title) {
|
function drawMoonIlluminationPage(gps, title) {
|
||||||
const phaseNames = [
|
const phaseNames = [
|
||||||
"New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
|
/*LANG*/"New Moon", /*LANG*/"Waxing Crescent", /*LANG*/"First Quarter", /*LANG*/"Waxing Gibbous",
|
||||||
"Full Moon", "Waning Gibbous", "Last Quater", "Waning Crescent",
|
/*LANG*/"Full Moon", /*LANG*/"Waning Gibbous", /*LANG*/"Last Quater", /*LANG*/"Waning Crescent",
|
||||||
];
|
];
|
||||||
|
|
||||||
const phase = SunCalc.getMoonIllumination(new Date());
|
const phase = SunCalc.getMoonIllumination(new Date());
|
||||||
|
const phaseIdx = Math.round(phase.phase*8);
|
||||||
const pageData = {
|
const pageData = {
|
||||||
Phase: phaseNames[phase.phase],
|
Phase: phaseNames[phaseIdx],
|
||||||
};
|
};
|
||||||
|
|
||||||
drawData(title, pageData, null, 35);
|
drawData(title, pageData, null, 35);
|
||||||
drawMoon(phase.phase, g.getWidth() / 2, g.getHeight() / 2);
|
drawMoon(phaseIdx, g.getWidth() / 2, g.getHeight() / 2);
|
||||||
|
|
||||||
let m = setWatch(() => {
|
let m = setWatch(() => {
|
||||||
let m = moonIndexPageMenu(gps);
|
let m = moonIndexPageMenu(gps);
|
||||||
}, BTN3, {repease: false, edge: "falling"});
|
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function drawMoonTimesPage(gps, title) {
|
function drawMoonTimesPage(gps, title) {
|
||||||
const times = SunCalc.getMoonTimes(new Date(), gps.lat, gps.lon);
|
const times = SunCalc.getMoonTimes(new Date(), gps.lat, gps.lon);
|
||||||
|
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
|
||||||
|
|
||||||
const pageData = {
|
const pageData = {
|
||||||
Rise: dateToTimeString(times.rise),
|
Rise: dateToTimeString(times.rise),
|
||||||
Set: dateToTimeString(times.set),
|
Set: dateToTimeString(times.set),
|
||||||
};
|
};
|
||||||
|
|
||||||
drawData(title, pageData, null, 105);
|
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
|
||||||
drawPoints();
|
drawPoints();
|
||||||
|
|
||||||
// Draw the moon rise position
|
// Draw the moon rise position
|
||||||
const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon);
|
const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon);
|
||||||
const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI);
|
const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI);
|
||||||
drawPoint(riseAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
drawPoint(riseAzimuthDegrees, 8, moonColor);
|
||||||
|
|
||||||
// Draw the moon set position
|
// Draw the moon set position
|
||||||
const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon);
|
const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon);
|
||||||
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
|
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
|
||||||
drawPoint(setAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
drawPoint(setAzimuthDegrees, 8, moonColor);
|
||||||
|
|
||||||
let m = setWatch(() => {
|
let m = setWatch(() => {
|
||||||
let m = moonIndexPageMenu(gps);
|
let m = moonIndexPageMenu(gps);
|
||||||
}, BTN3, {repease: false, edge: "falling"});
|
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSunShowPage(gps, key, date) {
|
function drawSunShowPage(gps, key, date) {
|
||||||
|
@ -224,7 +226,7 @@ function drawSunShowPage(gps, key, date) {
|
||||||
Degrees: azimuthDegrees
|
Degrees: azimuthDegrees
|
||||||
};
|
};
|
||||||
|
|
||||||
drawData(key, pageData, null, 85);
|
drawData(key, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
|
||||||
|
|
||||||
drawPoints();
|
drawPoints();
|
||||||
|
|
||||||
|
@ -233,7 +235,7 @@ function drawSunShowPage(gps, key, date) {
|
||||||
|
|
||||||
m = setWatch(() => {
|
m = setWatch(() => {
|
||||||
m = sunIndexPageMenu(gps);
|
m = sunIndexPageMenu(gps);
|
||||||
}, BTN3, {repeat: false, edge: "falling"});
|
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -273,15 +275,15 @@ function moonIndexPageMenu(gps) {
|
||||||
},
|
},
|
||||||
"Times": () => {
|
"Times": () => {
|
||||||
m = E.showMenu();
|
m = E.showMenu();
|
||||||
drawMoonTimesPage(gps, "Times");
|
drawMoonTimesPage(gps, /*LANG*/"Times");
|
||||||
},
|
},
|
||||||
"Position": () => {
|
"Position": () => {
|
||||||
m = E.showMenu();
|
m = E.showMenu();
|
||||||
drawMoonPositionPage(gps, "Position");
|
drawMoonPositionPage(gps, /*LANG*/"Position");
|
||||||
},
|
},
|
||||||
"Illumination": () => {
|
"Illumination": () => {
|
||||||
m = E.showMenu();
|
m = E.showMenu();
|
||||||
drawMoonIlluminationPage(gps, "Illumination");
|
drawMoonIlluminationPage(gps, /*LANG*/"Illumination");
|
||||||
},
|
},
|
||||||
"< Back": () => m = indexPageMenu(gps),
|
"< Back": () => m = indexPageMenu(gps),
|
||||||
};
|
};
|
||||||
|
@ -292,15 +294,15 @@ function moonIndexPageMenu(gps) {
|
||||||
function indexPageMenu(gps) {
|
function indexPageMenu(gps) {
|
||||||
const menu = {
|
const menu = {
|
||||||
"": {
|
"": {
|
||||||
"title": "Select",
|
"title": /*LANG*/"Select",
|
||||||
},
|
},
|
||||||
"Sun": () => {
|
/*LANG*/"Sun": () => {
|
||||||
m = sunIndexPageMenu(gps);
|
m = sunIndexPageMenu(gps);
|
||||||
},
|
},
|
||||||
"Moon": () => {
|
/*LANG*/"Moon": () => {
|
||||||
m = moonIndexPageMenu(gps);
|
m = moonIndexPageMenu(gps);
|
||||||
},
|
},
|
||||||
"< Exit": () => { load(); }
|
"< Back": () => { load(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
return E.showMenu(menu);
|
return E.showMenu(menu);
|
||||||
|
@ -310,78 +312,9 @@ function getCenterStringX(str) {
|
||||||
return (g.getWidth() - g.stringWidth(str)) / 2;
|
return (g.getWidth() - g.stringWidth(str)) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* GPS wait page, shows GPS locating animation until it gets a lock, then moves to the Sun page
|
|
||||||
*/
|
|
||||||
function drawGPSWaitPage() {
|
|
||||||
const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="));
|
|
||||||
const str1 = "Astrocalc v0.02";
|
|
||||||
const str2 = "Locating GPS";
|
|
||||||
const str3 = "Please wait...";
|
|
||||||
|
|
||||||
g.clear();
|
|
||||||
g.drawImage(img, 100, 50);
|
|
||||||
g.setFont("6x8", 1);
|
|
||||||
g.drawString(str1, getCenterStringX(str1), 105);
|
|
||||||
g.drawString(str2, getCenterStringX(str2), 140);
|
|
||||||
g.drawString(str3, getCenterStringX(str3), 155);
|
|
||||||
|
|
||||||
if (lastGPS) {
|
|
||||||
lastGPS = JSON.parse(lastGPS);
|
|
||||||
lastGPS.time = new Date();
|
|
||||||
|
|
||||||
const str4 = "Press Button 3 to use last GPS";
|
|
||||||
g.setColor("#d32e29");
|
|
||||||
g.fillRect(0, 190, g.getWidth(), 215);
|
|
||||||
g.setColor("#ffffff");
|
|
||||||
g.drawString(str4, getCenterStringX(str4), 200);
|
|
||||||
|
|
||||||
setWatch(() => {
|
|
||||||
clearWatch();
|
|
||||||
Bangle.setGPSPower(0);
|
|
||||||
m = indexPageMenu(lastGPS);
|
|
||||||
}, BTN3, {repeat: false});
|
|
||||||
}
|
|
||||||
|
|
||||||
g.flip();
|
|
||||||
|
|
||||||
const DEBUG = false;
|
|
||||||
if (DEBUG) {
|
|
||||||
clearWatch();
|
|
||||||
|
|
||||||
const gps = {
|
|
||||||
"lat": 56.45783133333,
|
|
||||||
"lon": -3.02188583333,
|
|
||||||
"alt": 75.3,
|
|
||||||
"speed": 0.070376,
|
|
||||||
"course": NaN,
|
|
||||||
"time":new Date(),
|
|
||||||
"satellites": 4,
|
|
||||||
"fix": 1
|
|
||||||
};
|
|
||||||
|
|
||||||
m = indexPageMenu(gps);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bangle.on('GPS', (gps) => {
|
|
||||||
if (gps.fix === 0) return;
|
|
||||||
clearWatch();
|
|
||||||
|
|
||||||
if (isNaN(gps.course)) gps.course = 0;
|
|
||||||
require("Storage").writeJSON(LAST_GPS_FILE, JSON.stringify(gps));
|
|
||||||
Bangle.setGPSPower(0);
|
|
||||||
Bangle.buzz();
|
|
||||||
Bangle.setLCDPower(true);
|
|
||||||
|
|
||||||
m = indexPageMenu(gps);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
Bangle.setGPSPower(1);
|
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
|
||||||
drawGPSWaitPage();
|
indexPageMenu(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
let m;
|
let m;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
{
|
{
|
||||||
"id": "astrocalc",
|
"id": "astrocalc",
|
||||||
"name": "Astrocalc",
|
"name": "Astrocalc",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
|
"description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app",
|
||||||
"icon": "astrocalc.png",
|
"icon": "astrocalc.png",
|
||||||
"tags": "app,sun,moon,cycles,tool,outdoors",
|
"tags": "app,sun,moon,cycles,tool",
|
||||||
"supports": ["BANGLEJS"],
|
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
|
"dependencies": {"mylocation":"app"},
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"astrocalc.app.js","url":"astrocalc-app.js"},
|
{"name":"astrocalc.app.js","url":"astrocalc-app.js"},
|
||||||
{"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true},
|
{"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true},
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -0,0 +1,9 @@
|
||||||
|
# BTHome Temperature and Pressure
|
||||||
|
|
||||||
|
This app displays temperature and pressure and advertises them over bluetooth using BTHome.io standard (along with battery level)
|
||||||
|
|
||||||
|
This can be used to integrate with [Home Assistant](https://www.home-assistant.io/), so you can use your Bangle as a wireless temperature/pressure sensor.
|
||||||
|
|
||||||
|
More info on the standard at https://bthome.io
|
||||||
|
|
||||||
|
And the data format used is https://bthome.io/format/
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4kA///1N6BIPf//1gMIwdE8sG2me+9Y/8C/2snXsoUNpdnzdt/xj/AH4AYgMRAAUQCyoYSCQNXs1muoFBFyHm1X//+qtwwPiMX1+YmczxP6uIwNFwN6yeDnGDmc504wNFwOpnGYC4OJweaGBsR9WTmYtBmc4GAOuC5ZGBt4SBAAQEBwf2JBcBiupnIuCmedxGTzVRC5cX1AuDnPZF4OKuIXLi3zIoedMgMzn9hC5uICQON5IDBxAXSznYC6RdDPQYXNO4JcB7pdCO56nBnGZ7p6DU5zXBXgSqDa5sAiPqIgOZd4c510RCxQXBi+pRQIXBxODzVxC5hIBvR1DnE505GMGAevzAvC/QuNGAfm1X//+qtwuOGAURq9ms11AoIWOGAQAEFw1EDBwWFggBCkUgAQMigUAAIIAJoABDCgIXQFwYXBCYYBDHAMCEAIkCFgcEAIIKCCoQFCkAhBAQIlCkAsBOoIXCBoIvEAwQTCAYI2BIwgXIF4YXDQwIVCC4YIBMIwfCAQRfGYBSPNC6TBFACgwBACouWAH4AiA="))
|
|
@ -0,0 +1,58 @@
|
||||||
|
// history of temperature/pressure readings
|
||||||
|
var history = [];
|
||||||
|
|
||||||
|
// When we get temperature...
|
||||||
|
function onTemperature(p) {
|
||||||
|
// Average the last 5 temperature readings
|
||||||
|
while (history.length>4) history.shift();
|
||||||
|
history.push(p);
|
||||||
|
var avrTemp = history.reduce((i,h)=>h.temperature+i,0) / history.length;
|
||||||
|
var avrPressure = history.reduce((i,h)=>h.pressure+i,0) / history.length;
|
||||||
|
var t = require('locale').temp(avrTemp).replace("'","°");
|
||||||
|
// Draw
|
||||||
|
var rect = Bangle.appRect;
|
||||||
|
g.reset(1).clearRect(rect.x, rect.y, rect.x2, rect.y2);
|
||||||
|
var x = (rect.x+rect.x2)/2;
|
||||||
|
var y = (rect.y+rect.y2)/2 + 10;
|
||||||
|
g.setFont("6x15").setFontAlign(0,0).drawString("Temperature:", x, y - 65);
|
||||||
|
g.setFontVector(50).setFontAlign(0,0).drawString(t, x, y-25);
|
||||||
|
g.setFont("6x15").setFontAlign(0,0).drawString("Pressure:", x, y+15 );
|
||||||
|
g.setFont("12x20").setFontAlign(0,0).drawString(Math.round(avrPressure)+" hPa", x, y+40);
|
||||||
|
// Set Bluetooth Advertising
|
||||||
|
// https://bthome.io/format/
|
||||||
|
var temp100 = Math.round(avrTemp*100);
|
||||||
|
var pressure100 = Math.round(avrPressure*100);
|
||||||
|
|
||||||
|
Bangle.bleAdvert[0xFCD2] = [ 0x40, /* BTHome Device Information
|
||||||
|
bit 0: "Encryption flag"
|
||||||
|
bit 1-4: "Reserved for future use"
|
||||||
|
bit 5-7: "BTHome Version" */
|
||||||
|
|
||||||
|
0x01, // Battery, 8 bit
|
||||||
|
E.getBattery(),
|
||||||
|
|
||||||
|
0x02, // Temperature, 16 bit
|
||||||
|
temp100&255,temp100>>8,
|
||||||
|
|
||||||
|
0x04, // Pressure, 16 bit
|
||||||
|
pressure100&255,(pressure100>>8)&255,pressure100>>16
|
||||||
|
];
|
||||||
|
NRF.setAdvertising(Bangle.bleAdvert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the temperature in the most accurate way with pressure sensor
|
||||||
|
function drawTemperature() {
|
||||||
|
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
|
||||||
|
setInterval(function() {
|
||||||
|
drawTemperature();
|
||||||
|
}, 10000); // update every 10s
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : "custom",
|
||||||
|
back : function() {load();}
|
||||||
|
});
|
||||||
|
E.showMessage("Reading temperature...");
|
||||||
|
drawTemperature();
|
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
{ "id": "bthometemp",
|
||||||
|
"name": "BTHome Temperature and Pressure",
|
||||||
|
"shortName":"BTHome T",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Displays temperature and pressure, and advertises them over bluetooth using BTHome.io standard",
|
||||||
|
"icon": "app.png",
|
||||||
|
"tags": "bthome,bluetooth,temperature",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"bthometemp.app.js","url":"app.js"},
|
||||||
|
{"name":"bthometemp.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
|
@ -20,4 +20,5 @@
|
||||||
0.20: Better handling of async data such as getPressure.
|
0.20: Better handling of async data such as getPressure.
|
||||||
0.21: On the default menu the week of year can be shown.
|
0.21: On the default menu the week of year can be shown.
|
||||||
0.22: Use the new clkinfo module for the menu.
|
0.22: Use the new clkinfo module for the menu.
|
||||||
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
|
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
|
||||||
|
0.24: Update clock_info to avoid a redraw
|
||||||
|
|
|
@ -93,7 +93,7 @@ var bwItems = {
|
||||||
items: [
|
items: [
|
||||||
{ name: "WeekOfYear",
|
{ name: "WeekOfYear",
|
||||||
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
||||||
show: function() { bwItems.items[0].emit("redraw"); },
|
show: function() {},
|
||||||
hide: function () {}
|
hide: function () {}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "bwclk",
|
"id": "bwclk",
|
||||||
"name": "BW Clock",
|
"name": "BW Clock",
|
||||||
"version": "0.23",
|
"version": "0.24",
|
||||||
"description": "A very minimalistic clock to mainly show date and time.",
|
"description": "A very minimalistic clock to mainly show date and time.",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Tell clock widgets to hide.
|
0.03: Tell clock widgets to hide.
|
||||||
0.04: Improve current time readability in light theme.
|
0.04: Improve current time readability in light theme.
|
||||||
0.05: Show calendar colors & improved all day events.
|
0.05: Show calendar colors & improved all day events.
|
||||||
|
0.06: Improved multi-line locations & titles
|
||||||
|
|
|
@ -54,13 +54,15 @@ function drawEventBody(event, y) {
|
||||||
var yStart = y;
|
var yStart = y;
|
||||||
if (lines.length > 2) {
|
if (lines.length > 2) {
|
||||||
lines = lines.slice(0,2);
|
lines = lines.slice(0,2);
|
||||||
lines[1] = lines[1].slice(0,-3)+"...";
|
lines[1] += "...";
|
||||||
}
|
}
|
||||||
g.drawString(lines.join('\n'),10,y);
|
g.drawString(lines.join('\n'),10,y);
|
||||||
y+=20 * lines.length;
|
y+=20 * lines.length;
|
||||||
if(event.location) {
|
if(event.location) {
|
||||||
g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),10,y);
|
g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),10,y);
|
||||||
g.drawString(event.location,25,y);
|
var loclines = g.wrapString(event.location, g.getWidth()-30);
|
||||||
|
if(loclines.length>1) loclines[0] += "...";
|
||||||
|
g.drawString(loclines[0],25,y);
|
||||||
y+=20;
|
y+=20;
|
||||||
}
|
}
|
||||||
if (event.color) {
|
if (event.color) {
|
||||||
|
@ -131,4 +133,3 @@ var minuteInterval = setInterval(redraw, 60 * 1000);
|
||||||
Bangle.setUI("clock");
|
Bangle.setUI("clock");
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "calclock",
|
"id": "calclock",
|
||||||
"name": "Calendar Clock",
|
"name": "Calendar Clock",
|
||||||
"shortName": "CalClock",
|
"shortName": "CalClock",
|
||||||
"version": "0.05",
|
"version": "0.06",
|
||||||
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
|
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
|
||||||
"icon": "calclock.png",
|
"icon": "calclock.png",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Support Bangle.js 2
|
0.02: Support Bangle.js 2
|
||||||
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
|
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
|
||||||
|
0.04: Combine code for both apps
|
||||||
|
Better colors for Bangle.js 2
|
||||||
|
Fix selection animation for Bangle.js 2
|
||||||
|
New icon
|
||||||
|
Slightly wider arc segments for better visibility
|
||||||
|
Extract arc drawing code in library
|
||||||
|
|
|
@ -11,16 +11,21 @@ the players seated in a circle, set the number of segments equal to the number
|
||||||
of players, ensure that each person knows which colour represents them, and then
|
of players, ensure that each person knows which colour represents them, and then
|
||||||
choose a segment. After a short animation, the chosen segment will fill the screen.
|
choose a segment. After a short animation, the chosen segment will fill the screen.
|
||||||
|
|
||||||
You can use Choozi to randomly select an element from any set with 2 to 13 members,
|
You can use Choozi to randomly select an element from any set with 2 to 15 members,
|
||||||
as long as you can define a bijection between members of the set and coloured
|
as long as you can define a bijection between members of the set and coloured
|
||||||
segments on the Bangle.js display.
|
segments on the Bangle.js display.
|
||||||
|
|
||||||
## Controls
|
## Controls Bangle 1
|
||||||
|
|
||||||
BTN1: increase the number of segments
|
BTN1: increase the number of segments
|
||||||
BTN2: choose a segment at random
|
BTN2: choose a segment at random
|
||||||
BTN3: decrease the number of segments
|
BTN3: decrease the number of segments
|
||||||
|
|
||||||
|
## Controls Bangle 2
|
||||||
|
|
||||||
|
Swipe up/down: increase/decrease the number of segments
|
||||||
|
BTN1 or tap: choose a segment at random
|
||||||
|
|
||||||
## Creator
|
## Creator
|
||||||
|
|
||||||
James Stanley
|
James Stanley
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
|
require("heatshrink").decompress(atob("mEwwcH/4AW/u27dt2wQL/YOBCIXbv4QI+AODAQVsh4RHwEbCI0LCI9gCIOANAXbsFbG437tkDPg1btoRFFoILBgmSpMggECHQO/CAf2CIVJkgRBAQIjC24RFsECCItIgIRFMYMAiQRFpMAlqmDVwPYgAOEAQUggu274RD4BWCCIskCIPbCIPt20ABwwCCwARFgIRJyEWCIVt2EJCJi2BCJmSUgIRCwARNt/7CIIOICI1sWAwCFoFbCOtt8EACJsAgARR8hwBCJlJk4RlgARQAgIRKDwMn/gRBdJgRPyARBn4RBpARLiQRB/4RBgIRJwAREpIRLAYP///ypMgCJMACI0ECI4JCp4RB/wZECIsAAYN/CIP/5JPDCIhjDCIraHTIWTCAX//K7DCI+fCIf/EZA1CCAn//ipCLIsBk4RF/5ZHCIIQG//wPo8vCI//6QRFpYQIAAPpCIeXCBQAC/VfBI4="))
|
|
@ -4,15 +4,16 @@
|
||||||
*
|
*
|
||||||
* James Stanley 2021
|
* James Stanley 2021
|
||||||
*/
|
*/
|
||||||
|
const GU = require("graphics_utils");
|
||||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff'];
|
||||||
|
var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0'];
|
||||||
|
|
||||||
var stepAngle = 0.18; // radians - resolution of polygon
|
var stepAngle = 0.18; // radians - resolution of polygon
|
||||||
var gapAngle = 0.035; // radians - gap between segments
|
var gapAngle = 0.035; // radians - gap between segments
|
||||||
var perimMin = 110; // px - min. radius of perimeter
|
var perimMin = g.getWidth()*0.40; // px - min. radius of perimeter
|
||||||
var perimMax = 120; // px - max. radius of perimeter
|
var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter
|
||||||
|
|
||||||
var segmentMax = 106; // px - max radius of filled-in segment
|
var segmentMax = g.getWidth()*0.38; // px - max radius of filled-in segment
|
||||||
var segmentStep = 5; // px - step size of segment fill animation
|
var segmentStep = 5; // px - step size of segment fill animation
|
||||||
var circleStep = 4; // px - step size of circle fill animation
|
var circleStep = 4; // px - step size of circle fill animation
|
||||||
|
|
||||||
|
@ -22,10 +23,10 @@ var minSpeed = 0.001; // rad/sec
|
||||||
var animStartSteps = 300; // how many steps before it can start slowing?
|
var animStartSteps = 300; // how many steps before it can start slowing?
|
||||||
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
||||||
var ballSize = 3; // px - ball radius
|
var ballSize = 3; // px - ball radius
|
||||||
var ballTrack = 100; // px - radius of ball path
|
var ballTrack = perimMin - ballSize*2; // px - radius of ball path
|
||||||
|
|
||||||
var centreX = 120; // px - centre of screen
|
var centreX = g.getWidth()*0.5; // px - centre of screen
|
||||||
var centreY = 120; // px - centre of screen
|
var centreY = g.getWidth()*0.5; // px - centre of screen
|
||||||
|
|
||||||
var fontSize = 50; // px
|
var fontSize = 50; // px
|
||||||
|
|
||||||
|
@ -33,7 +34,6 @@ var radians = 2*Math.PI; // radians per circle
|
||||||
|
|
||||||
var defaultN = 3; // default value for N
|
var defaultN = 3; // default value for N
|
||||||
var minN = 2;
|
var minN = 2;
|
||||||
var maxN = colours.length;
|
|
||||||
var N;
|
var N;
|
||||||
var arclen;
|
var arclen;
|
||||||
|
|
||||||
|
@ -51,42 +51,14 @@ function shuffle (array) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw an arc between radii minR and maxR, and between
|
|
||||||
// angles minAngle and maxAngle
|
|
||||||
function arc(minR, maxR, minAngle, maxAngle) {
|
|
||||||
var step = stepAngle;
|
|
||||||
var angle = minAngle;
|
|
||||||
var inside = [];
|
|
||||||
var outside = [];
|
|
||||||
var c, s;
|
|
||||||
while (angle < maxAngle) {
|
|
||||||
c = Math.cos(angle);
|
|
||||||
s = Math.sin(angle);
|
|
||||||
inside.push(centreX+c*minR); // x
|
|
||||||
inside.push(centreY+s*minR); // y
|
|
||||||
// outside coordinates are built up in reverse order
|
|
||||||
outside.unshift(centreY+s*maxR); // y
|
|
||||||
outside.unshift(centreX+c*maxR); // x
|
|
||||||
angle += step;
|
|
||||||
}
|
|
||||||
c = Math.cos(maxAngle);
|
|
||||||
s = Math.sin(maxAngle);
|
|
||||||
inside.push(centreX+c*minR);
|
|
||||||
inside.push(centreY+s*minR);
|
|
||||||
outside.unshift(centreY+s*maxR);
|
|
||||||
outside.unshift(centreX+c*maxR);
|
|
||||||
|
|
||||||
var vertices = inside.concat(outside);
|
|
||||||
g.fillPoly(vertices, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the arc segments around the perimeter
|
// draw the arc segments around the perimeter
|
||||||
function drawPerimeter() {
|
function drawPerimeter() {
|
||||||
|
g.setBgColor('#000000');
|
||||||
g.clear();
|
g.clear();
|
||||||
for (var i = 0; i < N; i++) {
|
for (var i = 0; i < N; i++) {
|
||||||
g.setColor(colours[i%colours.length]);
|
g.setColor(colours[i%colours.length]);
|
||||||
var minAngle = (i/N)*radians;
|
var minAngle = (i/N)*radians;
|
||||||
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
GU.fillArc(g, centreX, centreY, perimMin,perimMax,minAngle,minAngle+arclen, stepAngle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +103,7 @@ function animateChoice(target) {
|
||||||
g.fillCircle(x, y, ballSize);
|
g.fillCircle(x, y, ballSize);
|
||||||
oldx=x;
|
oldx=x;
|
||||||
oldy=y;
|
oldy=y;
|
||||||
|
if (process.env.HWVERSION == 2) g.flip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,11 +114,15 @@ function choose() {
|
||||||
var maxAngle = minAngle + arclen;
|
var maxAngle = minAngle + arclen;
|
||||||
animateChoice((minAngle+maxAngle)/2);
|
animateChoice((minAngle+maxAngle)/2);
|
||||||
g.setColor(colours[chosen%colours.length]);
|
g.setColor(colours[chosen%colours.length]);
|
||||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){
|
||||||
arc(i, perimMax, minAngle, maxAngle);
|
GU.fillArc(g, centreX, centreY, i, perimMax, minAngle, maxAngle, stepAngle);
|
||||||
arc(0, perimMax, minAngle, maxAngle);
|
if (process.env.HWVERSION == 2) g.flip();
|
||||||
for (var r = 1; r < segmentMax; r += circleStep)
|
}
|
||||||
|
GU.fillArc(g, centreX, centreY, 0, perimMax, minAngle, maxAngle, stepAngle);
|
||||||
|
for (var r = 1; r < segmentMax; r += circleStep){
|
||||||
g.fillCircle(centreX,centreY,r);
|
g.fillCircle(centreX,centreY,r);
|
||||||
|
if (process.env.HWVERSION == 2) g.flip();
|
||||||
|
}
|
||||||
g.fillCircle(centreX,centreY,segmentMax);
|
g.fillCircle(centreX,centreY,segmentMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,38 +148,47 @@ function setN(n) {
|
||||||
drawPerimeter();
|
drawPerimeter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// save N to choozi.txt
|
// save N to choozi.save
|
||||||
function writeN() {
|
function writeN() {
|
||||||
var file = require("Storage").open("choozi.txt","w");
|
var savedN = read();
|
||||||
file.write(N);
|
if (savedN != N) require("Storage").write("choozi.save","" + N);
|
||||||
}
|
}
|
||||||
|
|
||||||
// load N from choozi.txt
|
function read(){
|
||||||
|
var n = require("Storage").read("choozi.save");
|
||||||
|
if (n !== undefined) return parseInt(n);
|
||||||
|
return defaultN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load N from choozi.save
|
||||||
function readN() {
|
function readN() {
|
||||||
var file = require("Storage").open("choozi.txt","r");
|
setN(read());
|
||||||
var n = file.readLine();
|
|
||||||
if (n !== undefined) setN(parseInt(n));
|
|
||||||
else setN(defaultN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shuffle(colours); // is this really best?
|
if (process.env.HWVERSION == 1){
|
||||||
Bangle.setLCDMode("direct");
|
colours=colours.concat(colours2);
|
||||||
Bangle.setLCDTimeout(0); // keep screen on
|
shuffle(colours);
|
||||||
|
} else {
|
||||||
|
shuffle(colours);
|
||||||
|
shuffle(colours2);
|
||||||
|
colours=colours.concat(colours2);
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxN = colours.length;
|
||||||
|
if (process.env.HWVERSION == 1){
|
||||||
|
Bangle.setLCDMode("direct");
|
||||||
|
Bangle.setLCDTimeout(0); // keep screen on
|
||||||
|
}
|
||||||
readN();
|
readN();
|
||||||
drawN();
|
drawN();
|
||||||
|
|
||||||
setWatch(() => {
|
Bangle.setUI("updown", (v)=>{
|
||||||
setN(N+1);
|
if (!v){
|
||||||
drawN();
|
writeN();
|
||||||
}, BTN1, {repeat:true});
|
drawPerimeter();
|
||||||
|
choose();
|
||||||
setWatch(() => {
|
} else {
|
||||||
writeN();
|
setN(N-v);
|
||||||
drawPerimeter();
|
drawN();
|
||||||
choose();
|
}
|
||||||
}, BTN2, {repeat:true});
|
});
|
||||||
|
|
||||||
setWatch(() => {
|
|
||||||
setN(N-1);
|
|
||||||
drawN();
|
|
||||||
}, BTN3, {repeat:true});
|
|
||||||
|
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 551 B |
|
@ -1,207 +0,0 @@
|
||||||
/* Choozi - Choose people or things at random using Bangle.js.
|
|
||||||
* Inspired by the "Chwazi" Android app
|
|
||||||
*
|
|
||||||
* James Stanley 2021
|
|
||||||
*/
|
|
||||||
|
|
||||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
|
||||||
|
|
||||||
var stepAngle = 0.18; // radians - resolution of polygon
|
|
||||||
var gapAngle = 0.035; // radians - gap between segments
|
|
||||||
var perimMin = 80; // px - min. radius of perimeter
|
|
||||||
var perimMax = 87; // px - max. radius of perimeter
|
|
||||||
|
|
||||||
var segmentMax = 70; // px - max radius of filled-in segment
|
|
||||||
var segmentStep = 5; // px - step size of segment fill animation
|
|
||||||
var circleStep = 4; // px - step size of circle fill animation
|
|
||||||
|
|
||||||
// rolling ball animation:
|
|
||||||
var maxSpeed = 0.08; // rad/sec
|
|
||||||
var minSpeed = 0.001; // rad/sec
|
|
||||||
var animStartSteps = 300; // how many steps before it can start slowing?
|
|
||||||
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
|
||||||
var ballSize = 3; // px - ball radius
|
|
||||||
var ballTrack = 75; // px - radius of ball path
|
|
||||||
|
|
||||||
var centreX = 88; // px - centre of screen
|
|
||||||
var centreY = 88; // px - centre of screen
|
|
||||||
|
|
||||||
var fontSize = 50; // px
|
|
||||||
|
|
||||||
var radians = 2*Math.PI; // radians per circle
|
|
||||||
|
|
||||||
var defaultN = 3; // default value for N
|
|
||||||
var minN = 2;
|
|
||||||
var maxN = colours.length;
|
|
||||||
var N;
|
|
||||||
var arclen;
|
|
||||||
|
|
||||||
// https://www.frankmitchell.org/2015/01/fisher-yates/
|
|
||||||
function shuffle (array) {
|
|
||||||
var i = 0
|
|
||||||
, j = 0
|
|
||||||
, temp = null;
|
|
||||||
|
|
||||||
for (i = array.length - 1; i > 0; i -= 1) {
|
|
||||||
j = Math.floor(Math.random() * (i + 1));
|
|
||||||
temp = array[i];
|
|
||||||
array[i] = array[j];
|
|
||||||
array[j] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw an arc between radii minR and maxR, and between
|
|
||||||
// angles minAngle and maxAngle
|
|
||||||
function arc(minR, maxR, minAngle, maxAngle) {
|
|
||||||
var step = stepAngle;
|
|
||||||
var angle = minAngle;
|
|
||||||
var inside = [];
|
|
||||||
var outside = [];
|
|
||||||
var c, s;
|
|
||||||
while (angle < maxAngle) {
|
|
||||||
c = Math.cos(angle);
|
|
||||||
s = Math.sin(angle);
|
|
||||||
inside.push(centreX+c*minR); // x
|
|
||||||
inside.push(centreY+s*minR); // y
|
|
||||||
// outside coordinates are built up in reverse order
|
|
||||||
outside.unshift(centreY+s*maxR); // y
|
|
||||||
outside.unshift(centreX+c*maxR); // x
|
|
||||||
angle += step;
|
|
||||||
}
|
|
||||||
c = Math.cos(maxAngle);
|
|
||||||
s = Math.sin(maxAngle);
|
|
||||||
inside.push(centreX+c*minR);
|
|
||||||
inside.push(centreY+s*minR);
|
|
||||||
outside.unshift(centreY+s*maxR);
|
|
||||||
outside.unshift(centreX+c*maxR);
|
|
||||||
|
|
||||||
var vertices = inside.concat(outside);
|
|
||||||
g.fillPoly(vertices, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the arc segments around the perimeter
|
|
||||||
function drawPerimeter() {
|
|
||||||
g.clear();
|
|
||||||
for (var i = 0; i < N; i++) {
|
|
||||||
g.setColor(colours[i%colours.length]);
|
|
||||||
var minAngle = (i/N)*radians;
|
|
||||||
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// animate a ball rolling around and settling at "target" radians
|
|
||||||
function animateChoice(target) {
|
|
||||||
var angle = 0;
|
|
||||||
var speed = 0;
|
|
||||||
var oldx = -10;
|
|
||||||
var oldy = -10;
|
|
||||||
var decelFromAngle = -1;
|
|
||||||
var allowDecel = false;
|
|
||||||
for (var i = 0; true; i++) {
|
|
||||||
angle = angle + speed;
|
|
||||||
if (angle > radians) angle -= radians;
|
|
||||||
if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) {
|
|
||||||
speed = speed + accel;
|
|
||||||
if (speed > maxSpeed) {
|
|
||||||
speed = maxSpeed;
|
|
||||||
/* when we reach max speed, we know how long it takes
|
|
||||||
* to accelerate, and therefore how long to decelerate, so
|
|
||||||
* we can work out what angle to start decelerating from */
|
|
||||||
if (decelFromAngle < 0) {
|
|
||||||
decelFromAngle = target-angle;
|
|
||||||
while (decelFromAngle < 0) decelFromAngle += radians;
|
|
||||||
while (decelFromAngle > radians) decelFromAngle -= radians;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true;
|
|
||||||
if (allowDecel) speed = speed - accel;
|
|
||||||
if (speed < minSpeed) speed = minSpeed;
|
|
||||||
if (speed == minSpeed && angle < target && angle+speed >= target) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var r = i/2;
|
|
||||||
if (r > ballTrack) r = ballTrack;
|
|
||||||
var x = centreX+Math.cos(angle)*r;
|
|
||||||
var y = centreY+Math.sin(angle)*r;
|
|
||||||
g.setColor('#000000');
|
|
||||||
g.fillCircle(oldx,oldy,ballSize+1);
|
|
||||||
g.setColor('#ffffff');
|
|
||||||
g.fillCircle(x, y, ballSize);
|
|
||||||
oldx=x;
|
|
||||||
oldy=y;
|
|
||||||
g.flip();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// choose a winning segment and animate its selection
|
|
||||||
function choose() {
|
|
||||||
var chosen = Math.floor(Math.random()*N);
|
|
||||||
var minAngle = (chosen/N)*radians;
|
|
||||||
var maxAngle = minAngle + arclen;
|
|
||||||
animateChoice((minAngle+maxAngle)/2);
|
|
||||||
g.setColor(colours[chosen%colours.length]);
|
|
||||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
|
||||||
arc(i, perimMax, minAngle, maxAngle);
|
|
||||||
arc(0, perimMax, minAngle, maxAngle);
|
|
||||||
for (var r = 1; r < segmentMax; r += circleStep)
|
|
||||||
g.fillCircle(centreX,centreY,r);
|
|
||||||
g.fillCircle(centreX,centreY,segmentMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the current value of N in the middle of the screen, with
|
|
||||||
// up/down arrows
|
|
||||||
function drawN() {
|
|
||||||
g.setColor(g.theme.fg);
|
|
||||||
g.setFont("Vector",fontSize);
|
|
||||||
g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2);
|
|
||||||
if (N < maxN)
|
|
||||||
g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]);
|
|
||||||
if (N > minN)
|
|
||||||
g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update number of segments, with min/max limit, "arclen" update,
|
|
||||||
// and screen reset
|
|
||||||
function setN(n) {
|
|
||||||
N = n;
|
|
||||||
if (N < minN) N = minN;
|
|
||||||
if (N > maxN) N = maxN;
|
|
||||||
arclen = radians/N - gapAngle;
|
|
||||||
drawPerimeter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// save N to choozi.txt
|
|
||||||
function writeN() {
|
|
||||||
var file = require("Storage").open("choozi.txt","w");
|
|
||||||
file.write(N);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load N from choozi.txt
|
|
||||||
function readN() {
|
|
||||||
var file = require("Storage").open("choozi.txt","r");
|
|
||||||
var n = file.readLine();
|
|
||||||
if (n !== undefined) setN(parseInt(n));
|
|
||||||
else setN(defaultN);
|
|
||||||
}
|
|
||||||
|
|
||||||
shuffle(colours); // is this really best?
|
|
||||||
Bangle.setLCDTimeout(0); // keep screen on
|
|
||||||
readN();
|
|
||||||
drawN();
|
|
||||||
|
|
||||||
setWatch(() => {
|
|
||||||
writeN();
|
|
||||||
drawPerimeter();
|
|
||||||
choose();
|
|
||||||
}, BTN1, {repeat:true});
|
|
||||||
|
|
||||||
Bangle.on('touch', function(zone,e) {
|
|
||||||
if(e.x>+88){
|
|
||||||
setN(N-1);
|
|
||||||
drawN();
|
|
||||||
}else{
|
|
||||||
setN(N+1);
|
|
||||||
drawN();
|
|
||||||
}
|
|
||||||
});
|
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "choozi",
|
"id": "choozi",
|
||||||
"name": "Choozi",
|
"name": "Choozi",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Choose people or things at random using Bangle.js.",
|
"description": "Choose people or things at random using Bangle.js.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool",
|
"tags": "tool",
|
||||||
|
@ -10,8 +10,10 @@
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
|
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]},
|
{"name":"choozi.app.js","url":"app.js"},
|
||||||
{"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]},
|
|
||||||
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"choozi.save"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,3 +38,4 @@
|
||||||
Add fast load capability
|
Add fast load capability
|
||||||
0.21: Remade all icons without a palette for dark theme
|
0.21: Remade all icons without a palette for dark theme
|
||||||
Now re-adds widgets if they were hidden when fast-loading
|
Now re-adds widgets if they were hidden when fast-loading
|
||||||
|
0.22: Fixed crash if item has no image and cutting long overflowing text
|
||||||
|
|
|
@ -183,9 +183,12 @@ let drawCircle = function(index, item, data) {
|
||||||
if (txt.endsWith(" bpm")) txt=txt.slice(0,-4); // hack for heart rate - remove the 'bpm' text
|
if (txt.endsWith(" bpm")) txt=txt.slice(0,-4); // hack for heart rate - remove the 'bpm' text
|
||||||
if(item.hasRange) percent = (data.v-data.min) / (data.max-data.min);
|
if(item.hasRange) percent = (data.v-data.min) / (data.max-data.min);
|
||||||
if(data.short) txt = data.short;
|
if(data.short) txt = data.short;
|
||||||
|
//long text can overflow and we do not draw there anymore..
|
||||||
|
if(txt.length>6) txt = txt.slice(0,5)+"\n"+txt.slice(5,10)
|
||||||
drawGauge(w, h3, percent, color);
|
drawGauge(w, h3, percent, color);
|
||||||
drawInnerCircleAndTriangle(w);
|
drawInnerCircleAndTriangle(w);
|
||||||
writeCircleText(w, txt);
|
writeCircleText(w, txt);
|
||||||
|
if(!img) return; //or get it from the clkinfo?
|
||||||
g.setColor(getCircleIconColor(index, color, percent))
|
g.setColor(getCircleIconColor(index, color, percent))
|
||||||
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
|
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "circlesclock",
|
{ "id": "circlesclock",
|
||||||
"name": "Circles clock",
|
"name": "Circles clock",
|
||||||
"shortName":"Circles clock",
|
"shortName":"Circles clock",
|
||||||
"version":"0.21",
|
"version":"0.22",
|
||||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: First release
|
||||||
|
0.02: Update clock_info to avoid a redraw and image allocation
|
After Width: | Height: | Size: 1012 B |
|
@ -0,0 +1,17 @@
|
||||||
|
(function() {
|
||||||
|
return {
|
||||||
|
name: "Bangle",
|
||||||
|
items: [
|
||||||
|
{ name : "FW",
|
||||||
|
get : () => {
|
||||||
|
return {
|
||||||
|
text : process.env.VERSION,
|
||||||
|
img : atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV")
|
||||||
|
};
|
||||||
|
},
|
||||||
|
show : function() {},
|
||||||
|
hide : function() {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
})
|
|
@ -0,0 +1,13 @@
|
||||||
|
{ "id": "clkinfofw",
|
||||||
|
"name": "Firmware Clockinfo",
|
||||||
|
"version":"0.02",
|
||||||
|
"description": "For clocks that display 'clockinfo', this displays the firmware version string",
|
||||||
|
"icon": "app.png",
|
||||||
|
"type": "clkinfo",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"tags": "clkinfo,firmware",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"clkinfofw.clkinfo.js","url":"clkinfo.js"}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -3,4 +3,5 @@
|
||||||
0.03: Added clkinfo for clocks.
|
0.03: Added clkinfo for clocks.
|
||||||
0.04: Feedback if clkinfo run is called.
|
0.04: Feedback if clkinfo run is called.
|
||||||
0.05: Clkinfo improvements.
|
0.05: Clkinfo improvements.
|
||||||
0.06: Updated clkinfo icon.
|
0.06: Updated clkinfo icon.
|
||||||
|
0.07: Update clock_info to avoid a redraw
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
haItems.items.push({
|
haItems.items.push({
|
||||||
name: null,
|
name: null,
|
||||||
get: () => ({ text: trigger.display, img: trigger.getIcon()}),
|
get: () => ({ text: trigger.display, img: trigger.getIcon()}),
|
||||||
show: function() { haItems.items[i].emit("redraw"); },
|
show: function() {},
|
||||||
hide: function () {},
|
hide: function () {},
|
||||||
run: function() {
|
run: function() {
|
||||||
ha.sendTrigger("TRIGGER_BW");
|
ha.sendTrigger("TRIGGER_BW");
|
||||||
|
@ -23,4 +23,4 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
return haItems;
|
return haItems;
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ha",
|
"id": "ha",
|
||||||
"name": "HomeAssistant",
|
"name": "HomeAssistant",
|
||||||
"version": "0.06",
|
"version": "0.07",
|
||||||
"description": "Integrates your BangleJS into HomeAssistant.",
|
"description": "Integrates your BangleJS into HomeAssistant.",
|
||||||
"icon": "ha.png",
|
"icon": "ha.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "health",
|
"id": "health",
|
||||||
"name": "Health Tracking",
|
"name": "Health Tracking",
|
||||||
|
"shortName": "Health",
|
||||||
"version": "0.17",
|
"version": "0.17",
|
||||||
"description": "Logs health data and provides an app to view it",
|
"description": "Logs health data and provides an app to view it",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
@ -13,3 +13,7 @@
|
||||||
0.27: BJS2: Changed swipe down to swipe up
|
0.27: BJS2: Changed swipe down to swipe up
|
||||||
0.28: Reverted changes to implementation of 0.25
|
0.28: Reverted changes to implementation of 0.25
|
||||||
0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||||
|
0.30: BJS2: swipe seems to be working now
|
||||||
|
0.31: Tweaking the swipe option; Added mylocation as a dependency.
|
||||||
|
Remove calls to Bangle.loadWidgets as they are not needed and create warnings
|
||||||
|
0.32: Added setting to show single timezone small, like where multiple ones are configured
|
|
@ -1,8 +1,17 @@
|
||||||
|
|
||||||
# Hanks World Clock - See the time in four locations
|
# Hanks World Clock - See the time in four locations
|
||||||
|
|
||||||
In addition to the main clock and date in your current location, you can add up to three other locations. Great for travel or remote working.
|
In addition to the main clock and date in your current location, you can add up to three other locations. Great for travel or remote working.
|
||||||
Additionally we show the sunset/sunrise and seconds for the current location and the day name is shown in your locale.
|
Additionally we show the sunset/sunrise and seconds for the current location. The day name is shown in your locale.
|
||||||
If watch is locked, seconds get refreshed every 10 seconds.
|
Also, you can swipe up to show the current time to a friend.
|
||||||
|
|
||||||
|
All this is configurable:
|
||||||
|
|
||||||
|
- Show seconds only when unlocked (saves battery) / always / do not show seconds
|
||||||
|
- Green color on dark mode (on/off)
|
||||||
|
- 1 Offset Small: single location shows as small (like more than 1)
|
||||||
|
- Show sun info (on/off) (set your location in the mylocation app)
|
||||||
|
- Rotation degree on swipe (off / 90 / 180 / 270)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -10,9 +19,11 @@ If watch is locked, seconds get refreshed every 10 seconds.
|
||||||
|
|
||||||
Location for sun set / rise set with mylocation app.
|
Location for sun set / rise set with mylocation app.
|
||||||
|
|
||||||
Provide names and the UTC offsets for up to three other timezones in the app store. These are stored in a json file on your watch. UTC offsets can be decimal (e.g., 5.5 for India).
|
Provide names and the UTC offsets for up to three other timezones in the App Loader before uploading. These are stored in a json file on your watch. UTC offsets can be decimal (e.g., 5.5 for India).
|
||||||
|
|
||||||
The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the app store and update. Currently the clock only supports 24 hour time format for the additional time zones.
|
The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the App Launcher and update. Currently the clock only supports 24 hour time format for the additional time zones.
|
||||||
|
|
||||||
|
BangleJS2: Swipe up to rotate screen (Target rotation can be set in the settings). Swipe up again to go back to the default rotation. So you can show the time to a friend real quick or temporarily change orientation for sports etc.
|
||||||
|
|
||||||
## Requests
|
## Requests
|
||||||
|
|
||||||
|
@ -22,5 +33,4 @@ Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if y
|
||||||
|
|
||||||
Created by Hank.
|
Created by Hank.
|
||||||
|
|
||||||
Based on the great work of "World Clock - 4 time zones". Made by [Scott Hale](https://www.github.com/computermacgyver), based upon the [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock).
|
Based on the great work of "World Clock - 4 time zones". Made by [Scott Hale](https://www.github.com/computermacgyver)
|
||||||
And Sun Clock [Sun Clock](https://github.com/espruino/BangleApps/tree/master/apps/sunclock)
|
|
||||||
|
|
|
@ -2,20 +2,22 @@
|
||||||
|
|
||||||
// ------- Settings file
|
// ------- Settings file
|
||||||
const SETTINGSFILE = "hworldclock.json";
|
const SETTINGSFILE = "hworldclock.json";
|
||||||
var secondsMode;
|
let secondsMode;
|
||||||
var showSunInfo;
|
let showSunInfo;
|
||||||
var colorWhenDark;
|
let colorWhenDark;
|
||||||
|
let rotationTarget;
|
||||||
// ------- Settings file
|
// ------- Settings file
|
||||||
|
|
||||||
|
//const BANGLEJS2 = process.env.HWVERSION == 2;
|
||||||
const big = g.getWidth()>200;
|
const big = g.getWidth()>200;
|
||||||
// Font for primary time and date
|
// Font for primary time and date
|
||||||
const primaryTimeFontSize = big?6:5;
|
const primaryTimeFontSize = big?6:5;
|
||||||
const primaryDateFontSize = big?3:2;
|
const primaryDateFontSize = big?3:2;
|
||||||
require("Font5x9Numeric7Seg").add(Graphics);
|
let font5x9 = require("Font5x9Numeric7Seg").add(Graphics);
|
||||||
require("FontTeletext10x18Ascii").add(Graphics);
|
let font10x18 = require("FontTeletext10x18Ascii").add(Graphics);
|
||||||
|
|
||||||
// Font for single secondary time
|
// Font for single secondary time
|
||||||
const secondaryTimeFontSize = 4;
|
const secondaryTimeFontSize = 4;
|
||||||
const secondaryTimeZoneFontSize = 2;
|
const secondaryTimeZoneFontSize = 2;
|
||||||
|
|
||||||
// Font / columns for multiple secondary times
|
// Font / columns for multiple secondary times
|
||||||
|
@ -24,9 +26,8 @@ const xcol1 = 10;
|
||||||
const xcol2 = g.getWidth() - xcol1;
|
const xcol2 = g.getWidth() - xcol1;
|
||||||
|
|
||||||
const font = "6x8";
|
const font = "6x8";
|
||||||
|
let drag;
|
||||||
|
|
||||||
/* TODO: we could totally use 'Layout' here and
|
|
||||||
avoid a whole bunch of hard-coded offsets */
|
|
||||||
|
|
||||||
const xyCenter = g.getWidth() / 2;
|
const xyCenter = g.getWidth() / 2;
|
||||||
const xyCenterSeconds = xyCenter + (big ? 85 : 68);
|
const xyCenterSeconds = xyCenter + (big ? 85 : 68);
|
||||||
|
@ -39,22 +40,27 @@ const yposWorld = big ? 170 : 120;
|
||||||
const OFFSET_TIME_ZONE = 0;
|
const OFFSET_TIME_ZONE = 0;
|
||||||
const OFFSET_HOURS = 1;
|
const OFFSET_HOURS = 1;
|
||||||
|
|
||||||
var PosInterval = 0;
|
let PosInterval = 0;
|
||||||
|
|
||||||
var offsets = require("Storage").readJSON("hworldclock.settings.json") || [];
|
let offsets = require("Storage").readJSON("hworldclock.settings.json") || [];
|
||||||
|
|
||||||
//=======Sun
|
//=======Sun
|
||||||
setting = require("Storage").readJSON("setting.json",1);
|
let setting = require("Storage").readJSON("setting.json",1);
|
||||||
E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ
|
E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ
|
||||||
SunCalc = require("suncalc"); // from modules folder
|
//https://raw.githubusercontent.com/pebl-hank/BangleApps/master/modules/suncalc.js
|
||||||
|
let SunCalc = require("suncalc"); // from modules folder
|
||||||
const LOCATION_FILE = "mylocation.json";
|
const LOCATION_FILE = "mylocation.json";
|
||||||
var rise = "read";
|
let rise = "read";
|
||||||
var set = "...";
|
let set = "...";
|
||||||
//var pos = {altitude: 20, azimuth: 135};
|
//var pos = {altitude: 20, azimuth: 135};
|
||||||
//var noonpos = {altitude: 37, azimuth: 180};
|
//var noonpos = {altitude: 37, azimuth: 180};
|
||||||
//=======Sun
|
//=======Sun
|
||||||
|
|
||||||
var ampm = "AM";
|
let ampm = "AM";
|
||||||
|
|
||||||
|
let defaultRotation = setting.rotate || 0;
|
||||||
|
let currentRotation = defaultRotation;
|
||||||
|
|
||||||
|
|
||||||
// TESTING CODE
|
// TESTING CODE
|
||||||
// Used to test offset array values during development.
|
// Used to test offset array values during development.
|
||||||
|
@ -87,32 +93,39 @@ const mockOffsets = {
|
||||||
//offsets = mockOffsets.fourOffsets; // should render in columns
|
//offsets = mockOffsets.fourOffsets; // should render in columns
|
||||||
|
|
||||||
// END TESTING CODE
|
// END TESTING CODE
|
||||||
|
|
||||||
|
|
||||||
|
// ================ Load settings
|
||||||
// Load settings
|
// Helper function default setting
|
||||||
function loadMySettings() {
|
let def = function(value, def) {
|
||||||
// Helper function default setting
|
return value !== undefined ? value : def;
|
||||||
function def (value, def) {return value !== undefined ? value : def;}
|
|
||||||
|
|
||||||
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
|
||||||
secondsMode = def(settings.secondsMode, "when unlocked");
|
|
||||||
showSunInfo = def(settings.showSunInfo, true);
|
|
||||||
colorWhenDark = def(settings.colorWhenDark, "green");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||||
|
secondsMode = def(settings.secondsMode, "when unlocked");
|
||||||
|
showSunInfo = def(settings.showSunInfo, true);
|
||||||
|
singleOffsetSmall = def(settings.singleOffsetSmall, false);
|
||||||
|
colorWhenDark = def(settings.colorWhenDark, "green");
|
||||||
|
rotationTarget = def(settings.rotationTarget, "90");
|
||||||
|
rotationTarget = parseInt(rotationTarget) || 0;
|
||||||
|
if (rotationTarget == 90) rotationTarget = 1; // very lame, but works for now.
|
||||||
|
if (rotationTarget == 180) rotationTarget = 2;
|
||||||
|
if (rotationTarget == 270) rotationTarget = 3;
|
||||||
|
// ================ Load settings
|
||||||
|
|
||||||
|
|
||||||
// Check settings for what type our clock should be
|
// Check settings for what type our clock should be
|
||||||
var _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
|
let _12hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]||false;
|
||||||
|
|
||||||
// timeout used to update every minute
|
// timeout used to update every minute
|
||||||
var drawTimeout;
|
let drawTimeout;
|
||||||
var drawTimeoutSeconds;
|
let drawTimeoutSeconds;
|
||||||
var secondsTimeout;
|
let secondsTimeout;
|
||||||
|
|
||||||
g.setBgColor(g.theme.bg);
|
g.setBgColor(g.theme.bg);
|
||||||
|
|
||||||
// schedule a draw for the next minute
|
// schedule a draw for the next minute
|
||||||
function queueDraw() {
|
let queueDraw = function() {
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = setTimeout(function() {
|
drawTimeout = setTimeout(function() {
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
@ -121,7 +134,7 @@ function queueDraw() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// schedule a draw for the next second
|
// schedule a draw for the next second
|
||||||
function queueDrawSeconds() {
|
let queueDrawSeconds = function() {
|
||||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||||
drawTimeoutSeconds = setTimeout(function() {
|
drawTimeoutSeconds = setTimeout(function() {
|
||||||
drawTimeoutSeconds = undefined;
|
drawTimeoutSeconds = undefined;
|
||||||
|
@ -130,28 +143,29 @@ function queueDrawSeconds() {
|
||||||
}, secondsTimeout - (Date.now() % secondsTimeout));
|
}, secondsTimeout - (Date.now() % secondsTimeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
function doublenum(x) {
|
let doublenum = function(x) {
|
||||||
return x < 10 ? "0" + x : "" + x;
|
return x < 10 ? "0" + x : "" + x;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentTimeFromOffset(dt, offset) {
|
let getCurrentTimeFromOffset = function(dt, offset) {
|
||||||
return new Date(dt.getTime() + offset * 60 * 60 * 1000);
|
return new Date(dt.getTime() + offset * 60 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePos() {
|
let updatePos = function() {
|
||||||
coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":0,"lon":0,"location":"-"}; //{"lat":53.3,"lon":10.1,"location":"Pattensen"};
|
let coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":0,"lon":0,"location":"-"}; //{"lat":53.3,"lon":10.1,"location":"Pattensen"};
|
||||||
if (coord.lat != 0 && coord.lon != 0) {
|
if (coord.lat != 0 && coord.lon != 0) {
|
||||||
|
//pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon);
|
||||||
times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon);
|
times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon);
|
||||||
rise = "^" + times.sunrise.toString().split(" ")[4].substr(0,5);
|
rise = "^" + times.sunrise.toString().split(" ")[4].substr(0,5);
|
||||||
set = "v" + times.sunset.toString().split(" ")[4].substr(0,5);
|
set = "v" + times.sunset.toString().split(" ")[4].substr(0,5);
|
||||||
|
//noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon);
|
||||||
} else {
|
} else {
|
||||||
rise = null;
|
rise = null;
|
||||||
set = null;
|
set = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let drawSeconds = function() {
|
||||||
function drawSeconds() {
|
|
||||||
// get date
|
// get date
|
||||||
let d = new Date();
|
let d = new Date();
|
||||||
let da = d.toString().split(" ");
|
let da = d.toString().split(" ");
|
||||||
|
@ -173,16 +187,13 @@ function drawSeconds() {
|
||||||
} else {
|
} else {
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
}
|
}
|
||||||
//console.log("---");
|
|
||||||
//console.log(seconds);
|
|
||||||
if (Bangle.isLocked() && secondsMode != "always") seconds = seconds.slice(0, -1) + ':::'; // we use :: as the font does not have an x
|
if (Bangle.isLocked() && secondsMode != "always") seconds = seconds.slice(0, -1) + ':::'; // we use :: as the font does not have an x
|
||||||
//console.log(seconds);
|
g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true);
|
||||||
g.drawString(`${seconds}`, xyCenterSeconds, yposTime+14, true);
|
|
||||||
queueDrawSeconds();
|
queueDrawSeconds();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
let draw = function() {
|
||||||
// get date
|
// get date
|
||||||
let d = new Date();
|
let d = new Date();
|
||||||
let da = d.toString().split(" ");
|
let da = d.toString().split(" ");
|
||||||
|
@ -194,20 +205,19 @@ function draw() {
|
||||||
let time = da[4].split(":");
|
let time = da[4].split(":");
|
||||||
let hours = time[0],
|
let hours = time[0],
|
||||||
minutes = time[1];
|
minutes = time[1];
|
||||||
|
|
||||||
|
|
||||||
if (_12hour){
|
if (_12hour){
|
||||||
//do 12 hour stuff
|
//do 12 hour stuff
|
||||||
if (hours > 12) {
|
if (hours > 12) {
|
||||||
ampm = "PM";
|
ampm = "PM";
|
||||||
hours = hours - 12;
|
hours = hours - 12;
|
||||||
if (hours < 10) hours = doublenum(hours);
|
if (hours < 10) hours = doublenum(hours);
|
||||||
} else {
|
} else {
|
||||||
ampm = "AM";
|
ampm = "AM";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//g.setFont(font, primaryTimeFontSize);
|
|
||||||
g.setFont("5x9Numeric7Seg",primaryTimeFontSize);
|
g.setFont("5x9Numeric7Seg",primaryTimeFontSize);
|
||||||
if (g.theme.dark) {
|
if (g.theme.dark) {
|
||||||
if (colorWhenDark == "green") {
|
if (colorWhenDark == "green") {
|
||||||
|
@ -219,18 +229,18 @@ function draw() {
|
||||||
g.setColor(g.theme.fg);
|
g.setColor(g.theme.fg);
|
||||||
}
|
}
|
||||||
g.drawString(`${hours}:${minutes}`, xyCenter-10, yposTime, true);
|
g.drawString(`${hours}:${minutes}`, xyCenter-10, yposTime, true);
|
||||||
|
|
||||||
// am / PM ?
|
// am / PM ?
|
||||||
if (_12hour){
|
if (_12hour){
|
||||||
//do 12 hour stuff
|
//do 12 hour stuff
|
||||||
//let ampm = require("locale").medidian(new Date()); Not working
|
//let ampm = require("locale").medidian(new Date()); Not working
|
||||||
g.setFont("Vector", 17);
|
g.setFont("Vector", 17);
|
||||||
g.drawString(ampm, xyCenterSeconds, yAmPm, true);
|
g.drawString(ampm, xyCenterSeconds, yAmPm, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secondsMode != "none") drawSeconds(); // To make sure...
|
if (secondsMode != "none") drawSeconds(); // To make sure...
|
||||||
|
|
||||||
// draw Day, name of month, Date
|
// draw Day, name of month, Date
|
||||||
//DATE
|
//DATE
|
||||||
let localDate = require("locale").date(new Date(), 1);
|
let localDate = require("locale").date(new Date(), 1);
|
||||||
localDate = localDate.substring(0, localDate.length - 5);
|
localDate = localDate.substring(0, localDate.length - 5);
|
||||||
|
@ -248,8 +258,8 @@ function draw() {
|
||||||
minutes = doublenum(dx.getMinutes());
|
minutes = doublenum(dx.getMinutes());
|
||||||
|
|
||||||
|
|
||||||
if (offsets.length === 1) {
|
if (offsets.length === 1 && !singleOffsetSmall) {
|
||||||
let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
|
let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
|
||||||
// For a single secondary timezone, draw it bigger and drop time zone to second line
|
// For a single secondary timezone, draw it bigger and drop time zone to second line
|
||||||
const xOffset = 30;
|
const xOffset = 30;
|
||||||
g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
||||||
|
@ -275,7 +285,7 @@ function draw() {
|
||||||
g.setFontAlign(-1, 0).setFont("Vector",12).drawString(`${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw rise
|
g.setFontAlign(-1, 0).setFont("Vector",12).drawString(`${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw rise
|
||||||
g.setFontAlign(1, 0).drawString(`${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw set
|
g.setFontAlign(1, 0).drawString(`${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw set
|
||||||
} else {
|
} else {
|
||||||
g.setFontAlign(-1, 0).setFont("Vector",11).drawString("set city in \'my location\' app!", 10, 3 + yposWorld + 3 * 15, true);
|
g.setFontAlign(-1, 0).setFont("Vector",11).drawString("set city in \'my location\' app!", 10, 3 + yposWorld + 3 * 15, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//debug settings
|
//debug settings
|
||||||
|
@ -285,34 +295,62 @@ function draw() {
|
||||||
//g.drawString(colorWhenDark, xcol2, 3 + yposWorld + 3 * 15, true);
|
//g.drawString(colorWhenDark, xcol2, 3 + yposWorld + 3 * 15, true);
|
||||||
|
|
||||||
queueDraw();
|
queueDraw();
|
||||||
|
|
||||||
if (secondsMode != "none") queueDrawSeconds();
|
if (secondsMode != "none") queueDrawSeconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean app screen
|
|
||||||
g.clear();
|
|
||||||
|
|
||||||
// Init the settings of the app
|
|
||||||
loadMySettings();
|
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
|
||||||
Bangle.setUI({
|
|
||||||
mode : "clock",
|
|
||||||
remove : function() {
|
|
||||||
// Called to unload all of the clock app
|
|
||||||
if (PosInterval) clearInterval(PosInterval);
|
|
||||||
PosInterval = undefined;
|
|
||||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
|
||||||
drawTimeoutSeconds = undefined;
|
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
|
||||||
drawTimeout = undefined;
|
|
||||||
}});
|
|
||||||
Bangle.loadWidgets();
|
|
||||||
Bangle.drawWidgets();
|
|
||||||
|
|
||||||
|
|
||||||
// draw immediately at first, queue update
|
|
||||||
draw();
|
//if (BANGLEJS2) {
|
||||||
|
let onDrag = e => {
|
||||||
|
if (!drag) { // start dragging
|
||||||
|
drag = {x: e.x, y: e.y};
|
||||||
|
} else if (!e.b) { // released
|
||||||
|
const dx = e.x-drag.x, dy = e.y-drag.y;
|
||||||
|
drag = null;
|
||||||
|
if (Math.abs(dx)>Math.abs(dy)+10) {
|
||||||
|
// horizontal
|
||||||
|
if (dx < dy) {
|
||||||
|
// for later purpose
|
||||||
|
} else {
|
||||||
|
// for later purpose
|
||||||
|
}
|
||||||
|
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
||||||
|
// vertical
|
||||||
|
if (dx < dy) { //down
|
||||||
|
//g.clear().setRotation(defaultRotation);
|
||||||
|
//currentRotation = defaultRotation;
|
||||||
|
//draw();
|
||||||
|
//Bangle.drawWidgets();
|
||||||
|
} else {
|
||||||
|
if (currentRotation == rotationTarget) {
|
||||||
|
g.clear().setRotation(defaultRotation);
|
||||||
|
currentRotation = defaultRotation;
|
||||||
|
} else {
|
||||||
|
g.clear().setRotation(rotationTarget);
|
||||||
|
currentRotation = rotationTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//console.log("tap " + e.x + " " + e.y);
|
||||||
|
if (e.x > 145 && e.y > 145) {
|
||||||
|
// for later purpose
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}; //);
|
||||||
|
Bangle.on("drag", onDrag);
|
||||||
|
//} <-- BJS2 only } else {
|
||||||
|
//setWatch(xxx, BTN1, { repeat: true, debounce:50 }); // maybe adding this later
|
||||||
|
//setWatch(xxx, BTN3, { repeat: true, debounce:50 });
|
||||||
|
//setWatch(xxx, BTN4, { repeat: true, debounce:50 });
|
||||||
|
//setWatch(xxx, BTN5, { repeat: true, debounce:50 });
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!Bangle.isLocked()) { // Initial state
|
if (!Bangle.isLocked()) { // Initial state
|
||||||
|
@ -326,15 +364,15 @@ if (!Bangle.isLocked()) { // Initial state
|
||||||
if (secondsMode != "none") {
|
if (secondsMode != "none") {
|
||||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||||
drawTimeoutSeconds = undefined;
|
drawTimeoutSeconds = undefined;
|
||||||
}
|
}
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
draw(); // draw immediately, queue redraw
|
draw(); // draw immediately, queue redraw
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
if (secondsMode == "always") secondsTimeout = 1000;
|
if (secondsMode == "always") secondsTimeout = 1000;
|
||||||
if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000;
|
if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000;
|
||||||
|
|
||||||
if (secondsMode != "none") {
|
if (secondsMode != "none") {
|
||||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||||
drawTimeoutSeconds = undefined;
|
drawTimeoutSeconds = undefined;
|
||||||
|
@ -348,11 +386,10 @@ if (!Bangle.isLocked()) { // Initial state
|
||||||
updatePos();
|
updatePos();
|
||||||
}
|
}
|
||||||
draw(); // draw immediately, queue redraw
|
draw(); // draw immediately, queue redraw
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let onLock = on => {
|
||||||
Bangle.on('lock',on=>{
|
|
||||||
if (!on) { // UNlocked
|
if (!on) { // UNlocked
|
||||||
if (showSunInfo) {
|
if (showSunInfo) {
|
||||||
if (PosInterval != 0) clearInterval(PosInterval);
|
if (PosInterval != 0) clearInterval(PosInterval);
|
||||||
|
@ -364,7 +401,7 @@ Bangle.on('lock',on=>{
|
||||||
if (secondsMode != "none") {
|
if (secondsMode != "none") {
|
||||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||||
drawTimeoutSeconds = undefined;
|
drawTimeoutSeconds = undefined;
|
||||||
}
|
}
|
||||||
if (drawTimeout) clearTimeout(drawTimeout);
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
drawTimeout = undefined;
|
drawTimeout = undefined;
|
||||||
|
|
||||||
|
@ -373,7 +410,7 @@ Bangle.on('lock',on=>{
|
||||||
|
|
||||||
if (secondsMode == "always") secondsTimeout = 1000;
|
if (secondsMode == "always") secondsTimeout = 1000;
|
||||||
if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000;
|
if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000;
|
||||||
|
|
||||||
if (secondsMode != "none") {
|
if (secondsMode != "none") {
|
||||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||||
drawTimeoutSeconds = undefined;
|
drawTimeoutSeconds = undefined;
|
||||||
|
@ -386,7 +423,39 @@ Bangle.on('lock',on=>{
|
||||||
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
||||||
updatePos();
|
updatePos();
|
||||||
}
|
}
|
||||||
draw(); // draw immediately, queue redraw
|
draw(); // draw immediately, queue redraw
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
Bangle.on('lock', onLock);
|
||||||
|
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : "custom",clock:true,
|
||||||
|
remove : function() {
|
||||||
|
// Called to unload all of the clock app
|
||||||
|
g.setRotation(defaultRotation); // bring back default rotation
|
||||||
|
if (typeof PosInterval === "undefined") {
|
||||||
|
console.log("PosInterval is undefined");
|
||||||
|
} else {
|
||||||
|
if (PosInterval) clearInterval(PosInterval);
|
||||||
|
}
|
||||||
|
PosInterval = undefined;
|
||||||
|
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||||
|
drawTimeoutSeconds = undefined;
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
//if (BANGLEJS2)
|
||||||
|
Bangle.removeListener("drag",onDrag);
|
||||||
|
Bangle.removeListener("onLock",onLock);
|
||||||
|
}});
|
||||||
|
|
||||||
|
|
||||||
|
g.clear().setRotation(defaultRotation); // clean app screen and make sure the default rotation is set
|
||||||
|
draw(); // draw immediately at first, queue update
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "hworldclock",
|
"id": "hworldclock",
|
||||||
"name": "Hanks World Clock",
|
"name": "Hanks World Clock",
|
||||||
"shortName": "Hanks World Clock",
|
"shortName": "Hanks World Clock",
|
||||||
"version": "0.29",
|
"version": "0.32",
|
||||||
"description": "Current time zone plus up to three others",
|
"description": "Current time zone plus up to three others",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"custom": "custom.html",
|
"custom": "custom.html",
|
||||||
|
"dependencies": {"mylocation":"app"},
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"hworldclock.app.js","url":"app.js"},
|
{"name":"hworldclock.app.js","url":"app.js"},
|
||||||
{"name":"hworldclock.img","url":"hworldclock-icon.js","evaluate":true},
|
{"name":"hworldclock.img","url":"hworldclock-icon.js","evaluate":true},
|
||||||
|
@ -19,6 +20,6 @@
|
||||||
],
|
],
|
||||||
"data": [
|
"data": [
|
||||||
{"name":"hworldclock.settings.json"},
|
{"name":"hworldclock.settings.json"},
|
||||||
{"name":"hworldclock.json"}
|
{"name":"hworldclock.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,13 +36,21 @@
|
||||||
"< Back": () => back(),
|
"< Back": () => back(),
|
||||||
"Seconds": stringInSettings("secondsMode", ["always", "when unlocked", "none"]),
|
"Seconds": stringInSettings("secondsMode", ["always", "when unlocked", "none"]),
|
||||||
"Color w. dark": stringInSettings("colorWhenDark", ["green", "default"]),
|
"Color w. dark": stringInSettings("colorWhenDark", ["green", "default"]),
|
||||||
"Show SunInfo": {
|
"1 Offset Small": {
|
||||||
value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true),
|
value: (settings.singleOffsetSmall !== undefined ? settings.singleOffsetSmall : false),
|
||||||
onchange: v => {
|
onchange: v=> {
|
||||||
settings.showSunInfo = v;
|
settings.singleOffsetSmall = v;
|
||||||
writeSettings();
|
writeSettings();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Show SunInfo": {
|
||||||
|
value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true),
|
||||||
|
onchange: v => {
|
||||||
|
settings.showSunInfo = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Rotation": stringInSettings("rotationTarget", ["off", "90", "180", "270"]),
|
||||||
};
|
};
|
||||||
|
|
||||||
E.showMenu(mainmenu);
|
E.showMenu(mainmenu);
|
||||||
|
|
|
@ -17,4 +17,6 @@
|
||||||
0.12: Use Bangle.load and Bangle.showClock
|
0.12: Use Bangle.load and Bangle.showClock
|
||||||
0.13: Fix automatic switch to clock
|
0.13: Fix automatic switch to clock
|
||||||
0.14: Revert use of Bangle.load to classic load calls since widgets would
|
0.14: Revert use of Bangle.load to classic load calls since widgets would
|
||||||
still be loaded when they weren't supposed to.
|
still be loaded when they weren't supposed to.
|
||||||
|
0.15: Ensure that we hide widgets if in fullscreen mode
|
||||||
|
(So that widgets are still hidden if launcher is fast-loaded)
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
if (!settings.fullscreen) {
|
if (!settings.fullscreen) {
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
} else { // for fast-load, if we had widgets then we should hide them
|
||||||
|
require("widget_utils").hide();
|
||||||
}
|
}
|
||||||
let launchCache = s.readJSON("iconlaunch.cache.json", true)||{};
|
let launchCache = s.readJSON("iconlaunch.cache.json", true)||{};
|
||||||
let launchHash = s.hash(/\.info/);
|
let launchHash = s.hash(/\.info/);
|
||||||
|
@ -190,6 +192,9 @@
|
||||||
btn: _=> { if (settings.oneClickExit) Bangle.showClock(); },
|
btn: _=> { if (settings.oneClickExit) Bangle.showClock(); },
|
||||||
remove: function() {
|
remove: function() {
|
||||||
if (timeout) clearTimeout(timeout);
|
if (timeout) clearTimeout(timeout);
|
||||||
|
if (settings.fullscreen) { // for fast-load, if we hid widgets then we should show them again
|
||||||
|
require("widget_utils").show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "iconlaunch",
|
"id": "iconlaunch",
|
||||||
"name": "Icon Launcher",
|
"name": "Icon Launcher",
|
||||||
"shortName" : "Icon launcher",
|
"shortName" : "Icon launcher",
|
||||||
"version": "0.14",
|
"version": "0.15",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
||||||
"tags": "tool,system,launcher",
|
"tags": "tool,system,launcher",
|
||||||
|
|
|
@ -10,3 +10,4 @@
|
||||||
0.10: Added more bundleIds
|
0.10: Added more bundleIds
|
||||||
0.11: Added letters with caron to unicodeRemap, to properly display messages in Czech language
|
0.11: Added letters with caron to unicodeRemap, to properly display messages in Czech language
|
||||||
0.12: Use new message library
|
0.12: Use new message library
|
||||||
|
0.13: Making ANCS message receive more resilient (#2402)
|
||||||
|
|
|
@ -26,7 +26,7 @@ E.on('ANCS',msg=>{
|
||||||
// not a remove - we need to get the message info first
|
// not a remove - we need to get the message info first
|
||||||
function ancsHandler() {
|
function ancsHandler() {
|
||||||
var msg = Bangle.ancsMessageQueue[0];
|
var msg = Bangle.ancsMessageQueue[0];
|
||||||
NRF.ancsGetNotificationInfo( msg.uid ).then( info => {
|
NRF.ancsGetNotificationInfo( msg.uid ).then( info => { // success
|
||||||
|
|
||||||
if(msg.preExisting === true){
|
if(msg.preExisting === true){
|
||||||
info.new = false;
|
info.new = false;
|
||||||
|
@ -38,6 +38,10 @@ E.on('ANCS',msg=>{
|
||||||
Bangle.ancsMessageQueue.shift();
|
Bangle.ancsMessageQueue.shift();
|
||||||
if (Bangle.ancsMessageQueue.length)
|
if (Bangle.ancsMessageQueue.length)
|
||||||
ancsHandler();
|
ancsHandler();
|
||||||
|
}, err=>{ // failure
|
||||||
|
console.log("ancsGetNotificationInfo failed",err);
|
||||||
|
if (Bangle.ancsMessageQueue.length)
|
||||||
|
ancsHandler();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Bangle.ancsMessageQueue.push(msg);
|
Bangle.ancsMessageQueue.push(msg);
|
||||||
|
@ -196,7 +200,11 @@ Bangle.messageResponse = (msg,response) => {
|
||||||
// error/warn here?
|
// error/warn here?
|
||||||
};
|
};
|
||||||
// remove all messages on disconnect
|
// remove all messages on disconnect
|
||||||
NRF.on("disconnect", () => require("messages").clearAll());
|
NRF.on("disconnect", () => {
|
||||||
|
require("messages").clearAll();
|
||||||
|
// Remove any messages from the ANCS queue
|
||||||
|
Bangle.ancsMessageQueue = [];
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// For testing...
|
// For testing...
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "ios",
|
"id": "ios",
|
||||||
"name": "iOS Integration",
|
"name": "iOS Integration",
|
||||||
"version": "0.12",
|
"version": "0.13",
|
||||||
"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",
|
||||||
|
|
|
@ -4,3 +4,4 @@
|
||||||
0.04: Fix issue if going back without typing.
|
0.04: Fix issue if going back without typing.
|
||||||
0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat.
|
0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat.
|
||||||
0.06: Support input of numbers and uppercase characters.
|
0.06: Support input of numbers and uppercase characters.
|
||||||
|
0.07: Support input of symbols.
|
||||||
|
|
|
@ -4,7 +4,7 @@ A library that provides the ability to input text by swiping PalmOS Graffiti-sty
|
||||||
|
|
||||||
To get a legend of available characters, just tap the screen.
|
To get a legend of available characters, just tap the screen.
|
||||||
|
|
||||||
To switch between the input of alphabetic and numeric characters tap the widget which displays either "123" or "ABC".
|
To switch between the input of alphabetic, numeric and symbol characters tap the widget which displays either "123", "ABC" or "?:$".
|
||||||
|
|
||||||
To switch between lowercase and uppercase characters do an up swipe.
|
To switch between lowercase and uppercase characters do an up swipe.
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
exports.INPUT_MODE_ALPHA = 0;
|
exports.INPUT_MODE_ALPHA = 0;
|
||||||
exports.INPUT_MODE_NUM = 1;
|
exports.INPUT_MODE_NUM = 1;
|
||||||
|
exports.INPUT_MODE_SYM = 2;
|
||||||
|
|
||||||
/* To make your own strokes, type:
|
/* To make your own strokes, type:
|
||||||
|
|
||||||
|
@ -47,9 +48,41 @@ exports.getStrokes = function(mode, cb) {
|
||||||
cb("7", new Uint8Array([47, 38, 48, 38, 53, 38, 66, 38, 85, 38, 103, 38, 117, 38, 125, 38, 129, 38, 134, 41, 135, 47, 135, 54, 135, 66, 131, 93, 124, 126, 116, 149, 109, 161, 105, 168]));
|
cb("7", new Uint8Array([47, 38, 48, 38, 53, 38, 66, 38, 85, 38, 103, 38, 117, 38, 125, 38, 129, 38, 134, 41, 135, 47, 135, 54, 135, 66, 131, 93, 124, 126, 116, 149, 109, 161, 105, 168]));
|
||||||
cb("8", new Uint8Array([122, 61, 102, 61, 83, 61, 60, 61, 47, 62, 45, 78, 58, 99, 84, 112, 105, 122, 118, 134, 121, 149, 113, 165, 86, 171, 59, 171, 47, 164, 45, 144, 50, 132, 57, 125, 67, 117, 78, 109, 87, 102, 96, 94, 105, 86, 113, 85]));
|
cb("8", new Uint8Array([122, 61, 102, 61, 83, 61, 60, 61, 47, 62, 45, 78, 58, 99, 84, 112, 105, 122, 118, 134, 121, 149, 113, 165, 86, 171, 59, 171, 47, 164, 45, 144, 50, 132, 57, 125, 67, 117, 78, 109, 87, 102, 96, 94, 105, 86, 113, 85]));
|
||||||
cb("9", new Uint8Array([122, 58, 117, 55, 112, 51, 104, 51, 95, 51, 86, 51, 77, 51, 68, 51, 60, 51, 54, 56, 47, 64, 46, 77, 46, 89, 46, 96, 51, 103, 64, 109, 74, 110, 83, 110, 94, 107, 106, 102, 116, 94, 124, 84, 127, 79, 128, 78, 128, 94, 128, 123, 128, 161, 128, 175]));
|
cb("9", new Uint8Array([122, 58, 117, 55, 112, 51, 104, 51, 95, 51, 86, 51, 77, 51, 68, 51, 60, 51, 54, 56, 47, 64, 46, 77, 46, 89, 46, 96, 51, 103, 64, 109, 74, 110, 83, 110, 94, 107, 106, 102, 116, 94, 124, 84, 127, 79, 128, 78, 128, 94, 128, 123, 128, 161, 128, 175]));
|
||||||
|
} else if (mode === exports.INPUT_MODE_SYM) {
|
||||||
|
cb("?", new Uint8Array([36, 69, 39, 68, 44, 65, 52, 60, 61, 56, 70, 51, 78, 47, 87, 46, 96, 46, 108, 46, 121, 49, 128, 56, 129, 63, 126, 76, 119, 91, 108, 105, 103, 114, 98, 118, 93, 124, 91, 131, 91, 143, 91, 155, 91, 163]));
|
||||||
|
cb(".", new Uint8Array([105, 158, 97, 157, 80, 150, 60, 140, 44, 127, 34, 110, 31, 97, 31, 84, 35, 74, 48, 59, 78, 55, 115, 57, 145, 70, 159, 89, 162, 112, 160, 138, 153, 153, 144, 164, 125, 170, 103, 171]));
|
||||||
|
cb(",", new Uint8Array([140, 44, 139, 45, 138, 46, 137, 47, 135, 49, 132, 51, 127, 55, 123, 58, 117, 62, 112, 67, 105, 71, 100, 77, 93, 82, 86, 86, 80, 91, 74, 96, 69, 101, 64, 105, 60, 108, 57, 112, 53, 115, 51, 117, 49, 119, 48, 121, 47, 122, 46, 122, 46, 123]));
|
||||||
|
cb("'", new Uint8Array([100, 50, 100, 160]));
|
||||||
|
cb("-", new Uint8Array([34, 63, 36, 63, 40, 63, 46, 63, 54, 63, 63, 63, 72, 63, 82, 63, 92, 63, 103, 63, 113, 63, 124, 63, 132, 63, 139, 63, 143, 63, 145, 63, 147, 63, 149, 63, 152, 63]));
|
||||||
|
cb("_", new Uint8Array([34, 84, 36, 84, 40, 84, 47, 84, 56, 84, 67, 84, 81, 84, 95, 84, 108, 84, 120, 84, 131, 84, 139, 84, 146, 84, 149, 84, 151, 84, 154, 84, 155, 83, 154, 81, 150, 78, 143, 74, 130, 71, 111, 68, 90, 65, 73, 64, 60, 64, 51, 64, 46, 64]));
|
||||||
|
cb("\"", new Uint8Array([24, 168, 24, 158, 28, 132, 33, 102, 37, 82, 41, 66, 46, 54, 50, 47, 54, 46, 60, 49, 67, 64, 73, 88, 80, 114, 87, 138, 95, 149, 109, 145, 123, 128, 130, 108, 135, 87, 136, 70, 136, 57, 136, 50]));
|
||||||
|
cb(":", new Uint8Array([24, 62, 24, 63, 24, 68, 26, 73, 27, 80, 29, 88, 31, 94, 33, 101, 35, 108, 37, 114, 39, 121, 39, 127, 39, 131, 39, 134, 39, 135, 39, 133, 39, 130, 41, 125, 45, 114, 48, 100, 51, 89, 52, 81, 52, 74, 52, 70, 52, 67, 52, 63, 52, 60, 52, 57]));
|
||||||
|
cb(";", new Uint8Array([142, 58, 139, 59, 136, 61, 131, 65, 124, 71, 116, 79, 105, 87, 94, 98, 82, 109, 70, 121, 58, 132, 49, 141, 40, 149, 33, 156, 28, 160, 24, 164, 23, 166, 22, 164, 25, 156, 33, 138, 47, 111, 66, 81, 82, 58, 95, 41, 103, 30]));
|
||||||
|
cb("(", new Uint8Array([72, 51, 70, 51, 68, 51, 66, 54, 63, 56, 61, 59, 58, 61, 56, 65, 54, 70, 51, 74, 49, 79, 47, 83, 45, 87, 44, 92, 44, 94, 44, 96, 44, 99, 44, 101, 44, 104, 44, 107, 44, 114, 44, 120, 46, 127, 49, 135, 52, 141, 56, 145]));
|
||||||
|
cb(")", new Uint8Array([18, 42, 21, 43, 24, 45, 28, 47, 32, 50, 37, 53, 40, 58, 44, 62, 46, 69, 48, 76, 50, 81, 52, 85, 53, 90, 53, 94, 53, 98, 53, 103, 53, 106, 53, 111, 53, 119, 53, 129, 52, 137, 50, 142, 47, 146]));
|
||||||
|
cb("[", new Uint8Array([121, 138, 118, 143, 114, 146, 110, 149, 105, 152, 98, 152, 91, 152, 83, 152, 77, 152, 67, 151, 59, 146, 52, 138, 47, 131, 47, 124, 48, 118, 57, 115, 64, 115, 67, 113, 64, 106, 59, 95, 53, 85, 48, 80, 47, 74, 47, 64, 53, 57, 65, 56, 83, 56, 99, 61]));
|
||||||
|
cb("]", new Uint8Array([36, 136, 42, 140, 54, 145, 70, 149, 84, 151, 98, 149, 109, 143, 113, 135, 113, 127, 104, 115, 87, 105, 75, 103, 76, 98, 87, 84, 96, 67, 100, 54, 97, 48, 90, 45, 76, 45, 60, 47, 44, 52]));
|
||||||
|
cb("<", new Uint8Array([154, 122, 151, 122, 149, 121, 147, 118, 144, 116, 139, 114, 133, 112, 126, 110, 118, 107, 108, 105, 97, 102, 86, 97, 75, 93, 64, 90, 56, 88, 49, 85, 46, 84, 41, 82, 40, 80, 47, 76, 63, 69, 86, 59, 106, 50, 121, 44, 128, 40]));
|
||||||
|
cb(">", new Uint8Array([28, 115, 31, 115, 38, 113, 48, 110, 57, 107, 68, 103, 79, 98, 90, 94, 98, 92, 104, 90, 111, 88, 117, 85, 122, 83, 125, 81, 127, 80, 129, 80, 132, 80, 130, 78, 126, 75, 120, 72, 110, 69, 98, 66, 85, 63, 72, 60, 59, 57, 45, 53, 36, 49, 30, 46]));
|
||||||
|
cb("@", new Uint8Array([82, 50, 76, 50, 67, 50, 59, 50, 50, 51, 43, 57, 38, 68, 34, 83, 33, 95, 33, 108, 34, 121, 42, 136, 57, 148, 72, 155, 85, 157, 98, 155, 110, 149, 120, 139, 128, 127, 134, 119, 137, 114, 138, 107, 138, 98, 138, 88, 138, 77, 137, 71, 134, 65, 128, 60, 123, 58]));
|
||||||
|
cb("#", new Uint8Array([23, 70, 23, 76, 26, 85, 30, 97, 36, 112, 40, 129, 45, 142, 49, 152, 53, 158, 59, 161, 67, 155, 78, 130, 84, 98, 88, 76, 90, 68, 96, 62, 102, 61, 108, 61, 119, 67, 126, 80, 131, 101, 135, 129, 136, 152]));
|
||||||
|
cb("$", new Uint8Array([159, 72, 157, 70, 155, 68, 151, 66, 145, 63, 134, 60, 121, 58, 108, 56, 96, 55, 83, 55, 73, 55, 64, 56, 57, 60, 52, 65, 49, 71, 49, 76, 50, 81, 55, 87, 71, 94, 94, 100, 116, 104, 131, 108, 141, 114, 145, 124, 142, 135, 124, 146, 97, 153, 70, 157, 52, 158]));
|
||||||
|
cb("%", new Uint8Array([31, 39, 39, 54, 51, 78, 60, 97, 62, 107, 59, 118, 47, 118, 44, 109, 46, 92, 56, 73, 69, 62, 92, 61, 115, 70, 125, 90, 126, 110, 125, 122, 118, 127, 111, 127, 105, 124, 105, 115, 105, 97, 109, 75, 117, 56, 124, 45]));
|
||||||
|
cb("^", new Uint8Array([28, 175, 28, 168, 33, 156, 37, 142, 41, 128, 46, 111, 51, 95, 58, 82, 62, 75, 68, 68, 74, 57, 81, 49, 88, 44, 93, 44, 102, 56, 113, 79, 118, 95, 123, 110, 131, 130, 135, 146, 136, 158]));
|
||||||
|
cb("&", new Uint8Array([122, 61, 102, 61, 83, 61, 60, 61, 47, 62, 45, 78, 58, 99, 84, 112, 105, 122, 118, 134, 121, 149, 113, 165, 86, 171, 59, 171, 47, 164, 45, 144, 50, 132, 57, 125, 67, 117, 78, 109, 87, 102, 96, 94, 105, 86, 113, 85]));
|
||||||
|
cb("*", new Uint8Array([35, 61, 41, 62, 53, 68, 72, 78, 91, 91, 103, 99, 113, 103, 119, 106, 124, 107, 131, 107, 139, 107, 150, 107, 161, 104, 166, 97, 166, 89, 165, 78, 162, 70, 158, 61, 151, 54, 144, 51, 132, 51, 115, 57, 98, 66, 82, 78, 65, 89, 52, 100, 44, 109]));
|
||||||
|
cb("!", new Uint8Array([100, 160, 100, 50]));
|
||||||
|
cb("~", new Uint8Array([133, 40, 133, 48, 133, 65, 133, 87, 133, 105, 132, 116, 128, 125, 124, 133, 120, 140, 114, 146, 107, 148, 101, 147, 91, 139, 82, 126, 74, 108, 70, 91, 70, 82, 70, 75, 70, 65, 68, 57, 62, 51, 57, 50, 49, 57, 41, 76, 36, 96, 33, 114, 33, 132]));
|
||||||
|
cb("+", new Uint8Array([151, 41, 146, 46, 133, 55, 116, 71, 101, 87, 87, 98, 74, 105, 63, 109, 54, 110, 43, 106, 36, 94, 36, 80, 36, 68, 42, 60, 60, 58, 91, 64, 115, 77, 129, 88, 139, 99, 144, 106]));
|
||||||
|
cb("=", new Uint8Array([34, 46, 47, 46, 70, 46, 87, 46, 96, 46, 101, 46, 104, 46, 102, 50, 96, 58, 80, 78, 62, 100, 49, 117, 40, 127, 43, 132, 61, 132, 84, 132, 99, 132]));
|
||||||
|
cb("\\", new Uint8Array([25, 38, 26, 40, 30, 43, 35, 48, 43, 54, 54, 63, 65, 74, 76, 85, 87, 96, 98, 108, 108, 121, 116, 131, 123, 138, 127, 144, 131, 148, 134, 152, 136, 155]));
|
||||||
|
cb("|", new Uint8Array([66, 146, 66, 144, 66, 140, 66, 134, 66, 125, 66, 114, 66, 102, 66, 92, 66, 83, 66, 77, 66, 71, 66, 67, 66, 62, 66, 58, 66, 53, 66, 49, 66, 48, 66, 46, 64, 42, 61, 41, 58, 42, 54, 47, 51, 55, 46, 67, 40, 81, 37, 93, 34, 102, 30, 109, 28, 116]));
|
||||||
|
cb("/", new Uint8Array([24, 173, 26, 171, 30, 166, 36, 158, 44, 148, 53, 137, 63, 126, 73, 115, 82, 104, 91, 95, 99, 87, 105, 80, 112, 74, 117, 70, 122, 65, 125, 61, 127, 60, 129, 57, 133, 53, 136, 50, 137, 47]));
|
||||||
}
|
}
|
||||||
cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103]));
|
cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103]));
|
||||||
cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116]));
|
if (mode === exports.INPUT_MODE_ALPHA || mode === exports.INPUT_MODE_NUM) {
|
||||||
|
cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116]));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.input = function(options) {
|
exports.input = function(options) {
|
||||||
|
@ -129,14 +162,26 @@ exports.input = function(options) {
|
||||||
flashInterval = undefined;
|
flashInterval = undefined;
|
||||||
|
|
||||||
g.reset();
|
g.reset();
|
||||||
g.clearRect(R).setColor("#f00");
|
g.setFont("6x8");
|
||||||
var n=0;
|
g.clearRect(R);
|
||||||
|
let n=0;
|
||||||
exports.getStrokes(input_mode, (id,s) => {
|
exports.getStrokes(input_mode, (id,s) => {
|
||||||
var x = n%6;
|
let x = n%6;
|
||||||
var y = (n-x)/6;
|
let y = (n-x)/6;
|
||||||
s = g.transformVertices(s, {scale:0.16, x:R.x+x*30-4, y:R.y+y*30-4});
|
s = g.transformVertices(s, {scale:0.16, x:R.x+x*30-4, y:R.y+y*30-4});
|
||||||
g.fillRect(s[0]-1,s[1]-2,s[0]+1,s[1]+1);
|
g.fillRect(s[0]-1,s[1]-2,s[0]+1,s[1]+1);
|
||||||
g.drawPoly(s);
|
g.setColor("#f00").drawPoly(s);
|
||||||
|
switch(id) {
|
||||||
|
case 'SHIFT':
|
||||||
|
g.setBgColor(0).setColor("#00f").drawImage(atob("CgqBAfP4fh8D4fh+H4fh+HA="), R.x+x*30+20, R.y+y*30+20);
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
case '\n':
|
||||||
|
case ' ':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
g.setColor("#00f").drawString(shift ? id.toUpperCase() : id, R.x+x*30+20, R.y+y*30+20);
|
||||||
|
}
|
||||||
n++;
|
n++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -177,7 +222,7 @@ exports.input = function(options) {
|
||||||
// Switches between alphabetic and numeric input
|
// Switches between alphabetic and numeric input
|
||||||
function cycleInput() {
|
function cycleInput() {
|
||||||
input_mode++;
|
input_mode++;
|
||||||
if (input_mode > exports.INPUT_MODE_NUM) input_mode = 0;
|
if (input_mode > exports.INPUT_MODE_SYM) input_mode = 0;
|
||||||
shift = false;
|
shift = false;
|
||||||
setupStrokes();
|
setupStrokes();
|
||||||
show();
|
show();
|
||||||
|
@ -201,6 +246,8 @@ exports.input = function(options) {
|
||||||
g.drawString(shift ? "ABC" : "abc", this.x, this.y);
|
g.drawString(shift ? "ABC" : "abc", this.x, this.y);
|
||||||
} else if (input_mode === exports.INPUT_MODE_NUM) {
|
} else if (input_mode === exports.INPUT_MODE_NUM) {
|
||||||
g.drawString("123", this.x, this.y);
|
g.drawString("123", this.x, this.y);
|
||||||
|
} else if (input_mode === exports.INPUT_MODE_SYM) {
|
||||||
|
g.drawString("?:$", this.x, this.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "kbswipe",
|
{ "id": "kbswipe",
|
||||||
"name": "Swipe keyboard",
|
"name": "Swipe keyboard",
|
||||||
"version":"0.06",
|
"version":"0.07",
|
||||||
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
|
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type":"textinput",
|
"type":"textinput",
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
require("heatshrink").decompress(atob("mEwwkA/4A/AH4AUiIAKCxXzC5c/C5PyC5cvC8PxC5cfLxNEABhgI+gXNp4X3//+9wAK96PJC6/zC5bvKC6//C5YWKC4nUoMUpoXS8lDn/zmlOC6NCA4ckC6Hkl4HD+QwCC5c+LoIsCoSKBMIPjC5tD//0olEp//mgXNmMRiYuBC4JjBBAYAK+MRj//CwIABBAgXkI5AXOiRyBC4J8BkIXN+dEoKnFiNEAYIXNa4sUC59EJAIACkIHBC5iMCoMTn/zmIuBSQIXODAMRAAKqDABikCAAqqBC8i8CAArCBC/n0C49PC5oA/AH4AIA=="))
|
require("heatshrink").decompress(atob("mEw4UA///1NygH+zn/Jf4AJgdVAAnABZ8BBYtABbc1BYtcBYcVBYtUBbcC1QAEwALPgYLFQYoLWgAHBytWAYK0F1Wpv/9tQLH0v//9aBY+XBYPWBY3qz/1r/21YLGv/Vq/9BY3Vv6NB/tXBaMVBYamEBZ1fHYP1BY01r5TB+ruEBYVXNYPVBY9VBYNVBY0FqoiBqtQBY4ACBb0NBYdwBbsBBYdABYoA/AAg="))
|
||||||
|
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -0,0 +1 @@
|
||||||
|
atob("MDABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf///T//+f///T///Q///Qf//Sf//Qf//Pf//Qf//AP//Yf//f///c///f///f///fiD/f8f/fur/f9f/fir/f9f/f67/f9f/fy7/f8f/fz//f+//AAAAAAAAAAAAAAAAf///////TD/u///vQB/EX/9PUV/d3/9fSd/F3/9HWd/d3/9Xf9/d//9Hf///////f///////f///8Hg/fmefwPAffmecz/Offmecz/Offmecz/OffmAcwHOffngc8DPffn+c/zOffn+c/zOffn+c/zOffn+f8DAff///8Hg/f///8Pw/f///////f//////+P//////8AAAAAAAAAAAAAAAAAAAAAAAA")
|
|
@ -0,0 +1,84 @@
|
||||||
|
Graphics.prototype.setFont7Seg = function() {
|
||||||
|
return this.setFontCustom(atob("AAAAAAAAAAAACAQCAAAAAAIAd0BgMBdwAAAAAAAADuAAAB0RiMRcAAAAAiMRiLuAAAcAQCAQdwAADgiMRiIOAAAd0RiMRBwAAAAgEAgDuAAAd0RiMRdwAADgiMRiLuAAAABsAAAd0QiEQdwAADuCIRCIOAAAd0BgMBAAAAAOCIRCLuAAAd0RiMRAAAADuiEQiAAAAAd0BgMBBwAADuCAQCDuAAAdwAAAAAAAAAAAIBALuAAAdwQCAQdwAADuAIBAIAAAAd0AgEAcEAgEAdwAd0AgEAdwAADugMBgLuAAAd0QiEQcAAADgiEQiDuAAAd0AgEAAAAADgiMRiIOAAAAEAgEAdwAADuAIBALuAAAdwBAIBdwAADuAIBAIOAIBALuADuCAQCDuAAAcAQCAQdwAAAOiMRiLgAAAA=="), 32, atob("BwAAAAAAAAAAAAAAAAcCAAcHBwcHBwcHBwcEAAAAAAAABwcHBwcHBwcHBwcHCgcHBwcHBwcHBwoHBwc="), 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||||
|
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
||||||
|
let drawTimeout;
|
||||||
|
|
||||||
|
// Actually draw the watch face
|
||||||
|
let draw = function() {
|
||||||
|
var x = R.x + R.w/2;
|
||||||
|
var y = R.y + R.h/2;
|
||||||
|
g.reset().setColor(g.theme.bg).setBgColor(g.theme.fg);
|
||||||
|
g.clearRect(R.x,barY+2,R.x2,R.y2-8);
|
||||||
|
var date = new Date();
|
||||||
|
var timeStr = require("locale").time(date, 1); // Hour and minute
|
||||||
|
g.setFontAlign(0, 0).setFont("7Seg:5").drawString(timeStr, x, y+39);
|
||||||
|
// Show date and day of week
|
||||||
|
g.setFontAlign(0, 0).setFont("7Seg:2");
|
||||||
|
g.setFontAlign(-1, 0).drawString(require("locale").meridian(date).toUpperCase(), R.x+6, y);
|
||||||
|
g.setFontAlign(0, 0).drawString(require("locale").dow(date, 1).toUpperCase(), x, y);
|
||||||
|
g.setFontAlign(1, 0).drawString(date.getDate(), R.x2 - 6, y);
|
||||||
|
|
||||||
|
// queue next draw
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = setTimeout(function() {
|
||||||
|
drawTimeout = undefined;
|
||||||
|
draw();
|
||||||
|
}, 60000 - (Date.now() % 60000));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI({
|
||||||
|
mode : "clock",
|
||||||
|
remove : function() {
|
||||||
|
// Called to unload all of the clock app
|
||||||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||||||
|
drawTimeout = undefined;
|
||||||
|
delete Graphics.prototype.setFont7Seg;
|
||||||
|
// remove info menu
|
||||||
|
clockInfoMenu.remove();
|
||||||
|
delete clockInfoMenu;
|
||||||
|
clockInfoMenu2.remove();
|
||||||
|
delete clockInfoMenu2;
|
||||||
|
// reset theme
|
||||||
|
g.setTheme(oldTheme);
|
||||||
|
}});
|
||||||
|
// Load widgets
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
var R = Bangle.appRect;
|
||||||
|
R.x+=1;
|
||||||
|
R.y+=1;
|
||||||
|
R.x2-=1;
|
||||||
|
R.y2-=1;
|
||||||
|
R.w-=2;
|
||||||
|
R.h-=2;
|
||||||
|
var midX = R.x+R.w/2;
|
||||||
|
var barY = 80;
|
||||||
|
// Clear the screen once, at startup
|
||||||
|
let oldTheme = g.theme;
|
||||||
|
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(1);
|
||||||
|
g.fillRect({x:R.x, y:R.y, w:R.w, h:R.h, r:8}).clearRect(R.x,barY,R.w,barY+1).clearRect(midX,R.y,midX+1,barY);
|
||||||
|
draw();
|
||||||
|
setTimeout(Bangle.drawWidgets,0);
|
||||||
|
|
||||||
|
let clockInfoDraw = (itm, info, options) => {
|
||||||
|
let texty = options.y+41;
|
||||||
|
g.reset().setFont("7Seg").setColor(g.theme.bg).setBgColor(g.theme.fg);
|
||||||
|
if (options.focus) g.setBgColor("#FF0");
|
||||||
|
g.clearRect({x:options.x,y:options.y,w:options.w,h:options.h,r:8});
|
||||||
|
|
||||||
|
if (info.img) g.drawImage(info.img, options.x+2, options.y+2);
|
||||||
|
var title = clockInfoItems[options.menuA].name;
|
||||||
|
var text = info.text.toString().toUpperCase();
|
||||||
|
if (title!="Bangle") g.setFontAlign(1,0).drawString(title.toUpperCase(), options.x+options.w-2, options.y+14);
|
||||||
|
if (g.setFont("7Seg:2").stringWidth(text)+8>options.w) g.setFont("7Seg");
|
||||||
|
g.setFontAlign(0,0).drawString(text, options.x+options.w/2, options.y+40);
|
||||||
|
|
||||||
|
};
|
||||||
|
let clockInfoItems = require("clock_info").load();
|
||||||
|
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:R.x, y:R.y, w:midX-2, h:barY-R.y-2, draw : clockInfoDraw});
|
||||||
|
let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { x:midX+2, y:R.y, w:midX-3, h:barY-R.y-2, draw : clockInfoDraw});
|
||||||
|
}
|
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,14 @@
|
||||||
|
{ "id": "lcdclock",
|
||||||
|
"name": "LCD Clock",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A Casio-style clock, with ClockInfo areas at the top and bottom. Tap them and swipe up/down to toggle between different information",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clock",
|
||||||
|
"tags": "clock,clkinfo",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"lcdclock.app.js","url":"app.js"},
|
||||||
|
{"name":"lcdclock.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App.
|
0.01: New App.
|
||||||
0.02: Performance improvements.
|
0.02: Performance improvements.
|
||||||
|
0.03: Update clock_info to avoid a redraw
|
||||||
|
|
|
@ -52,22 +52,22 @@ var H = g.getHeight();
|
||||||
items: [
|
items: [
|
||||||
{ name: "time",
|
{ name: "time",
|
||||||
get: () => ({ text: getTime(), img: null}),
|
get: () => ({ text: getTime(), img: null}),
|
||||||
show: function() { dateMenu.items[0].emit("redraw"); },
|
show: function() {},
|
||||||
hide: function () {}
|
hide: function () {}
|
||||||
},
|
},
|
||||||
{ name: "day",
|
{ name: "day",
|
||||||
get: () => ({ text: getDay(), img: null}),
|
get: () => ({ text: getDay(), img: null}),
|
||||||
show: function() { dateMenu.items[2].emit("redraw"); },
|
show: function() {},
|
||||||
hide: function () {}
|
hide: function () {}
|
||||||
},
|
},
|
||||||
{ name: "date",
|
{ name: "date",
|
||||||
get: () => ({ text: getDate(), img: null}),
|
get: () => ({ text: getDate(), img: null}),
|
||||||
show: function() { dateMenu.items[1].emit("redraw"); },
|
show: function() {},
|
||||||
hide: function () {}
|
hide: function () {}
|
||||||
},
|
},
|
||||||
{ name: "week",
|
{ name: "week",
|
||||||
get: () => ({ text: weekOfYear(), img: null}),
|
get: () => ({ text: weekOfYear(), img: null}),
|
||||||
show: function() { dateMenu.items[3].emit("redraw"); },
|
show: function() {},
|
||||||
hide: function () {}
|
hide: function () {}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "linuxclock",
|
"id": "linuxclock",
|
||||||
"name": "Linux Clock",
|
"name": "Linux Clock",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "A Linux inspired clock.",
|
"description": "A Linux inspired clock.",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
|
|
@ -82,3 +82,7 @@
|
||||||
0.58: Fast load messages without writing to flash
|
0.58: Fast load messages without writing to flash
|
||||||
Don't write messages to flash until the app closes
|
Don't write messages to flash until the app closes
|
||||||
0.59: Ensure we do write messages if messages app can't be fast loaded (see #2373)
|
0.59: Ensure we do write messages if messages app can't be fast loaded (see #2373)
|
||||||
|
0.60: Fix saving of removal messages if UI not open
|
||||||
|
0.61: Fix regression where loading into messages app stops back from working (#2398)
|
||||||
|
0.62: Remove '.show' field, tidyup and fix .open if fast load not enabled
|
||||||
|
0.63: Fix messages app loading on clock without fast load
|
||||||
|
|
|
@ -72,10 +72,7 @@ var onMessagesModified = function(type,msg) {
|
||||||
Bangle.on("message", onMessagesModified);
|
Bangle.on("message", onMessagesModified);
|
||||||
|
|
||||||
function saveMessages() {
|
function saveMessages() {
|
||||||
require("messages").write(MESSAGES.map(m => {
|
require("messages").write(MESSAGES);
|
||||||
delete m.show;
|
|
||||||
return m;
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
E.on("kill", saveMessages);
|
E.on("kill", saveMessages);
|
||||||
|
|
||||||
|
@ -116,11 +113,12 @@ function showMapMessage(msg) {
|
||||||
Bangle.setUI({mode:"updown", back: back}, back); // any input takes us back
|
Bangle.setUI({mode:"updown", back: back}, back); // any input takes us back
|
||||||
}
|
}
|
||||||
|
|
||||||
let updateLabelsInterval,
|
let updateLabelsInterval;
|
||||||
music = {artist: "", album: "", title: ""}; // defaults, so e.g. msg.title.length doesn't error
|
|
||||||
function showMusicMessage(msg) {
|
function showMusicMessage(msg) {
|
||||||
active = "music";
|
active = "music";
|
||||||
msg = Object.assign(music, msg); // combine+remember "musicinfo" and "musicstate" messages
|
// defaults, so e.g. msg.xyz.length doesn't error. `msg` should contain up to date info
|
||||||
|
msg = Object.assign({artist: "", album: "", track: "Music"}, msg);
|
||||||
openMusic = msg.state=="play";
|
openMusic = msg.state=="play";
|
||||||
var trackScrollOffset = 0;
|
var trackScrollOffset = 0;
|
||||||
var artistScrollOffset = 0;
|
var artistScrollOffset = 0;
|
||||||
|
@ -349,6 +347,7 @@ function showMessage(msgid) {
|
||||||
clockIfNoMsg : bool
|
clockIfNoMsg : bool
|
||||||
clockIfAllRead : bool
|
clockIfAllRead : bool
|
||||||
showMsgIfUnread : bool
|
showMsgIfUnread : bool
|
||||||
|
openMusic : bool // open music if it's playing
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
function checkMessages(options) {
|
function checkMessages(options) {
|
||||||
|
@ -364,12 +363,9 @@ function checkMessages(options) {
|
||||||
}
|
}
|
||||||
// we have >0 messages
|
// we have >0 messages
|
||||||
var newMessages = MESSAGES.filter(m=>m.new&&m.id!="music");
|
var newMessages = MESSAGES.filter(m=>m.new&&m.id!="music");
|
||||||
var toShow = MESSAGES.find(m=>m.show);
|
|
||||||
if (toShow) {
|
|
||||||
newMessages.unshift(toShow);
|
|
||||||
}
|
|
||||||
// If we have a new message, show it
|
// If we have a new message, show it
|
||||||
if ((toShow||options.showMsgIfUnread) && newMessages.length) {
|
if (options.showMsgIfUnread && newMessages.length) {
|
||||||
|
delete newMessages[0].show; // stop us getting stuck here if we're called a second time
|
||||||
showMessage(newMessages[0].id);
|
showMessage(newMessages[0].id);
|
||||||
// buzz after showMessage, so being busy during layout doesn't affect the buzz pattern
|
// buzz after showMessage, so being busy during layout doesn't affect the buzz pattern
|
||||||
if (global.BUZZ_ON_NEW_MESSAGE) {
|
if (global.BUZZ_ON_NEW_MESSAGE) {
|
||||||
|
@ -381,8 +377,8 @@ function checkMessages(options) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// no new messages: show playing music? (only if we have playing music to show)
|
// no new messages: show playing music? Only if we have playing music, or state=="show" (set by messagesmusic)
|
||||||
if (options.openMusic && MESSAGES.some(m=>m.id=="music" && m.track && m.state=="play"))
|
if (options.openMusic && MESSAGES.some(m=>m.id=="music" && ((m.track && m.state=="play") || m.state=="show")))
|
||||||
return showMessage('music');
|
return showMessage('music');
|
||||||
// no new messages - go to clock?
|
// no new messages - go to clock?
|
||||||
if (options.clockIfAllRead && newMessages.length==0)
|
if (options.clockIfAllRead && newMessages.length==0)
|
||||||
|
@ -448,7 +444,9 @@ setTimeout(() => {
|
||||||
if (!isFinite(settings.unreadTimeout)) settings.unreadTimeout=60;
|
if (!isFinite(settings.unreadTimeout)) settings.unreadTimeout=60;
|
||||||
if (settings.unreadTimeout)
|
if (settings.unreadTimeout)
|
||||||
unreadTimeout = setTimeout(load, settings.unreadTimeout*1000);
|
unreadTimeout = setTimeout(load, settings.unreadTimeout*1000);
|
||||||
// only openMusic on launch if music is new
|
// only openMusic on launch if music is new, or state=="show" (set by messagesmusic)
|
||||||
var newMusic = MESSAGES.some(m => m.id === "music" && m.new);
|
var musicMsg = MESSAGES.find(m => m.id === "music");
|
||||||
checkMessages({ clockIfNoMsg: 0, clockIfAllRead: 0, showMsgIfUnread: 1, openMusic: newMusic && settings.openMusic });
|
checkMessages({
|
||||||
|
clockIfNoMsg: 0, clockIfAllRead: 0, showMsgIfUnread: 1,
|
||||||
|
openMusic: ((musicMsg&&musicMsg.new) && settings.openMusic) || (musicMsg&&musicMsg.state=="show") });
|
||||||
}, 10); // if checkMessages wants to 'load', do that
|
}, 10); // if checkMessages wants to 'load', do that
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
(function() {
|
Bangle.on("message", (type, msg) => require("messagegui").listener(type, msg));
|
||||||
Bangle.on("message", (type, msg) => require("messagegui").listener(type, msg));
|
|
||||||
})();
|
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
// Will calling Bangle.load reset everything? if false, we fast load
|
||||||
|
function loadWillReset() {
|
||||||
|
return Bangle.load === load || !Bangle.uiRemove;
|
||||||
|
/* FIXME: Maybe we need a better way of deciding if an app will
|
||||||
|
be fast loaded than just hard-coding a Bangle.uiRemove check.
|
||||||
|
Bangle.load could return a bool (as the load doesn't happen immediately). */
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener set up in boot.js, calls into here to keep boot.js short
|
* Listener set up in boot.js, calls into here to keep boot.js short
|
||||||
*/
|
*/
|
||||||
|
@ -10,46 +18,49 @@ exports.listener = function(type, msg) {
|
||||||
clearTimeout(exports.messageTimeout);
|
clearTimeout(exports.messageTimeout);
|
||||||
delete exports.messageTimeout;
|
delete exports.messageTimeout;
|
||||||
}
|
}
|
||||||
if (msg.t==="remove") return;
|
if (msg.t==="remove") {
|
||||||
|
// we won't open the UI for removed messages, so make sure to delete it from flash
|
||||||
|
if (Bangle.MESSAGES) {
|
||||||
|
// we were waiting for exports.messageTimeout
|
||||||
|
require("messages").apply(msg, Bangle.MESSAGES);
|
||||||
|
if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES;
|
||||||
|
}
|
||||||
|
return require("messages").save(msg); // always write removal to flash
|
||||||
|
}
|
||||||
|
|
||||||
const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {};
|
const appSettings = require("Storage").readJSON("messages.settings.json", 1) || {};
|
||||||
let loadMessages = (Bangle.CLOCK || event.important);
|
let loadMessages = (Bangle.CLOCK || event.important); // should we load the messages app?
|
||||||
if (type==="music") {
|
if (type==="music") {
|
||||||
if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true;
|
if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true;
|
||||||
else return;
|
else return;
|
||||||
}
|
}
|
||||||
if (Bangle.load === load || !Bangle.uiRemove) {
|
// Write the message to Bangle.MESSAGES. We'll deal with it in messageTimeout below
|
||||||
// no fast loading: store message to flash
|
if (!Bangle.MESSAGES) Bangle.MESSAGES = [];
|
||||||
/* FIXME: Maybe we need a better way of deciding if an app will
|
require("messages").apply(msg, Bangle.MESSAGES);
|
||||||
be fast loaded than just hard-coding a Bangle.uiRemove check.
|
if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES;
|
||||||
Bangle.load could return a bool (as the load doesn't happen immediately). */
|
|
||||||
require("messages").save(msg);
|
|
||||||
} else {
|
|
||||||
if (!Bangle.MESSAGES) Bangle.MESSAGES = [];
|
|
||||||
Bangle.MESSAGES.push(msg);
|
|
||||||
}
|
|
||||||
const saveToFlash = () => {
|
const saveToFlash = () => {
|
||||||
// save messages from RAM to flash after all, if we decide not to launch app
|
// save messages from RAM to flash if we decide not to launch app
|
||||||
if (!Bangle.MESSAGES) return;
|
// We apply all of Bangle.MESSAGES here in one write
|
||||||
Bangle.MESSAGES.forEach(m => require("messages").save(m));
|
if (!Bangle.MESSAGES || !Bangle.MESSAGES.length) return;
|
||||||
|
let messages = require("messages").getMessages(msg);
|
||||||
|
(Bangle.MESSAGES || []).forEach(m => require("messages").apply(m, messages));
|
||||||
|
require("messages").write(messages);
|
||||||
delete Bangle.MESSAGES;
|
delete Bangle.MESSAGES;
|
||||||
}
|
}
|
||||||
msg.handled = true;
|
msg.handled = true;
|
||||||
if ((msg.t!=="add" || !msg.new) && (type!=="music")) { // music always has t:"modify"
|
if ((msg.t!=="add" || !msg.new) && (type!=="music")) // music always has t:"modify"
|
||||||
saveToFlash();
|
return saveToFlash();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const quiet = (require("Storage").readJSON("setting.json", 1) || {}).quiet;
|
const quiet = (require("Storage").readJSON("setting.json", 1) || {}).quiet;
|
||||||
const unlockWatch = appSettings.unlockWatch;
|
const unlockWatch = appSettings.unlockWatch;
|
||||||
// don't auto-open messages in quiet mode if quietNoAutOpn is true
|
// don't auto-open messages in quiet mode if quietNoAutOpn is true
|
||||||
if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn)
|
if ((quiet && appSettings.quietNoAutOpn) || appSettings.noAutOpn)
|
||||||
loadMessages = false;
|
loadMessages = false;
|
||||||
|
|
||||||
// after a delay load the app, to ensure we have all the messages
|
// after a delay load the app, to ensure we have all the messages
|
||||||
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
if (exports.messageTimeout) clearTimeout(exports.messageTimeout);
|
||||||
exports.messageTimeout = setTimeout(function() {
|
exports.messageTimeout = setTimeout(function() {
|
||||||
delete exports.messageTimeout;
|
delete exports.messageTimeout;
|
||||||
|
if (!Bangle.MESSAGES) return; // message was removed during the delay
|
||||||
if (type!=="music") {
|
if (type!=="music") {
|
||||||
if (!loadMessages) {
|
if (!loadMessages) {
|
||||||
// not opening the app, just buzz
|
// not opening the app, just buzz
|
||||||
|
@ -61,6 +72,8 @@ exports.listener = function(type, msg) {
|
||||||
Bangle.setLCDPower(1); // turn screen on
|
Bangle.setLCDPower(1); // turn screen on
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if loading the gui would reload everything, we must save our messages
|
||||||
|
if (loadWillReset()) saveToFlash();
|
||||||
exports.open(msg);
|
exports.open(msg);
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
@ -70,11 +83,17 @@ exports.listener = function(type, msg) {
|
||||||
* @param {object} msg
|
* @param {object} msg
|
||||||
*/
|
*/
|
||||||
exports.open = function(msg) {
|
exports.open = function(msg) {
|
||||||
if (msg && msg.id && !msg.show) {
|
if (msg && msg.id) {
|
||||||
msg.show = 1;
|
// force a display by setting it as new and ensuring it ends up at the beginning of messages list
|
||||||
if (Bangle.load === load) {
|
msg.new = 1;
|
||||||
// no fast loading: store message to load in flash
|
if (loadWillReset()) {
|
||||||
|
// no fast loading: store message to load in flash - `msg` will be put in first
|
||||||
require("messages").save(msg, {force: 1});
|
require("messages").save(msg, {force: 1});
|
||||||
|
} else {
|
||||||
|
// fast load - putting it at the end of Bangle.MESSAGES ensures it goes at the start of messages list
|
||||||
|
if (!Bangle.MESSAGES) Bangle.MESSAGES=[];
|
||||||
|
Bangle.MESSAGES = Bangle.MESSAGES.filter(m => m.id!=msg.id)
|
||||||
|
Bangle.MESSAGES.push(msg); // putting at the
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "messagegui",
|
"id": "messagegui",
|
||||||
"name": "Message UI",
|
"name": "Message UI",
|
||||||
"shortName": "Messages",
|
"shortName": "Messages",
|
||||||
"version": "0.59",
|
"version": "0.63",
|
||||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
0.01: Moved message icons from messages into standalone library
|
0.01: Moved message icons from messages into standalone library
|
||||||
0.02: Added several new icons and colors
|
0.02: Added several new icons and colors
|
||||||
|
0.03: Fix icons broken in 0v02 (#2386)
|
||||||
|
Store all icons in a separate binary file (much faster lookup)
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 204 B |
After Width: | Height: | Size: 210 B |
After Width: | Height: | Size: 207 B |
After Width: | Height: | Size: 182 B |
|
@ -0,0 +1,143 @@
|
||||||
|
#!/usr/bin/node
|
||||||
|
|
||||||
|
// Creates lib.js from icons
|
||||||
|
// npm install png-js
|
||||||
|
|
||||||
|
// default icon must come first in icon_names
|
||||||
|
|
||||||
|
var imageconverter = require("../../../webtools/imageconverter.js");
|
||||||
|
var icons = JSON.parse(require("fs").readFileSync(__dirname+"/icon_names.json"));
|
||||||
|
const imgOptions = {
|
||||||
|
mode : "1bit",
|
||||||
|
inverted : true,
|
||||||
|
transparent : true,
|
||||||
|
output: "raw"
|
||||||
|
};
|
||||||
|
var PNG = require('png-js');
|
||||||
|
var IMAGE_BYTES = 76;
|
||||||
|
|
||||||
|
var iconTests = [];
|
||||||
|
var iconImages = []; // array of converted icons
|
||||||
|
var iconIndices = {}; // maps filename -> index in iconImages
|
||||||
|
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
|
icons.forEach(icon => {
|
||||||
|
var index = iconIndices[icon.icon];
|
||||||
|
if (index===undefined) { // need a new icon
|
||||||
|
index = iconImages.length;
|
||||||
|
iconIndices[icon.icon] = index;
|
||||||
|
iconImages.push(""); // placeholder
|
||||||
|
// create image
|
||||||
|
console.log("Loading "+icon.icon);
|
||||||
|
var png = new PNG(require("fs").readFileSync(__dirname+"/"+icon.icon));
|
||||||
|
if (png.width!=24 || png.height!=24) {
|
||||||
|
console.warn(icon.icon+" should be 24x24px");
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(new Promise(r => {
|
||||||
|
png.decode(function (pixels) {
|
||||||
|
var rgba = new Uint8Array(pixels);
|
||||||
|
var isTransparent = false;
|
||||||
|
for (var i=0;i<rgba.length;i+=4)
|
||||||
|
if (rgba[i+3]<255) isTransparent=true;
|
||||||
|
if (!isTransparent) { // make it transparent
|
||||||
|
for (var i=0;i<rgba.length;i+=4)
|
||||||
|
rgba[i+3] = 255-rgba[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
imgOptions.width = png.width;
|
||||||
|
imgOptions.height = png.height;
|
||||||
|
var img = imageconverter.RGBAtoString(rgba, imgOptions);
|
||||||
|
iconImages[index] = img;
|
||||||
|
console.log("Loaded "+icon.icon);
|
||||||
|
if (img.length != IMAGE_BYTES) throw new Error("Image size should be 76 bytes");
|
||||||
|
r(); // done
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
//
|
||||||
|
}
|
||||||
|
icon.index = index;
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all(promises).then(function() {
|
||||||
|
// Allocate a big array of icons
|
||||||
|
var iconData = new Uint8Array(IMAGE_BYTES * iconImages.length);
|
||||||
|
iconImages.forEach((img,idx) => {
|
||||||
|
// Yay, more JS. Why is it so hard to get the bytes???
|
||||||
|
iconData.set(Array.prototype.slice.call(Buffer.from(img,"binary")), idx*IMAGE_BYTES)
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Saving images");
|
||||||
|
require("fs").writeFileSync(__dirname+"/../icons.img", Buffer.from(iconData,"binary"));
|
||||||
|
|
||||||
|
console.log("Saving library");
|
||||||
|
require("fs").writeFileSync(__dirname+"/../lib.js", `exports.getImage = function(msg) {
|
||||||
|
if (msg.img) return atob(msg.img);
|
||||||
|
let s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase();
|
||||||
|
if (msg.id=="music") s="music";
|
||||||
|
let match = ${JSON.stringify(","+icons.map(icon=>icon.app+"|"+icon.index).join(",")+",")}.match(new RegExp(\`,\${s}\\\\|(\\\\d+)\`))
|
||||||
|
return require("Storage").read("messageicons.img", (match===null)?0:match[1]*${IMAGE_BYTES}, ${IMAGE_BYTES});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getColor = function(msg,options) {
|
||||||
|
options = options||{};
|
||||||
|
var st = options.settings || require('Storage').readJSON("messages.settings.json", 1) || {};
|
||||||
|
if (options.default===undefined) options.default=g.theme.fg;
|
||||||
|
if (st.iconColorMode == 'mono') return options.default;
|
||||||
|
const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase();
|
||||||
|
return {
|
||||||
|
// generic colors, using B2-safe colors
|
||||||
|
// DO NOT USE BLACK OR WHITE HERE, just leave the declaration out and then the theme's fg color will be used
|
||||||
|
"airbnb": "#ff385c", // https://news.airbnb.com/media-assets/category/brand/
|
||||||
|
"mail": "#ff0",
|
||||||
|
"music": "#f0f",
|
||||||
|
"phone": "#0f0",
|
||||||
|
"sms message": "#0ff",
|
||||||
|
// brands, according to https://www.schemecolor.com/?s (picking one for multicolored logos)
|
||||||
|
// all dithered on B2, but we only use the color for the icons. (Could maybe pick the closest 3-bit color for B2?)
|
||||||
|
"bibel": "#54342c",
|
||||||
|
"bring": "#455a64",
|
||||||
|
"discord": "#5865f2", // https://discord.com/branding
|
||||||
|
"etar": "#36a18b",
|
||||||
|
"facebook": "#1877f2", // https://www.facebook.com/brand/resources/facebookapp/logo
|
||||||
|
"gmail": "#ea4335",
|
||||||
|
"gmx": "#1c449b",
|
||||||
|
"google": "#4285F4",
|
||||||
|
"google home": "#fbbc05",
|
||||||
|
// "home assistant": "#41bdf5", // ha-blue is #41bdf5, but that's the background
|
||||||
|
"instagram": "#ff0069", // https://about.instagram.com/brand/gradient
|
||||||
|
"lieferando": "#ff8000",
|
||||||
|
"linkedin": "#0a66c2", // https://brand.linkedin.com/
|
||||||
|
"messenger": "#0078ff",
|
||||||
|
"mastodon": "#563acc", // https://www.joinmastodon.org/branding
|
||||||
|
"mattermost": "#00f",
|
||||||
|
"n26": "#36a18b",
|
||||||
|
"nextbike": "#00f",
|
||||||
|
"newpipe": "#f00",
|
||||||
|
"nina": "#e57004",
|
||||||
|
"opentasks": "#409f8f",
|
||||||
|
"outlook mail": "#0078d4", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products
|
||||||
|
"paypal": "#003087",
|
||||||
|
"pocket": "#ef4154f", // https://blog.getpocket.com/press/
|
||||||
|
"post & dhl": "#f2c101",
|
||||||
|
"reddit": "#ff4500", // https://www.redditinc.com/brand
|
||||||
|
"signal": "#3a76f0", // https://github.com/signalapp/Signal-Desktop/blob/main/images/signal-logo.svg
|
||||||
|
"skype": "#0078d4", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products
|
||||||
|
"slack": "#e51670",
|
||||||
|
"snapchat": "#ff0",
|
||||||
|
"steam": "#171a21",
|
||||||
|
"teams": "#6264a7", // https://developer.microsoft.com/en-us/fluentui#/styles/web/colors/products
|
||||||
|
"telegram": "#0088cc",
|
||||||
|
"telegram foss": "#0088cc",
|
||||||
|
"to do": "#3999e5",
|
||||||
|
"twitch": "#9146ff", // https://brand.twitch.tv/
|
||||||
|
"twitter": "#1d9bf0", // https://about.twitter.com/en/who-we-are/brand-toolkit
|
||||||
|
"vlc": "#ff8800",
|
||||||
|
"whatsapp": "#4fce5d",
|
||||||
|
"wordfeud": "#e7d3c7",
|
||||||
|
"youtube": "#f00", // https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors
|
||||||
|
}[s]||options.default;
|
||||||
|
};
|
||||||
|
`);
|
||||||
|
});
|
After Width: | Height: | Size: 227 B |
After Width: | Height: | Size: 269 B |
|
@ -0,0 +1,111 @@
|
||||||
|
[
|
||||||
|
{ "app":"default", "icon":"default.png" },
|
||||||
|
{ "app":"airbnb", "icon":"airbnb.png" },
|
||||||
|
{ "app":"alarm", "icon":"alarm.png" },
|
||||||
|
{ "app":"alarmclockreceiver", "icon":"alarm.png" },
|
||||||
|
{ "app":"amazon shopping", "icon":"amazon.png" },
|
||||||
|
{ "app":"bibel", "icon":"bibel.png" },
|
||||||
|
{ "app":"bitwarden", "icon":"security.png" },
|
||||||
|
{ "app":"1password", "icon":"security.png" },
|
||||||
|
{ "app":"lastpass", "icon":"security.png" },
|
||||||
|
{ "app":"dashlane", "icon":"security.png" },
|
||||||
|
{ "app":"bring", "icon":"bring.png" },
|
||||||
|
{ "app":"calendar", "icon":"etar.png" },
|
||||||
|
{ "app":"etar", "icon":"etar.png" },
|
||||||
|
{ "app":"chat", "icon":"google chat.png" },
|
||||||
|
{ "app":"chrome", "icon":"chrome.png" },
|
||||||
|
{ "app":"corona-warn", "icon":"coronavirus.png" },
|
||||||
|
{ "app":"bmo", "icon":"bank.png" },
|
||||||
|
{ "app":"desjardins", "icon":"bank.png" },
|
||||||
|
{ "app":"rbc mobile", "icon":"bank.png" },
|
||||||
|
{ "app":"nbc", "icon":"bank.png" },
|
||||||
|
{ "app":"rabobank", "icon":"bank.png" },
|
||||||
|
{ "app":"scotiabank", "icon":"bank.png" },
|
||||||
|
{ "app":"td (canada)", "icon":"bank.png" },
|
||||||
|
{ "app":"discord", "icon":"discord.png" },
|
||||||
|
{ "app":"drive", "icon":"google drive.png" },
|
||||||
|
{ "app":"element", "icon":"matrix element.png" },
|
||||||
|
{ "app":"facebook", "icon":"facebook.png" },
|
||||||
|
{ "app":"messenger", "icon":"facebook messenger.png" },
|
||||||
|
{ "app":"firefox", "icon":"firefox.png" },
|
||||||
|
{ "app":"firefox beta", "icon":"firefox.png" },
|
||||||
|
{ "app":"firefox nightly", "icon":"firefox.png" },
|
||||||
|
{ "app":"f-droid", "icon":"security.png" },
|
||||||
|
{ "app":"neo store", "icon":"security.png" },
|
||||||
|
{ "app":"aurora droid", "icon":"security.png" },
|
||||||
|
{ "app":"github", "icon":"github.png" },
|
||||||
|
{ "app":"gitlab", "icon":"gitlab.png" },
|
||||||
|
{ "app":"gmx", "icon":"gmx.png" },
|
||||||
|
{ "app":"google", "icon":"google.png" },
|
||||||
|
{ "app":"google home", "icon":"google home.png" },
|
||||||
|
{ "app":"google play store", "icon":"google play store.png" },
|
||||||
|
{ "app":"home assistant", "icon":"home assistant.png" },
|
||||||
|
{ "app":"instagram", "icon":"instagram.png" },
|
||||||
|
{ "app":"kalender", "icon":"kalender.png" },
|
||||||
|
{ "app":"keep notes", "icon":"google keep.png" },
|
||||||
|
{ "app":"lieferando", "icon":"lieferando.png" },
|
||||||
|
{ "app":"linkedin", "icon":"linkedin.png" },
|
||||||
|
{ "app":"maps", "icon":"map.png" },
|
||||||
|
{ "app":"organic maps", "icon":"map.png" },
|
||||||
|
{ "app":"osmand", "icon":"map.png" },
|
||||||
|
{ "app":"mastodon", "icon":"mastodon.png" },
|
||||||
|
{ "app":"fedilab", "icon":"mastodon.png" },
|
||||||
|
{ "app":"tooot", "icon":"mastodon.png" },
|
||||||
|
{ "app":"tusky", "icon":"mastodon.png" },
|
||||||
|
{ "app":"mattermost", "icon":"mattermost.png" },
|
||||||
|
{ "app":"n26", "icon":"n26.png" },
|
||||||
|
{ "app":"netflix", "icon":"netflix.png" },
|
||||||
|
{ "app":"news", "icon":"news.png" },
|
||||||
|
{ "app":"cbc news", "icon":"news.png" },
|
||||||
|
{ "app":"rc info", "icon":"news.png" },
|
||||||
|
{ "app":"reuters", "icon":"news.png" },
|
||||||
|
{ "app":"ap news", "icon":"news.png" },
|
||||||
|
{ "app":"la presse", "icon":"news.png" },
|
||||||
|
{ "app":"nbc news", "icon":"news.png" },
|
||||||
|
{ "app":"nextbike", "icon":"nextbike.png" },
|
||||||
|
{ "app":"nina", "icon":"nina.png" },
|
||||||
|
{ "app":"outlook mail", "icon":"outlook.png" },
|
||||||
|
{ "app":"paypal", "icon":"paypal.png" },
|
||||||
|
{ "app":"phone", "icon":"phone.png" },
|
||||||
|
{ "app":"plex", "icon":"plex.png" },
|
||||||
|
{ "app":"pocket", "icon":"pocket.png" },
|
||||||
|
{ "app":"post & dhl", "icon":"delivery.png" },
|
||||||
|
{ "app":"proton mail", "icon":"protonmail.png" },
|
||||||
|
{ "app":"reddit", "icon":"reddit.png" },
|
||||||
|
{ "app":"sync pro", "icon":"reddit.png" },
|
||||||
|
{ "app":"sync dev", "icon":"reddit.png" },
|
||||||
|
{ "app":"boost", "icon":"reddit.png" },
|
||||||
|
{ "app":"infinity", "icon":"reddit.png" },
|
||||||
|
{ "app":"slide", "icon":"reddit.png" },
|
||||||
|
{ "app":"signal", "icon":"signal.png" },
|
||||||
|
{ "app":"skype", "icon":"skype.png" },
|
||||||
|
{ "app":"slack", "icon":"slack.png" },
|
||||||
|
{ "app":"snapchat", "icon":"snapchat.png" },
|
||||||
|
{ "app":"starbucks", "icon":"cafe.png" },
|
||||||
|
{ "app":"steam", "icon":"steam.png" },
|
||||||
|
{ "app":"teams", "icon":"teams.png" },
|
||||||
|
{ "app":"telegram", "icon":"telegram.png" },
|
||||||
|
{ "app":"telegram foss", "icon":"telegram.png" },
|
||||||
|
{ "app":"threema", "icon":"threema.png" },
|
||||||
|
{ "app":"tiktok", "icon":"tiktok.png" },
|
||||||
|
{ "app":"to do", "icon":"task.png" },
|
||||||
|
{ "app":"opentasks", "icon":"task.png" },
|
||||||
|
{ "app":"tasks", "icon":"task.png" },
|
||||||
|
{ "app":"transit", "icon":"transit.png" },
|
||||||
|
{ "app":"twitch", "icon":"twitch.png" },
|
||||||
|
{ "app":"twitter", "icon":"twitter.png" },
|
||||||
|
{ "app":"uber", "icon":"taxi.png" },
|
||||||
|
{ "app":"lyft", "icon":"taxi.png" },
|
||||||
|
{ "app":"vlc", "icon":"vlc.png" },
|
||||||
|
{ "app":"warnapp", "icon":"warnapp.png" },
|
||||||
|
{ "app":"whatsapp", "icon":"whatsapp.png" },
|
||||||
|
{ "app":"wordfeud", "icon":"wordfeud.png" },
|
||||||
|
{ "app":"youtube", "icon":"youtube.png" },
|
||||||
|
{ "app":"newpipe", "icon":"youtube.png" },
|
||||||
|
{ "app":"zoom", "icon":"videoconf.png" },
|
||||||
|
{ "app":"meet", "icon":"videoconf.png" },
|
||||||
|
{ "app":"music", "icon":"music.png" },
|
||||||
|
{ "app":"sms message", "icon":"default.png" },
|
||||||
|
{ "app":"mail", "icon":"default.png" },
|
||||||
|
{ "app":"gmail", "icon":"default.png" }
|
||||||
|
]
|
After Width: | Height: | Size: 249 B |
After Width: | Height: | Size: 209 B |
After Width: | Height: | Size: 188 B |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 261 B |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 213 B |
|
@ -1,80 +1,9 @@
|
||||||
exports.getImage = function(msg) {
|
exports.getImage = function(msg) {
|
||||||
/*
|
|
||||||
* icons should be 24x24px or less with 1bpp colors and 'Transparency to Color'
|
|
||||||
* http://www.espruino.com/Image+Converter
|
|
||||||
*/
|
|
||||||
if (msg.img) return atob(msg.img);
|
if (msg.img) return atob(msg.img);
|
||||||
const s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase();
|
let s = (("string"=== typeof msg) ? msg : (msg.src || "")).toLowerCase();
|
||||||
if (s=="airbnb") return atob("GBgBAAAAADwAAH4AAMMAAIMAAYGAAQGAAwDAAwDABjxgBn5gDMMwDMMwGMMYGMMYMGYMMGYMIDwEIBgEIDwEMH4MHee4D4HwAAAA"); // icons/airbnb.png
|
if (msg.id=="music") s="music";
|
||||||
if (s=="alarm" || s =="alarmclockreceiver") return atob("GBgBAAAAAAAAAgBABwDgHn54Of+cE8PIBwDgDhhwDBgwHBg4GBgYGBgYGBgYGA4YHAc4DAEwDgBwBwDgA8PAAf+AAH4AAAAAAAAA"); // icons/alarm.png
|
let match = ",default|0,airbnb|1,alarm|2,alarmclockreceiver|2,amazon shopping|3,bibel|4,bitwarden|5,1password|5,lastpass|5,dashlane|5,bring|6,calendar|7,etar|7,chat|8,chrome|9,corona-warn|10,bmo|11,desjardins|11,rbc mobile|11,nbc|11,rabobank|11,scotiabank|11,td (canada)|11,discord|12,drive|13,element|14,facebook|15,messenger|16,firefox|17,firefox beta|17,firefox nightly|17,f-droid|5,neo store|5,aurora droid|5,github|18,gitlab|19,gmx|20,google|21,google home|22,google play store|23,home assistant|24,instagram|25,kalender|26,keep notes|27,lieferando|28,linkedin|29,maps|30,organic maps|30,osmand|30,mastodon|31,fedilab|31,tooot|31,tusky|31,mattermost|32,n26|33,netflix|34,news|35,cbc news|35,rc info|35,reuters|35,ap news|35,la presse|35,nbc news|35,nextbike|36,nina|37,outlook mail|38,paypal|39,phone|40,plex|41,pocket|42,post & dhl|43,proton mail|44,reddit|45,sync pro|45,sync dev|45,boost|45,infinity|45,slide|45,signal|46,skype|47,slack|48,snapchat|49,starbucks|50,steam|51,teams|52,telegram|53,telegram foss|53,threema|54,tiktok|55,to do|56,opentasks|56,tasks|56,transit|57,twitch|58,twitter|59,uber|60,lyft|60,vlc|61,warnapp|62,whatsapp|63,wordfeud|64,youtube|65,newpipe|65,zoom|66,meet|66,music|67,sms message|0,mail|0,gmail|0,".match(new RegExp(`,${s}\\|(\\d+)`))
|
||||||
if (s=="amazon shopping") return atob("GBgBAAAAAP8AAf+AA//AA+fAA8PAAIPAAD/AAP/AA//AA+PAB8PAB8fAB8fgB//gA//gA/3AAPCecAAeOAAeDwH0B//kAf+AAAAA"); // icons/amazon.png
|
return require("Storage").read("messageicons.img", (match===null)?0:match[1]*76, 76);
|
||||||
if (s=="bibel") return atob("GBgBAAAAA//wD//4D//4H//4H/f4H/f4H+P4H4D4H4D4H/f4H/f4H/f4H/f4H/f4H//4H//4H//4GAAAEAAAEAAACAAAB//4AAAA");
|
|
||||||
if (s=="bitwarden" || s=="1password" || s=="lastpass" || s=="dashlane") return atob("GBgBAAAAABgAAP8AA//AD4/wHg/4GA/4GA/4GA/4GA/4GA/4GA/4H/AYH/AYH/A4D/AwD/BwB/BgB/DgA/HAAfeAAP8AADwAAAAA"); // icons/security.png
|
|
||||||
if (s=="bring") return atob("GBgBAAAAAAAAAAAAAAAAAHwAAFoAAf+AA/+AA/+AA/+AA/eAA+eAA0+AAx+AA7+AA/+AA//AA/+AAf8AAAIAAAAAAAAAAAAAAAAA");
|
|
||||||
if (s=="calendar" || s=="etar") return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA==");
|
|
||||||
if (s=="chat") return atob("GBgBAAAAAf/8A//+A//+A//+OAB+e/8+e/++e/++e/++e/++e/++e/++ef+8fAAAf//Af//Af//Af//Af/+AcAAAYAAAQAAAAAAA"); // icons/google chat.png
|
|
||||||
if (s=="chrome") return atob("GBgBAAAAAAAAAP8AA//AB+fgDwDwHgB4HAA4Pj/8OmYcO8McMYEMMYEMOMMcOGccOD4cHAw4Hgx4DxjwB//gA//AAP8AAAAAAAAA"); // icons/chrome.png
|
|
||||||
if (s=="corona-warn") return atob("GBgBAAAAAAAAABgAABgABhhgDn5wD//wA8PAA+fAB2bgBgBgPpl8Ppl8BgBgB2bgA+fAA8PAD//wDn5wBhhgABgAABgAAAAAAAAA"); // icons/coronavirus.png
|
|
||||||
if (s=="bmo" || s=="desjardins" || s=="rbc mobile" || s=="nbc" || s=="rabobank" || s=="scotiabank" || s=="td (canada)") return atob("GBgBAAAAADgAAP4AAe8AB4PAHgDwP//4P//4AAAAAAAADjjgDjjgDjjgDjjgDjjgDjjgDjjgAAAAAAAAP//4P//4AAAAAAAAAAAA"); // icons/bank.png
|
|
||||||
if (s=="discord") return atob("GBgBAAAAAAAAAAAAAAAAA4HAD//wH//4H//4P//8P//8P//8fn5+fDw+fDw+fn5+f//+f//+ff++PgB8DgBwAAAAAAAAAAAAAAAA"); // icons/discord.png
|
|
||||||
if (s=="drive") return atob("GBgBAAAAAAAAAH8AAH8AAT+AA7/AA9/AB8/gB+/gD+fwD+fwH8P4P8P8P4H8fwAAf3/+Pn/8Pv/8HP/4Df/wC//wAAAAAAAAAAAA"); // icons/google drive.png
|
|
||||||
if (s=="element") return atob("GBgBAAAAAHwAAH4AAH8AAAeAAePAB+HAD+DgHgDgPADuOADucAAOcAAOdwAcdwA8BwB4BwfwA4fgA8eAAeAAAP4AAH4AAD4AAAAA"); // icons/matrix element.png
|
|
||||||
if (s=="facebook") return atob("GBgBAAAAAAAAAH4AAf+AB//gD//wD/DwH+D4H+P4P+f8P+f8P+f8PwD8PwD8PwD8H+f4H+f4D+fwD+fwB+fgAeeAAOcAAAAAAAAA"); // icons/facebook.png
|
|
||||||
if (s=="messenger") return atob("GBgBAAAAAAAAAP8AA//AB//gD//wH//4H//4P//8P9+8P458PwB8PgD8PnH8Pfv8H//4H//4D//wB//gB//AB/8AAwAAAAAAAAAA"); // icons/facebook messenger.png
|
|
||||||
if (s=="firefox" || s=="firefox beta" || s=="firefox nightly") return atob("GBgBAAAAAAAAAAMAAAcAAAeABA/ADY/gH4P4H4H4H8H8P/H8P+D8PwD8PwD8PwD8H4H4H8P4H//4D//wB//gA//AAP8AAAAAAAAA"); // icons/firefox.png
|
|
||||||
if (s=="f-droid" || s=="neo store" || s=="aurora droid") return atob("GBgBAAAAQAACYAAGP//8H//4H//4HH44HH44H//4AAAAH//4H8P4H734H374HsN4Hvl4Hv14Hvl4HsN4H374H734H8P4D//wAAAA"); // icons/security.png
|
|
||||||
if (s=="github") return atob("GBgBAAAAAAAAAH4AAf+AB//gD//wDv9wHgB4HgB4PAA8PAA8PAA8PAA8PAA8PgB8HwD4G8P4DcPwDgPwB4PgAcOAAAAAAAAAAAAA"); // icons/github.png
|
|
||||||
if (s=="gitlab") return atob("GBgBAAAABAAgDAAwDAAwHgB4HgB4PgB8PwD8P//8f//+f//+f//+f//+f//+f//+P//8H//4D//wA//AAf+AAP8AADwAABgAAAAA"); // icons/gitlab.png
|
|
||||||
if (s=="gmx") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEJmfmd8Zuc85v847/88Z9s8fttmHIHiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
|
||||||
if (s=="google") return atob("GBgBAAAAAP8AA//AB//gD//gH+fAP4CAPwAAPgAAfAAAfA/+fA/+fA/+fA/+fAA+PgA+PwB8P4D8H+f4D//4B//wA//AAP8AAAAA"); // icons/google.png
|
|
||||||
if (s=="google home") return atob("GBgBAAAAABgAADwAAH4AAf4AA/zAB/vgD/fwH+f4P4H8fwD+fgB+fAA+eAA+cAA+bAA+HAA+PAA+ff++ff++ff++ff++Pf+8AAAA"); // icons/google home.png
|
|
||||||
if (s=="google play store") return atob("GBgBAAAAAAAAAH4AAP8AAMMAAMMAP//8P//8MAAMMAAMMGAMMHgMMH4MMH8MMH4MMHgMMGAMMAAMMAAMP//8H//4AAAAAAAAAAAA"); // icons/google play store.png
|
|
||||||
if (s=="home assistant") return atob("FhaBAAAAAADAAAeAAD8AAf4AD/3AfP8D7fwft/D/P8ec572zbzbNsOEhw+AfD8D8P4fw/z/D/P8P8/w/z/AAAAA=");
|
|
||||||
if (s=="instagram") return atob("GBgBAAAAD//wH//4OAAccAAOYABmYDxmYP8GYeeGYYGGY4HGYwDGYwDGY4HGYYGGYeeGYP8GYDwGYAAGcAAOOAAcH//4D//wAAAA"); // icons/instagram.png
|
|
||||||
if (s=="kalender") return atob("GBgBBgBgBQCgff++RQCiRgBiQAACf//+QAACQAACR//iRJkiRIEiR//iRNsiRIEiRJkiR//iRIEiRIEiR//iQAACQAACf//+AAAA");
|
|
||||||
if (s=="keep notes") return atob("GBgBAAAAAAAAH//4P//8P8P8Pzz8P378Pv98Pv98Pv98Pv98P378Pzz8P738P4H8P738P738P4GMP8OYP/+wP//gH//AAAAAAAAA"); // icons/google keep.png
|
|
||||||
if (s=="lieferando") return atob("GBgBAAAAADwAAH4AAP/gAf/wA//wB//wD//wH//4H/98Pt58ft5+Ptx8DtxwDtxwDhxwDhhwDhhwDzhwD75wD75wD75wB77gAAAA"); // icons/lieferando.png
|
|
||||||
if (s=="linkedin") return atob("GBgBAAAAf//+f//+f//+ef/+cf/+cf/+f//+f//+ccw+ccAeccAecccOcceOcceOcceOcceOcceOcceOec+ef//+f//+f//+AAAA"); // icons/linkedin.png
|
|
||||||
if (s=="maps" || s=="organic maps" || s=="osmand") return atob("GBgBAAAAAAAAAAAAAeAYD/z4H//4GMeYGMMYGMMYGMMYGMMYGMMYGMMYGMMYGMMYGMMYGMMYGeMYH//4Hz/wGAeAAAAAAAAAAAAA"); // icons/map.png
|
|
||||||
if (s=="mastodon" || s=="fedilab" || s=="tooot" || s=="tusky") return atob("GBgBAAAAB//gD//4H//4P//8PBg8PAA8fOMeeOeeeOeeOOeeOOecOP+cOP+cP//8P//4P//4P//gHwAAH4AAD+cAB/8AAf4AAAAA"); // icons/mastodon.png
|
|
||||||
if (s=="mattermost") return atob("GBgBAAAAAPAAA+EAB4GADgOQHAeYOA+cOB+MeB+OcD+GcD+GcD+GeD8OeB4OeAAOfAAePgA8P4B8H/f4D//wB//gA//AAP8AAAAA"); // icons/mattermost.png
|
|
||||||
if (s=="n26") return atob("GBgBAAAAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAOIAAOIAAPIAANoAANoAAM4AAMYAAMYAAAAAAAAAAAAAAP8AAAAAAAAAAAAAAAAA");
|
|
||||||
if (s=="netflix") return atob("GBgBAAAAA8PAA+PAAePAAePAAfPAAvPAA/PAAvvAAn/AA/nAA3/AA/7AA5/AA/5AA99AA8/AA89AA8+AA8eAA8eAA8fAA8PAAAAA"); // icons/netflix.png
|
|
||||||
if (s=="news" || s=="cbc news" || s=="rc info" || s=="reuters" || s=="ap news" || s=="la presse" || s=="nbc news") return atob("GBgBAAAAAAAAAAAALaW0P//8P//8P//8P//8MAAMMAAMMAAMP//8P//8MBwcMBwcMB/8MB/8MBwcMBwcP//8P//8AAAAAAAAAAAA"); // icons/news.png
|
|
||||||
if (s=="nextbike") return atob("GBgBAAAAAAAAAAAAAAAAAAAAAACAfgDAPwDAP4HAH4N4H8f8D82GMd8CMDsDMGMDMGGGGMHOD4D8AAAAAAAAAAAAAAAAAAAAAAAA");
|
|
||||||
if (s=="nina") return atob("GBgBAAAABAAQCAAICAAIEAAEEgAkJAgSJBwSKRxKSj4pUn8lVP+VVP+VUgAlSgApKQBKJAASJAASEgAkEAAECAAICAAIBAAQAAAA");
|
|
||||||
if (s=="outlook mail") return atob("GBgBAAAAAAAAAP/8AP/8AP/8AJjMf/jMf//8f//8cHjMd3jMZz/+Zz/+d3jecHj+f//mf/eGf/PGAwDmAwA+A//+Af/+AAAAAAAA"); // icons/outlook.png
|
|
||||||
if (s=="paypal") return atob("GBgBAAAAA/+AA//gA//wB//wB//wB//wB//wB//wB//gD//gD//ID/+ID/wwD4BwD5/gD74AH7gAHzAAHzAAHzAAAHAAAHAAAAAA"); // icons/paypal.png
|
|
||||||
if (s=="phone") return atob("GBgBAAAAAAAAH4AAP8AAP8AAP8AAH8AAH8AAH8AAH4AADwAADwAABwAAA4AAA8HwAeP8AP/8AH/8AD/8AA/8AAP8AAB4AAAAAAAA"); // icons/phone.png
|
|
||||||
if (s=="plex") return atob("GBgBAAAAB/gAB/gAA/wAAf4AAf4AAP8AAH+AAH+AAD/AAB/gAB/gAB/gAB/gAD/AAH+AAH+AAP8AAf4AAf4AA/wAB/gAB/gAAAAA"); // icons/plex.png
|
|
||||||
if (s=="pocket") return atob("GBgBAAAAAAAAP//8f//+f//+f//+f//+f//+fP8+eH4efDw+fhh+fwD+f4H+P8P8P+f8H//4H//4D//wB//gAf+AAH4AAAAAAAAA"); // icons/pocket.png
|
|
||||||
if (s=="post & dhl") return atob("GBgBAAAAAAAAAAAAAAAAP/+Af/+AYAGAYAGAYAHwYAH4YAGMYAGGYAH+YAH+bwH+f//+ef+eGYGYH4H4DwDwAAAAAAAAAAAAAAAA"); // icons/delivery.png
|
|
||||||
if (s=="proton mail") return atob("GBgBAAAAAAAAAAAAQAACYAAGcAAOeAAePABeXgDebwHed4Pee/fefe/efh/ef//ef//ef//ef//ef//ef//eP//cAAAAAAAAAAAA"); // icons/protonmail.png
|
|
||||||
if (s=="reddit" || s=="sync pro" || s=="sync dev" || s=="boost" || s=="infinity" || s=="slide") return atob("GBgBAAAAAAAAAAYwAAX4AAh4AAgwAAgAAAgAAH4AAf+AN//sf//+fn5+PDw8HDw4Hn54H//4H//4DzzwB4HgAf+AAH4AAAAAAAAA"); // icons/reddit.png
|
|
||||||
if (s=="signal") return atob("GBgBAAAAAL0AAYGABH4gCf+QE//IB//gL//0b//2H//4X//6X//6X//6X//6H//4b//2L//0D//gL//ID/+QYH4gVYGAcL0AAAAA"); // icons/signal.png
|
|
||||||
if (s=="skype") return atob("GBgBAAAAB8AAH/8AP//AP//gf8fwfwD4fgB4fjx8fj/8Pg/8PwH8P4B8P/h8Pnx+Pjx+Hhh+HwD+D8P+B//8A//8AP/4AAPgAAAA"); // icons/skype.png
|
|
||||||
if (s=="slack") return atob("GBgBAAAAAOcAAeeAAeeAAeeAAGeAAAeAP+ecf+eef+e+f+e+AAAAAAAAfef+fef+eef+Oef8AeAAAeYAAeeAAeeAAeeAAOcAAAAA"); // icons/slack.png
|
|
||||||
if (s=="snapchat") return atob("GBgBAAAAAAAAAAAAAH4AAf+AAYGAAwDAAwDAAwDADwDwDwDwDgBwBwDgBwDgDgBwHAA4OAAcHAA4D4HwB//gAH4AAAAAAAAAAAAA"); // icons/snapchat.png
|
|
||||||
if (s=="starbucks") return atob("GBgBAAAAAAAAAAAAD//4D//8DADMDADMDADMDAD8DAD4DADADADADADADADADgHAB/+AA/8AAAAAAAAAP//wP//wAAAAAAAAAAAA"); // icons/cafe.png
|
|
||||||
if (s=="steam") return atob("GBgBAAAAAAAAAf+AA//AD//wD//wH/g4P/OcP/RcP+RcP+ReH8OeB4A+AAH+AMP8IC/8OS/8HN/4Dj/wD//wA//AAf+AAAAAAAAA"); // icons/steam.png
|
|
||||||
if (s=="teams") return atob("GBgBAAAAAAgAAD4AADcYAGM8AGNmP/dmP/48MDAYMD/+PP/+PPBmPPBmPPBmPPBmP/BmP/BmH+B+AYD4AMDAAOOAAH8AABwAAAAA"); // icons/teams.png
|
|
||||||
if (s=="telegram" || s=="telegram foss") return atob("GBgBAAAAAAAAAAAAAAAeAAB+AAP+AA/+AD/+Af9+B/z+H/n8f+P8f8f8Dw/8AB/8AB/8AB/4AAf4AAP4AAD4AABwAAAAAAAAAAAA"); // icons/telegram.png
|
|
||||||
if (s=="threema") return atob("GBgBAAAAAP8AA//AB//gD//wH8P4H9v4H734P5n8P4H8P4H8H4H4H4H4D//wD//gD//AH/8AHDwAAAAAAAAABhhgDzzwBhhgAAAA"); // icons/threema.png
|
|
||||||
if (s=="tiktok") return atob("GBgBAAAAAAAAAAcAAAcAAAeAAAfAAAfwAAf4AAf4AMd4A8cAB8cAD8cADwcAHgcAHgcAHg8ADw8AD/4AB/4AA/wAAfAAAAAAAAAA"); // icons/tiktok.png
|
|
||||||
if (s=="to do" || s=="opentasks" || s=="tasks") return atob("GBgBAAAAAHwAAf+AA//ID4GcHwA8HAB4PADwOAHgcAPGcAeOcY8Oc94OcfwOcPgOOHAcOCAcHAA4DgB4D4HwB//gAf+AAH4AAAAA"); // icons/task.png
|
|
||||||
if (s=="transit") return atob("GBgBAAAAD//wP//8P//8f//+f/j+ffA+eOA+eOMef+cefef+eOe+fecef+e+eOf+eOcefAcefA++fx/+f//+P//8P//8D//wAAAA"); // icons/transit.png
|
|
||||||
if (s=="twitch") return atob("GBgBAAAAA//8B//8DgAMHgAMPhjMPhjMPhjMPhjMPhjMPgAMPgAMPgAYPgAwP+fgP+/AP/+AP/8AP/4AAeAAAcAAAYAAAQAAAAAA"); // icons/twitch.png
|
|
||||||
if (s=="twitter") return atob("GBgBAAAAAAAAAAAAAAPAIAf8MA/4PA/8Pg/4H//4H//4P//4P//wH//wD//wD//gD//AA//AAf+AB/8AP/wAD/AAAAAAAAAAAAAA"); // icons/twitter.png
|
|
||||||
if (s=="uber" || s=="lyft") return atob("GBgBAAAAAAAAAAAAAH4AAH4AB//gB//gDgBwDAAwDAAwH//4H//4GAAYG4HYG4HYG4HYGAAYH//4H//4HAA4HAA4AAAAAAAAAAAA"); // icons/taxi.png
|
|
||||||
if (s=="vlc") return atob("GBgBAAAAABgAABgAADwAADwAAAAAAAAAAAAAAAAAAIEAAP8AAP8AAf+AAP8AAAAADAAwDAAwHAA4HwD4H//4P//8P//8P//8AAAA"); // icons/vlc.png
|
|
||||||
if (s=="warnapp") return atob("GBgBAAAAAAAAAAAAAH4AAP8AA//AA//AD//gP//gf//4f//+/+P+/8H//8n//4n/fxh/fzg+Pj88Dn44AA4AAAwAAAwAAAgAAAAA");
|
|
||||||
if (s=="whatsapp") return atob("GBgBAAAAAP8AA//AB4HwDgB4HAA4OAAcMYAMc8AOc8AGY8AGYcAGYeAGYPOGcH/OcD/OMA+MOAAcMAA4MgBwf8Pgf//AcP8AAAAA"); // icons/whatsapp.png
|
|
||||||
if (s=="wordfeud") return atob("GBgCWqqqqqqlf//////9v//////+v/////++v/////++v8///Lu+v8///L++v8///P/+v8v//P/+v9v//P/+v+fx/P/+v+Pk+P/+v/PN+f/+v/POuv/+v/Ofdv/+v/NvM//+v/I/Y//+v/k/k//+v/i/w//+v/7/6//+v//////+v//////+f//////9Wqqqqqql");
|
|
||||||
if (s=="youtube" || s=="newpipe") return atob("GBgBAAAAAAAAAAAAAAAAAAAAH//4P//8P//8f//+f8/+f8P+f8D+f8D+f8P+f8/+f//+P//8P//8H//4AAAAAAAAAAAAAAAAAAAA"); // icons/youtube.png
|
|
||||||
if (s=="zoom" || s=="meet") return atob("GBgBAAAAAAAAAAAAP/+Af//Af//AcADicADmcADucAD+cAD+cAD+cAD+cAD+cAD+cADucADmcADif//Af//AP/+AAAAAAAAAAAAA"); // icons/videoconf.png
|
|
||||||
if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
|
|
||||||
// if (s=="sms message" || s=="mail" || s=="gmail") // .. default icon (below)
|
|
||||||
return atob("FhKBAH//+P//yf/+c//z5/+fz/z/n+f/Pz/+ef/8D///////////////////////f//4///A");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getColor = function(msg,options) {
|
exports.getColor = function(msg,options) {
|
||||||
|
@ -136,3 +65,4 @@ exports.getColor = function(msg,options) {
|
||||||
"youtube": "#f00", // https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors
|
"youtube": "#f00", // https://www.youtube.com/howyoutubeworks/resources/brand-resources/#logos-icons-and-colors
|
||||||
}[s]||options.default;
|
}[s]||options.default;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "messageicons",
|
"id": "messageicons",
|
||||||
"name": "Message Icons",
|
"name": "Message Icons",
|
||||||
"version": "0.02",
|
"version": "0.03",
|
||||||
"description": "Library containing a list of icons and colors for apps",
|
"description": "Library containing a list of icons and colors for apps",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
"provides_modules" : ["messageicons"],
|
"provides_modules" : ["messageicons"],
|
||||||
"default": true,
|
"default": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"messageicons","url":"lib.js"}
|
{"name":"messageicons","url":"lib.js"},
|
||||||
|
{"name":"messageicons.img","url":"icons.img"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New app!
|
|
@ -0,0 +1,69 @@
|
||||||
|
# Message List
|
||||||
|
|
||||||
|
Display messages inline as a single list:
|
||||||
|
Displays one message at a time, if it doesn't fit on the screen you can scroll
|
||||||
|
up/down. When you reach the bottom, you can scroll on to the next message.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
**First** uninstall the default [Message UI](/?id=messagegui) app (`messagegui`,
|
||||||
|
not the library!).
|
||||||
|
Then install this app.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
### Main menu:
|
||||||
|

|
||||||
|
|
||||||
|
### Unread message:
|
||||||
|

|
||||||
|
The chevrons are hints for swipe actions:
|
||||||
|
- Swipe right to go back
|
||||||
|
- Swipe left for the message-actions menu
|
||||||
|
- Swipe down to show the previous message: We are currently viewing message 2 of 2,
|
||||||
|
so message 1 is "above" this one.
|
||||||
|
|
||||||
|
### Long (read) message:
|
||||||
|

|
||||||
|
The button is disabled until you scroll all the way to the bottom.
|
||||||
|
|
||||||
|
### Music:
|
||||||
|

|
||||||
|
Minimal setup: album name and buttons disabled through settings.
|
||||||
|
Swipe for next/previous song, tap to pause/resume.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
### Interface
|
||||||
|
* `Font size` - The font size used when displaying messages/music.
|
||||||
|
* `On Tap` - If messages are too large to fit on the screen, tapping the screen scrolls down.
|
||||||
|
This is the action to take when tapping a message after reaching the bottom:
|
||||||
|
- `Message menu`: Open menu with message actions
|
||||||
|
- `Dismiss`: Dismiss message right away
|
||||||
|
- `Back`: Go back to clock/main menu
|
||||||
|
- `Nothing`: Do nothing
|
||||||
|
* `Dismiss button` - Show inline button to dismiss message right away
|
||||||
|
|
||||||
|
### Behaviour
|
||||||
|
* `Vibrate` - The pattern of buzzes when a new message is received.
|
||||||
|
* `Vibrate for calls` - The pattern of buzzes for incoming calls.
|
||||||
|
* `Vibrate for alarms` - The pattern of buzzes for (phone) alarms.
|
||||||
|
* `Repeat` - How often buzzes repeat - the default of 4 means the Bangle will buzz every 4 seconds.
|
||||||
|
* `Unread timer` - When a new message is received the Messages app is opened.
|
||||||
|
If there is no user input for this amount of time then the app will exit and return to the clock.
|
||||||
|
* `Auto-open` - Automatically open app when a new message arrives.
|
||||||
|
* `Respect quiet mode` - Prevent auto-opening during quiet mode.
|
||||||
|
|
||||||
|
### Music
|
||||||
|
* `Auto-open` - Automatically open app when music starts playing.
|
||||||
|
* `Always visible` - Show "music" in the main menu even when nothing is playing.
|
||||||
|
* `Buttons` - Show `previous`/`play/pause`/`next` buttons on music screen.
|
||||||
|
* `Show album` - Display album names?
|
||||||
|
|
||||||
|
|
||||||
|
### Util
|
||||||
|
* `Delete all` - Erase all messages.
|
||||||
|
|
||||||
|
|
||||||
|
## Attributions
|
||||||
|
|
||||||
|
Some icons used in this app are from https://icons8.com
|
|
@ -0,0 +1,17 @@
|
||||||
|
## Nice to have:
|
||||||
|
* Add labels to B1 music HW buttons
|
||||||
|
* Add volume buttons to B2 music screen (when controls are enabled)
|
||||||
|
* Draw messages ourselves instead of piling hacks on Layout
|
||||||
|
* Make sure all icons are 24x24px: icon sizes affect layout
|
||||||
|
* Check/optimize layout for B1, other fonts (scrolling for just 5px is a shame)
|
||||||
|
|
||||||
|
## Wishlist:
|
||||||
|
* Option to swipe-dismiss (instead of action menu)
|
||||||
|
* Maybe refactor showGrid() out into a general-use module?
|
||||||
|
|
||||||
|
* Message replies (needs `android` support)
|
||||||
|
* Customize replies
|
||||||
|
* Custom replies (i.e. `textinput`)
|
||||||
|
* Hooks to add custom replies/actions,
|
||||||
|
e.g. external code could add "Send intent" option to Home Assistant messages
|
||||||
|
Maybe just use this for all replies, so we don't hardcode anything in "messages"?
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA="))
|
After Width: | Height: | Size: 8.8 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
(function() {
|
||||||
|
Bangle.on("message", require("messagegui").messageListener);
|
||||||
|
})();
|
|
@ -0,0 +1,246 @@
|
||||||
|
// Handle incoming messages while the app is not loaded
|
||||||
|
// The messages app overrides Bangle.messageListener
|
||||||
|
// (placed in separate file, so we don't read this all at boot time)
|
||||||
|
exports.messageListener = function(type, msg) {
|
||||||
|
if (msg.handled || (global.__FILE__ && __FILE__.startsWith("messagelist."))) return; // already handled/app open
|
||||||
|
// clean up, in case previous message didn't load the app after all
|
||||||
|
if (exports.loadTimeout) clearTimeout(exports.loadTimeout);
|
||||||
|
delete exports.loadTimeout;
|
||||||
|
delete exports.buzz;
|
||||||
|
const quiet = () => (require("Storage").readJSON("setting.json", 1) || {}).quiet;
|
||||||
|
/**
|
||||||
|
* Quietly load the app for music/map, if not already loading
|
||||||
|
*/
|
||||||
|
function loadQuietly(msg) {
|
||||||
|
if (exports.loadTimeout) return; // already loading
|
||||||
|
exports.loadTimeout = setTimeout(function() {
|
||||||
|
Bangle.load("messagelist.app.js");
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
function loadNormal(msg) {
|
||||||
|
if (exports.loadTimeout) clearTimeout(exports.loadTimeout); // restart timeout
|
||||||
|
exports.loadTimeout = setTimeout(function() {
|
||||||
|
delete exports.loadTimeout;
|
||||||
|
// check there are still new messages (for #1362)
|
||||||
|
let messages = require("messages").getMessages(msg);
|
||||||
|
(Bangle.MESSAGES || []).forEach(m => require("messages").apply(m, messages));
|
||||||
|
if (!messages.some(m => m.new)) return; // don't use `status()`: also load for new music!
|
||||||
|
// if we're in a clock, or it's important, open app
|
||||||
|
if (Bangle.CLOCK || msg.important) {
|
||||||
|
if (exports.buzz) require("messages").buzz(msg.src);
|
||||||
|
Bangle.load("messagelist.app.js");
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark message as handled, and save it for the app
|
||||||
|
*/
|
||||||
|
const handled = () => {
|
||||||
|
if (!Bangle.MESSAGES) Bangle.MESSAGES = [];
|
||||||
|
require("messages").apply(msg, Bangle.MESSAGES);
|
||||||
|
if (!Bangle.MESSAGES.length) delete Bangle.MESSAGES;
|
||||||
|
if (msg.t==="remove") require("messages").save(msg);
|
||||||
|
else msg.handled = true;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Write messages to flash after all, when not laoding the app
|
||||||
|
*/
|
||||||
|
const saveToFlash = () => {
|
||||||
|
(Bangle.MESSAGES||[]).forEach(m=>require("messages").save(m));
|
||||||
|
delete Bangle.MESSAGES;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case "music":
|
||||||
|
if (!Bangle.CLOCK) return;
|
||||||
|
// only load app if we are playing, and we know which song
|
||||||
|
if (msg.state!=="play" || !msg.title) return;
|
||||||
|
if (exports.openMusic===undefined) {
|
||||||
|
// only read settings for first music message
|
||||||
|
exports.openMusic = !!(exports.settings().openMusic);
|
||||||
|
}
|
||||||
|
if (!exports.openMusic) return; // we don't care about music
|
||||||
|
if (quiet()) return;
|
||||||
|
msg.new = true;
|
||||||
|
handled();
|
||||||
|
return loadQuietly();
|
||||||
|
|
||||||
|
case "map":
|
||||||
|
handled();
|
||||||
|
if (msg.t!=="remove" && Bangle.CLOCK) loadQuietly();
|
||||||
|
else saveToFlash();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "text":
|
||||||
|
handled();
|
||||||
|
if (exports.settings().autoOpen===false) return saveToFlash();
|
||||||
|
if (quiet()) return saveToFlash();
|
||||||
|
if (msg.t!=="add" || !msg.new || !(Bangle.CLOCK || msg.important)) {
|
||||||
|
// not important enough to load the app
|
||||||
|
if (msg.t==="add" && msg.new) require("messages").buzz(msg);
|
||||||
|
return saveToFlash();
|
||||||
|
}
|
||||||
|
if (msg.t==="add" && msg.new) exports.buzz = true;
|
||||||
|
return loadNormal(msg);
|
||||||
|
|
||||||
|
case "alarm":
|
||||||
|
if (quiet()<2) return saveToFlash();
|
||||||
|
// fall through
|
||||||
|
case "call":
|
||||||
|
handled();
|
||||||
|
exports.buzz = true;
|
||||||
|
return loadNormal(msg);
|
||||||
|
|
||||||
|
// case "clearAll": do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.settings = function() {
|
||||||
|
return Object.assign({
|
||||||
|
// Interface //
|
||||||
|
fontSize: 1,
|
||||||
|
onTap: 0, // [Message menu, Dismiss, Back, Nothing]
|
||||||
|
button: true,
|
||||||
|
|
||||||
|
// Behaviour //
|
||||||
|
vibrate: ":",
|
||||||
|
vibrateCalls: ":",
|
||||||
|
vibrateAlarms: ":",
|
||||||
|
repeat: 4,
|
||||||
|
vibrateTimeout: 60,
|
||||||
|
unreadTimeout: 60,
|
||||||
|
autoOpen: true,
|
||||||
|
|
||||||
|
// Music //
|
||||||
|
openMusic: true,
|
||||||
|
// no default: alwaysShowMusic (auto-enabled by app when music happens)
|
||||||
|
showAlbum: true,
|
||||||
|
musicButtons: false,
|
||||||
|
|
||||||
|
// Widget //
|
||||||
|
flash: true,
|
||||||
|
// showRead: false,
|
||||||
|
|
||||||
|
// Utils //
|
||||||
|
},
|
||||||
|
// fall back to default app settings if not set for messagelist
|
||||||
|
(require("Storage").readJSON("messages.settings.json", true) || {}),
|
||||||
|
(require("Storage").readJSON("messagelist.settings.json", true) || {}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} icon Icon name
|
||||||
|
* @returns string Icon image string, for use with g.drawImage()
|
||||||
|
*/
|
||||||
|
exports.getIcon = function(icon) {
|
||||||
|
// TODO: icons should be 24x24px with 1bpp colors
|
||||||
|
switch(icon.toLowerCase()) {
|
||||||
|
// generic icons:
|
||||||
|
case "alert":
|
||||||
|
return atob("GBgBAAAAAP8AA//AD8PwHwD4HBg4ODwcODwccDwOcDwOYDwGYDwGYBgGYBgGcBgOcAAOOBgcODwcHDw4Hxj4D8PwA//AAP8AAAAA");
|
||||||
|
case "alarm":
|
||||||
|
case "alarmclockreceiver":
|
||||||
|
return atob("GBjBAP////8AAAAAAAACAEAHAOAefng5/5wTgcgHAOAOGHAMGDAYGBgYGBgYGBgYGBgYDhgYBxgMATAOAHAHAOADgcAB/4AAfgAAAAAAAAA=");
|
||||||
|
case "back": // TODO: 22x22
|
||||||
|
return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA==");
|
||||||
|
case "calendar":
|
||||||
|
return atob("GBiBAAAAAAAAAAAAAA//8B//+BgAGBgAGBgAGB//+B//+B//+B9m2B//+B//+Btm2B//+B//+Btm+B//+B//+A//8AAAAAAAAAAAAA==");
|
||||||
|
case "mail": // TODO: 28x18
|
||||||
|
case "sms message":
|
||||||
|
case "notification":
|
||||||
|
return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A==");
|
||||||
|
case "map": // TODO: 25x25,
|
||||||
|
return atob("GRmBAAAAAAAAAAAAAAIAYAHx/wH//+D/+fhz75w/P/4f//8P//uH///D///h3f/w4P+4eO/8PHZ+HJ/nDu//g///wH+HwAYAIAAAAAAAAAAAAAA=");
|
||||||
|
case "menu":
|
||||||
|
return atob("GBiBAAAAAAAAAAAAAAAAAP///////wAAAAAAAAAAAAAAAAAAAP///////wAAAAAAAAAAAAAAAAAAAP///////wAAAAAAAAAAAAAAAA==");
|
||||||
|
case "music": // TODO: 22x22
|
||||||
|
return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A=");
|
||||||
|
case "nak": // TODO: 22x25
|
||||||
|
return atob("FhmBAA//wH//j//+P//8///7///v//+///7//////////////v//////////z//+D8AAPwAAfgAB+AAD4AAPgAAeAAB4AAHAAA==");
|
||||||
|
case "neg": // TODO: 22x22
|
||||||
|
return atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA=");
|
||||||
|
case "next":
|
||||||
|
return atob("GBiBAAAAAAAAAAAAAAwAcB8A+B+A+B/g+B/4+B/8+B//+B//+B//+B//+B//+B//+B/8+B/4+B/g+B+A+B8A+AwAcAAAAAAAAAAAAA==");
|
||||||
|
case "nophone": // TODO: 30x30
|
||||||
|
return atob("Hh6BAAAAAAGAAAAHAAAADgAAABwADwA4Af8AcA/8AOB/+AHH/+ADv/8AB//wAA/HAAAeAAACOAAADHAAAHjgAAPhwAAfg4AAfgcAAfwOAA/wHAA/wDgA/gBwA/gA4AfAAcAfAAOAGAAHAAAADgAAABgAAAAA");
|
||||||
|
case "ok": // TODO: 22x25
|
||||||
|
return atob("FhmBAAHAAAeAAB4AAPgAA+AAH4AAfgAD8AAPwAD//+//////////////7//////////////v//+///7///v//8///gf/+A//wA==");
|
||||||
|
case "pause":
|
||||||
|
return atob("GBiBAAAAAAAAAAAAAAOBwAfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AfD4AOBwAAAAAAAAAAAAA==");
|
||||||
|
case "phone": // TODO: 23x23
|
||||||
|
case "call":
|
||||||
|
return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA=");
|
||||||
|
case "play":
|
||||||
|
return atob("GBiBAAAAAAAAAAAAAAcAAA+AAA/gAA/4AA/8AA//AA//wA//4A//8A//8A//4A//wA//AA/8AA/4AA/gAA+AAAcAAAAAAAAAAAAAAA==");
|
||||||
|
case "pos": // TODO: 25x20
|
||||||
|
return atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA==");
|
||||||
|
case "previous":
|
||||||
|
return atob("GBiBAAAAAAAAAAAAAA4AMB8A+B8B+B8H+B8f+B8/+B//+B//+B//+B//+B//+B//+B8/+B8f+B8H+B8B+B8A+A4AMAAAAAAAAAAAAA==");
|
||||||
|
case "settings": // TODO: 20x20
|
||||||
|
return atob("FBSBAAAAAA8AAPABzzgf/4H/+A//APnwfw/n4H5+B+fw/g+fAP/wH/+B//gc84APAADwAAAA");
|
||||||
|
case "to do":
|
||||||
|
return atob("GBgBAAAAAAAAAAAwAAB4AAD8AAH+AAP/DAf/Hg//Px/+f7/8///4///wf//gP//AH/+AD/8AB/4AA/wAAfgAAPAAAGAAAAAAAAAA");
|
||||||
|
case "trash":
|
||||||
|
return atob("GBiBAAAAAAAAAAB+AA//8A//8AYAYAYAYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAZmYAYAYAYAYAf/4AP/wAAAAAAAAA==");
|
||||||
|
case "unknown": // TODO: 30x30
|
||||||
|
return atob("Hh6BAAAAAAAAAAAAAAAAAAPwAAA/8AAB/+AAD//AAD4fAAHwPgAHwPgAAAPgAAAfAAAA/AAAD+AAAH8AAAHwAAAPgAAAPgAAAPgAAAAAAAAAAAAAAAAAAHAAAAPgAAAPgAAAPgAAAHAAAAAAAAAAAAAAAAAA");
|
||||||
|
case "unread": // TODO: 29x24
|
||||||
|
return atob("HRiBAAAAH4AAAf4AAB/4AAHz4AAfn4AA/Pz/5+fj/z8/j/n5/j/P//j/Pn3j+PPPx+P8fx+Pw/x+AF/B4A78RiP3xwOPvHw+Pcf/+Ox//+NH//+If//+B///+A==");
|
||||||
|
default: //should never happen
|
||||||
|
return exports.getIcon("unknown");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @param {string} icon Icon
|
||||||
|
* @returns {string} Color to use with g.setColor()
|
||||||
|
*/
|
||||||
|
exports.getColor = function(icon) {
|
||||||
|
switch(icon.toLowerCase()) {
|
||||||
|
// generic colors, using B2-safe colors
|
||||||
|
case "alert":
|
||||||
|
return "#ff0";
|
||||||
|
case "alarm":
|
||||||
|
return "#fff";
|
||||||
|
case "calendar":
|
||||||
|
return "#f00";
|
||||||
|
case "mail":
|
||||||
|
return "#ff0";
|
||||||
|
case "map":
|
||||||
|
return "#f0f";
|
||||||
|
case "music":
|
||||||
|
return "#f0f";
|
||||||
|
case "neg":
|
||||||
|
return "#f00";
|
||||||
|
case "notification":
|
||||||
|
return "#0ff";
|
||||||
|
case "phone":
|
||||||
|
case "call":
|
||||||
|
return "#0f0";
|
||||||
|
case "settings":
|
||||||
|
return "#000";
|
||||||
|
case "sms message":
|
||||||
|
return "#0ff";
|
||||||
|
case "trash":
|
||||||
|
return "#f00";
|
||||||
|
case "unknown":
|
||||||
|
return g.theme.fg;
|
||||||
|
case "unread":
|
||||||
|
return "#ff0";
|
||||||
|
default:
|
||||||
|
return g.theme.fg;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch GUI app with given message
|
||||||
|
* @param {object} msg
|
||||||
|
*/
|
||||||
|
exports.open = function(msg) {
|
||||||
|
if (msg && msg.id && !msg.show) {
|
||||||
|
// store which message to load
|
||||||
|
msg.show = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.load((msg && msg.new && msg.id!=="music") ? "messagelist.new.js" : "messagelist.app.js");
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"id": "messagelist",
|
||||||
|
"name": "Message List",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Display notifications from iOS and Gadgetbridge/Android as a list",
|
||||||
|
"icon": "app.png",
|
||||||
|
"type": "app",
|
||||||
|
"tags": "tool,system",
|
||||||
|
"screenshots": [
|
||||||
|
{"url": "screenshot0.png"},
|
||||||
|
{"url": "screenshot1.png"},
|
||||||
|
{"url": "screenshot2.png"},
|
||||||
|
{"url": "screenshot3.png"}
|
||||||
|
],
|
||||||
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"dependencies" : { "messageicons":"module" },
|
||||||
|
"provides_modules": ["messagegui"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"messagelist.boot.js","url":"boot.js"},
|
||||||
|
{"name":"messagegui","url":"lib.js"},
|
||||||
|
{"name":"messagelist.app.js","url":"app.js"},
|
||||||
|
{"name":"messagelist.settings.js","url":"settings.js"},
|
||||||
|
{"name":"messagelist.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [{"name":"messagelist.settings.json"}],
|
||||||
|
"sortorder": -9
|
||||||
|
}
|
After Width: | Height: | Size: 19 KiB |