Merge remote-tracking branch 'upstream/master'
commit
3904ec0429
|
|
@ -786,12 +786,13 @@
|
||||||
{ "id": "widpedom",
|
{ "id": "widpedom",
|
||||||
"name": "Pedometer widget",
|
"name": "Pedometer widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"0.08",
|
"version":"0.09",
|
||||||
"description": "Daily pedometer widget",
|
"description": "Daily pedometer widget",
|
||||||
"tags": "widget",
|
"tags": "widget",
|
||||||
"type":"widget",
|
"type":"widget",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"widpedom.wid.js","url":"widget.js"}
|
{"name":"widpedom.wid.js","url":"widget.js"},
|
||||||
|
{"name":"widpedom.settings.js","url":"settings.js"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ "id": "berlinc",
|
{ "id": "berlinc",
|
||||||
|
|
@ -914,7 +915,7 @@
|
||||||
{ "id": "marioclock",
|
{ "id": "marioclock",
|
||||||
"name": "Mario Clock",
|
"name": "Mario Clock",
|
||||||
"icon": "marioclock.png",
|
"icon": "marioclock.png",
|
||||||
"version":"0.08",
|
"version":"0.09",
|
||||||
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
|
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
|
||||||
"tags": "clock,mario,retro",
|
"tags": "clock,mario,retro",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,5 @@
|
||||||
0.05: use 12/24 hour clock from settings
|
0.05: use 12/24 hour clock from settings
|
||||||
0.06: Performance refactor, and enhanced graphics!
|
0.06: Performance refactor, and enhanced graphics!
|
||||||
0.07: Swipe right to change between Mario and Toad characters, swipe left to toggle night mode
|
0.07: Swipe right to change between Mario and Toad characters, swipe left to toggle night mode
|
||||||
0.08: Update date panel to be info panel toggling between Date, Battery and Temperature. Add Princes Daisy.
|
0.08: Update date panel to be info panel toggling between Date, Battery and Temperature. Add Princes Daisy
|
||||||
|
0.09: Add GadgetBridge functionality. Mario shows message type in speach bubble, while message scrolls in info panel
|
||||||
|
|
@ -13,7 +13,8 @@ Enjoy watching Mario, or one of the other game characters run through a level wh
|
||||||
* Awesome 8-bit style grey-scale graphics
|
* Awesome 8-bit style grey-scale graphics
|
||||||
* Mario jumps to change the time, every minute
|
* Mario jumps to change the time, every minute
|
||||||
* You can make Mario jump by pressing the bottom button (Button 3) on the watch
|
* You can make Mario jump by pressing the bottom button (Button 3) on the watch
|
||||||
* Toggle the info pannel bettween `Date`, `Battery level`, and `Temperature` by pressing the top button (Button 1).
|
* Toggle the info pannel bettween `Date`, `Battery level`, and `Temperature` by pressing the top button (Button 1)
|
||||||
|
* If you have [GadgetBridge](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/) installed on your phone, Mario will let you know when you get a new call or notification. You can clear a message by pressing either Button 1 or Button 3
|
||||||
|
|
||||||
## Requests
|
## Requests
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ const ONE_SECOND = 1000;
|
||||||
const DATE_MODE = "date";
|
const DATE_MODE = "date";
|
||||||
const BATT_MODE = "batt";
|
const BATT_MODE = "batt";
|
||||||
const TEMP_MODE = "temp";
|
const TEMP_MODE = "temp";
|
||||||
|
const PHON_MODE = "gbri";
|
||||||
|
|
||||||
let timer = 0;
|
let timer = 0;
|
||||||
let backgroundArr = [];
|
let backgroundArr = [];
|
||||||
|
|
@ -68,6 +69,77 @@ let infoMode = DATE_MODE;
|
||||||
let lastBatt = 0;
|
let lastBatt = 0;
|
||||||
let lastTemp = 0;
|
let lastTemp = 0;
|
||||||
|
|
||||||
|
const phone = {
|
||||||
|
get status() {
|
||||||
|
return NRF.getSecurityStatus().connected ? "Yes" : "No";
|
||||||
|
},
|
||||||
|
message: null,
|
||||||
|
messageTimeout: null,
|
||||||
|
messageScrollX: null,
|
||||||
|
messageType: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
function phoneOutbound(msg) {
|
||||||
|
Bluetooth.println(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
function phoneClearMessage() {
|
||||||
|
if (phone.message === null) return;
|
||||||
|
|
||||||
|
if (phone.messageTimeout) {
|
||||||
|
clearTimeout(phone.messageTimeout);
|
||||||
|
phone.messageTimeout = null;
|
||||||
|
}
|
||||||
|
phone.message = null;
|
||||||
|
phone.messageScrollX = null;
|
||||||
|
phone.messageType = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function phoneNewMessage(type, msg) {
|
||||||
|
Bangle.buzz();
|
||||||
|
|
||||||
|
phoneClearMessage();
|
||||||
|
phone.messageTimeout = setTimeout(() => phone.message = null, ONE_SECOND * 30);
|
||||||
|
phone.message = msg;
|
||||||
|
phone.messageType = type;
|
||||||
|
|
||||||
|
// Notify user and active screen
|
||||||
|
if (!Bangle.isLCDOn()) {
|
||||||
|
clearTimers();
|
||||||
|
Bangle.setLCDPower(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function truncStr(str, max) {
|
||||||
|
if (str.length > max) {
|
||||||
|
return str.substr(0, max) + '...';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function phoneInbound(evt) {
|
||||||
|
switch (evt.t) {
|
||||||
|
case 'notify':
|
||||||
|
const sender = truncStr(evt.sender, 10);
|
||||||
|
const subject = truncStr(evt.subject, 15);
|
||||||
|
phoneNewMessage("notify", `${sender} - '${subject}'`);
|
||||||
|
break;
|
||||||
|
case 'call':
|
||||||
|
if (evt.cmd === "accept") {
|
||||||
|
let nameOrNumber = "Unknown";
|
||||||
|
if (evt.name !== null || evt.name !== "") {
|
||||||
|
nameOrNumber = evt.name;
|
||||||
|
} else if (evt.number !== null || evt.number !== "") {
|
||||||
|
nameOrNumber = evt.number;
|
||||||
|
}
|
||||||
|
phoneNewMessage("call", nameOrNumber);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function genRanNum(min, max) {
|
function genRanNum(min, max) {
|
||||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||||
}
|
}
|
||||||
|
|
@ -248,6 +320,22 @@ function drawToadFrame(idx, x, y) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mario speach bubble
|
||||||
|
function drawNotice(x, y) {
|
||||||
|
if (phone.message === null) return;
|
||||||
|
|
||||||
|
switch (phone.messageType) {
|
||||||
|
case "call":
|
||||||
|
const callImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
|
||||||
|
g.drawImage(callImg, characterSprite.x, characterSprite.y - 16);
|
||||||
|
break;
|
||||||
|
case "notify":
|
||||||
|
const msgImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
|
||||||
|
g.drawImage(msgImg, characterSprite.x, characterSprite.y - 16);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function drawCharacter(date, character) {
|
function drawCharacter(date, character) {
|
||||||
// calculate jumping
|
// calculate jumping
|
||||||
const seconds = date.getSeconds(),
|
const seconds = date.getSeconds(),
|
||||||
|
|
@ -352,9 +440,33 @@ function buildTempStr() {
|
||||||
return tempStr;
|
return tempStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildPhonStr() {
|
||||||
|
return `Phone: ${phone.status}`;
|
||||||
|
}
|
||||||
|
|
||||||
function drawInfo(date) {
|
function drawInfo(date) {
|
||||||
|
let xPos;
|
||||||
let str = "";
|
let str = "";
|
||||||
switch(infoMode) {
|
|
||||||
|
if (phone.message !== null) {
|
||||||
|
str = phone.message;
|
||||||
|
const strLen = g.stringWidth(str);
|
||||||
|
if (strLen > W) {
|
||||||
|
if (phone.messageScrollX === null || (phone.messageScrollX <= (strLen * -1))) {
|
||||||
|
phone.messageScrollX = W;
|
||||||
|
resetDisplayTimeout();
|
||||||
|
} else {
|
||||||
|
phone.messageScrollX -= 2;
|
||||||
|
}
|
||||||
|
xPos = phone.messageScrollX;
|
||||||
|
} else {
|
||||||
|
xPos = (W - g.stringWidth(str)) / 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch(infoMode) {
|
||||||
|
case PHON_MODE:
|
||||||
|
str = buildPhonStr();
|
||||||
|
break;
|
||||||
case TEMP_MODE:
|
case TEMP_MODE:
|
||||||
str = buildTempStr();
|
str = buildTempStr();
|
||||||
break;
|
break;
|
||||||
|
|
@ -364,19 +476,26 @@ function drawInfo(date) {
|
||||||
case DATE_MODE:
|
case DATE_MODE:
|
||||||
default:
|
default:
|
||||||
str = buildDateStr(date);
|
str = buildDateStr(date);
|
||||||
|
}
|
||||||
|
xPos = (W - g.stringWidth(str)) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setFont("6x8");
|
g.setFont("6x8");
|
||||||
g.setColor(LIGHTEST);
|
g.setColor(LIGHTEST);
|
||||||
g.drawString(str, (W - g.stringWidth(str))/2, 1);
|
g.drawString(str, xPos, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeInfoMode() {
|
function changeInfoMode() {
|
||||||
|
phoneClearMessage();
|
||||||
|
|
||||||
switch(infoMode) {
|
switch(infoMode) {
|
||||||
case BATT_MODE:
|
case BATT_MODE:
|
||||||
infoMode = TEMP_MODE;
|
infoMode = TEMP_MODE;
|
||||||
break;
|
break;
|
||||||
case TEMP_MODE:
|
case TEMP_MODE:
|
||||||
|
infoMode = PHON_MODE;
|
||||||
|
break;
|
||||||
|
case PHON_MODE:
|
||||||
infoMode = DATE_MODE;
|
infoMode = DATE_MODE;
|
||||||
break;
|
break;
|
||||||
case DATE_MODE:
|
case DATE_MODE:
|
||||||
|
|
@ -399,6 +518,7 @@ function redraw() {
|
||||||
drawTime(date);
|
drawTime(date);
|
||||||
drawInfo(date);
|
drawInfo(date);
|
||||||
drawCharacter(date);
|
drawCharacter(date);
|
||||||
|
drawNotice();
|
||||||
drawCoin();
|
drawCoin();
|
||||||
|
|
||||||
// Render new frame
|
// Render new frame
|
||||||
|
|
@ -450,6 +570,7 @@ function init() {
|
||||||
setWatch(() => {
|
setWatch(() => {
|
||||||
if (intervalRef && !characterSprite.isJumping) characterSprite.isJumping = true;
|
if (intervalRef && !characterSprite.isJumping) characterSprite.isJumping = true;
|
||||||
resetDisplayTimeout();
|
resetDisplayTimeout();
|
||||||
|
phoneClearMessage(); // Clear any phone messages and message timers
|
||||||
}, BTN3, {repeat: true});
|
}, BTN3, {repeat: true});
|
||||||
|
|
||||||
// Close watch and load launcher app
|
// Close watch and load launcher app
|
||||||
|
|
@ -487,8 +608,21 @@ function init() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Phone connectivity
|
||||||
|
try { NRF.wake(); } catch (e) {}
|
||||||
|
|
||||||
|
NRF.on('disconnect', () => Bangle.buzz());
|
||||||
|
NRF.on('connect', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
phoneOutbound({ t: "status", bat: E.getBattery() });
|
||||||
|
}, ONE_SECOND * 2);
|
||||||
|
Bangle.buzz();
|
||||||
|
});
|
||||||
|
|
||||||
|
GB = (evt) => phoneInbound(evt);
|
||||||
|
|
||||||
startTimers();
|
startTimers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise!
|
// Initialise!
|
||||||
init();
|
init()
|
||||||
|
|
@ -5,3 +5,4 @@
|
||||||
0.06: Fix widget position increment
|
0.06: Fix widget position increment
|
||||||
0.07: Tweaks for variable size widget system
|
0.07: Tweaks for variable size widget system
|
||||||
0.08: Ensure redrawing works with variable size widget system
|
0.08: Ensure redrawing works with variable size widget system
|
||||||
|
0.09: Add daily goal
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
(function(back) {
|
||||||
|
const SETTINGS_FILE = 'widpedom.settings.json'
|
||||||
|
|
||||||
|
// initialize with default settings...
|
||||||
|
let s = {
|
||||||
|
'goal': 10000,
|
||||||
|
'progress': false,
|
||||||
|
}
|
||||||
|
// ...and overwrite them with any saved values
|
||||||
|
// This way saved values are preserved if a new version adds more settings
|
||||||
|
const storage = require('Storage')
|
||||||
|
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||||
|
for (const key in saved) {
|
||||||
|
s[key] = saved[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
storage.write(SETTINGS_FILE, s)
|
||||||
|
WIDGETS['wpedom'].reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu({
|
||||||
|
'': { 'title': 'Pedometer widget' },
|
||||||
|
'Daily Goal': {
|
||||||
|
value: s.goal,
|
||||||
|
min: 0, step: 1000,
|
||||||
|
format: s => (s ? s / 1000 + ',000' : '0'),
|
||||||
|
onchange: (g) => {
|
||||||
|
s.goal = g
|
||||||
|
s.progress = !!g
|
||||||
|
save()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Show Progress': {
|
||||||
|
value: s.progress,
|
||||||
|
format: () => (s.progress ? 'Yes' : 'No'),
|
||||||
|
onchange: () => {
|
||||||
|
s.progress = !s.progress
|
||||||
|
save()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'< Back': back,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -1,7 +1,57 @@
|
||||||
(() => {
|
(() => {
|
||||||
const PEDOMFILE = "wpedom.json";
|
const PEDOMFILE = "wpedom.json"
|
||||||
|
const SETTINGS_FILE = "widpedom.settings.json"
|
||||||
|
const DEFAULTS = {
|
||||||
|
'goal': 10000,
|
||||||
|
'progress': false,
|
||||||
|
}
|
||||||
|
const COLORS = {
|
||||||
|
'white': -1,
|
||||||
|
'progress': 0x001F, // Blue
|
||||||
|
'done': 0x03E0, // DarkGreen
|
||||||
|
}
|
||||||
|
const TAU = Math.PI*2;
|
||||||
let lastUpdate = new Date();
|
let lastUpdate = new Date();
|
||||||
let stp_today = 0;
|
let stp_today = 0;
|
||||||
|
let settings;
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function setting(key) {
|
||||||
|
if (!settings) { loadSettings() }
|
||||||
|
return (key in settings) ? settings[key] : DEFAULTS[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawProgress(stps) {
|
||||||
|
if (setting('progress')) {
|
||||||
|
const width = 24, half = width/2;
|
||||||
|
const goal = setting('goal'), left = Math.max(goal-stps,0);
|
||||||
|
const c = left ? COLORS.progress : COLORS.done;
|
||||||
|
g.setColor(c).fillCircle(this.x + half, this.y + half, half);
|
||||||
|
if (left) {
|
||||||
|
const f = left/goal; // fraction to blank out
|
||||||
|
let p = [];
|
||||||
|
p.push(half,half);
|
||||||
|
p.push(half,0);
|
||||||
|
if(f>1/8) p.push(0,0);
|
||||||
|
if(f>2/8) p.push(0,half);
|
||||||
|
if(f>3/8) p.push(0,width);
|
||||||
|
if(f>4/8) p.push(half,width);
|
||||||
|
if(f>5/8) p.push(width,width);
|
||||||
|
if(f>6/8) p.push(width,half);
|
||||||
|
if(f>7/8) p.push(width,0);
|
||||||
|
p.push(half - Math.sin(f * TAU) * half);
|
||||||
|
p.push(half - Math.cos(f * TAU) * half);
|
||||||
|
for (let i = p.length; i; i -= 2) {
|
||||||
|
p[i - 2] += this.x;
|
||||||
|
p[i - 1] += this.y;
|
||||||
|
}
|
||||||
|
g.setColor(0).fillPoly(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// draw your widget
|
// draw your widget
|
||||||
function draw() {
|
function draw() {
|
||||||
|
|
@ -11,6 +61,9 @@
|
||||||
}
|
}
|
||||||
let stps = stp_today.toString();
|
let stps = stp_today.toString();
|
||||||
g.reset();
|
g.reset();
|
||||||
|
g.clearRect(this.x, this.y, this.x + width, this.y + 23); // erase background
|
||||||
|
drawProgress(stps);
|
||||||
|
g.setColor(COLORS.white);
|
||||||
if (stps.length > 3){
|
if (stps.length > 3){
|
||||||
stps = stps.slice(0,-3) + "," + stps.slice(-3);
|
stps = stps.slice(0,-3) + "," + stps.slice(-3);
|
||||||
g.setFont("4x6", 1); // if big, shrink text to fix
|
g.setFont("4x6", 1); // if big, shrink text to fix
|
||||||
|
|
@ -18,11 +71,15 @@
|
||||||
g.setFont("6x8", 1);
|
g.setFont("6x8", 1);
|
||||||
}
|
}
|
||||||
g.setFontAlign(0, 0); // align to x: center, y: center
|
g.setFontAlign(0, 0); // align to x: center, y: center
|
||||||
g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background
|
|
||||||
g.drawString(stps, this.x+width/2, this.y+19);
|
g.drawString(stps, this.x+width/2, this.y+19);
|
||||||
g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(width-10)/2,this.y+2);
|
g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(width-10)/2,this.y+2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reload() {
|
||||||
|
loadSettings()
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
|
||||||
Bangle.on('step', (up) => {
|
Bangle.on('step', (up) => {
|
||||||
let date = new Date();
|
let date = new Date();
|
||||||
if (lastUpdate.getDate() == date.getDate()){
|
if (lastUpdate.getDate() == date.getDate()){
|
||||||
|
|
@ -31,7 +88,13 @@
|
||||||
// TODO: could save this to PEDOMFILE for lastUpdate's day?
|
// TODO: could save this to PEDOMFILE for lastUpdate's day?
|
||||||
stp_today = 1;
|
stp_today = 1;
|
||||||
}
|
}
|
||||||
lastUpdate = date;
|
if (stp_today === setting('goal')) {
|
||||||
|
let b = 3, buzz = () => {
|
||||||
|
if (b--) Bangle.buzz().then(() => setTimeout(buzz, 100))
|
||||||
|
}
|
||||||
|
buzz()
|
||||||
|
}
|
||||||
|
lastUpdate = date
|
||||||
//console.log("up: " + up + " stp: " + stp_today + " " + date.toString());
|
//console.log("up: " + up + " stp: " + stp_today + " " + date.toString());
|
||||||
if (Bangle.isLCDOn()) WIDGETS["wpedom"].draw();
|
if (Bangle.isLCDOn()) WIDGETS["wpedom"].draw();
|
||||||
});
|
});
|
||||||
|
|
@ -49,7 +112,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// add your widget
|
// add your widget
|
||||||
WIDGETS["wpedom"]={area:"tl",width:26,draw:draw};
|
WIDGETS["wpedom"]={area:"tl",width:26,draw:draw,reload:Reload};
|
||||||
// Load data at startup
|
// Load data at startup
|
||||||
let pedomData = require("Storage").readJSON(PEDOMFILE,1);
|
let pedomData = require("Storage").readJSON(PEDOMFILE,1);
|
||||||
if (pedomData) {
|
if (pedomData) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue