Merge branch 'master' into custom_theme
commit
2b5f1228b8
33
apps.json
33
apps.json
|
|
@ -42,7 +42,7 @@
|
|||
"name": "Launcher (Default)",
|
||||
"shortName":"Launcher",
|
||||
"icon": "app.png",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
|
||||
"tags": "tool,system,launcher,b2",
|
||||
"type":"launch",
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
"name": "Fullscreen Notifications",
|
||||
"shortName":"Notifications",
|
||||
"icon": "notify.png",
|
||||
"version":"0.11",
|
||||
"version":"0.12",
|
||||
"description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.",
|
||||
"tags": "widget,b2",
|
||||
"type": "notify",
|
||||
|
|
@ -199,11 +199,11 @@
|
|||
"sortorder" : -2
|
||||
},
|
||||
{ "id": "alarm",
|
||||
"name": "Default Alarm",
|
||||
"name": "Default Alarm & Timer",
|
||||
"shortName":"Alarms",
|
||||
"icon": "app.png",
|
||||
"version":"0.12",
|
||||
"description": "Set and respond to alarms",
|
||||
"version":"0.13",
|
||||
"description": "Set and respond to alarms and timers",
|
||||
"tags": "tool,alarm,widget,b2",
|
||||
"storage": [
|
||||
{"name":"alarm.app.js","url":"app.js"},
|
||||
|
|
@ -585,7 +585,7 @@
|
|||
{ "id": "weather",
|
||||
"name": "Weather",
|
||||
"icon": "icon.png",
|
||||
"version":"0.08",
|
||||
"version":"0.10",
|
||||
"description": "Show Gadgetbridge weather report",
|
||||
"readme": "readme.md",
|
||||
"tags": "widget,outdoors",
|
||||
|
|
@ -1187,7 +1187,7 @@
|
|||
{ "id": "widclk",
|
||||
"name": "Digital clock widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "A simple digital clock widget",
|
||||
"tags": "widget,clock",
|
||||
"type":"widget",
|
||||
|
|
@ -1236,9 +1236,9 @@
|
|||
{ "id": "demoapp",
|
||||
"name": "Demo Loop",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information",
|
||||
"tags": "",
|
||||
"tags": "bno2",
|
||||
"type":"app",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
|
|
@ -1328,14 +1328,13 @@
|
|||
"id": "grocery",
|
||||
"name": "Grocery",
|
||||
"icon": "grocery.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.",
|
||||
"tags": "tool,outdoors,shopping,list",
|
||||
"type": "app",
|
||||
"custom":"grocery.html",
|
||||
"storage": [
|
||||
{"name":"grocery"},
|
||||
{"name":"grocery.app.js"},
|
||||
{"name":"grocery.app.js", "url":"app.js"},
|
||||
{"name":"grocery.img","url":"grocery-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
|
|
@ -1381,7 +1380,7 @@
|
|||
{ "id": "barclock",
|
||||
"name": "Bar Clock",
|
||||
"icon": "clock-bar.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"description": "A simple digital clock showing seconds as a bar",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
|
|
@ -2058,7 +2057,7 @@
|
|||
"name": "Find Phone",
|
||||
"shortName":"Find Phone",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.",
|
||||
"tags": "tool,android",
|
||||
"readme": "README.md",
|
||||
|
|
@ -3520,10 +3519,11 @@
|
|||
{ "id": "antonclk",
|
||||
"name": "Anton Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "A simple clock using the bold Anton font.",
|
||||
"tags":"clock,b2",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
{"name":"antonclk.app.js","url":"app.js"},
|
||||
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
|
||||
|
|
@ -3532,10 +3532,11 @@
|
|||
{ "id": "waveclk",
|
||||
"name": "Wave Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**",
|
||||
"tags":"clock,b2",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
{"name":"waveclk.app.js","url":"app.js"},
|
||||
{"name":"waveclk.img","url":"app-icon.js","evaluate":true}
|
||||
|
|
|
|||
|
|
@ -10,3 +10,5 @@
|
|||
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
|
||||
0.11: Respect Quiet Mode
|
||||
0.12: Fix widget for bangle 2, now uses theme
|
||||
Widgets now shown on Alarm screen
|
||||
Alarm widget state now updates when setting/resetting an alarm
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ function showAlarm(alarm) {
|
|||
var buzzCount = 10;
|
||||
if (alarm.msg)
|
||||
msg += "\n"+alarm.msg;
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
E.showPrompt(msg,{
|
||||
title:"ALARM!",
|
||||
title:alarm.timer ? "TIMER!" : "ALARM!",
|
||||
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
|
||||
}).then(function(sleep) {
|
||||
buzzCount = 0;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[];
|
|||
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
|
||||
rp : true, // repeat
|
||||
as : false, // auto snooze
|
||||
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
|
||||
}
|
||||
];*/
|
||||
|
||||
|
|
@ -18,6 +19,12 @@ function formatTime(t) {
|
|||
return hrs+":"+("0"+mins).substr(-2);
|
||||
}
|
||||
|
||||
function formatMins(t) {
|
||||
mins = (0|t)%60;
|
||||
hrs = 0|(t/60);
|
||||
return hrs+":"+("0"+mins).substr(-2);
|
||||
}
|
||||
|
||||
function getCurrentHr() {
|
||||
var time = new Date();
|
||||
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||
|
|
@ -25,17 +32,24 @@ function getCurrentHr() {
|
|||
|
||||
function showMainMenu() {
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
'New Alarm': ()=>editAlarm(-1)
|
||||
'': { 'title': 'Alarm/Timer' },
|
||||
'New Alarm': ()=>editAlarm(-1),
|
||||
'New Timer': ()=>editTimer(-1)
|
||||
};
|
||||
alarms.forEach((alarm,idx)=>{
|
||||
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
if (alarm.timer) {
|
||||
txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer);
|
||||
} else {
|
||||
txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
}
|
||||
menu[txt] = function() {
|
||||
editAlarm(idx);
|
||||
if (alarm.timer) editTimer(idx);
|
||||
else editAlarm(idx);
|
||||
};
|
||||
});
|
||||
menu['< Back'] = ()=>{load();};
|
||||
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +69,7 @@ function editAlarm(alarmIndex) {
|
|||
as = a.as;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
'': { 'title': 'Alarm' },
|
||||
'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
|
|
@ -109,4 +123,59 @@ function editAlarm(alarmIndex) {
|
|||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function editTimer(alarmIndex) {
|
||||
var newAlarm = alarmIndex<0;
|
||||
var hrs = 0;
|
||||
var mins = 5;
|
||||
var en = true;
|
||||
if (!newAlarm) {
|
||||
var a = alarms[alarmIndex];
|
||||
mins = (0|a.timer)%60;
|
||||
hrs = 0|(a.timer/60);
|
||||
en = a.on;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Timer' },
|
||||
'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Enabled': {
|
||||
value: en,
|
||||
format: v=>v?"On":"Off",
|
||||
onchange: v=>en=v
|
||||
}
|
||||
};
|
||||
function getTimer() {
|
||||
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
|
||||
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
|
||||
// Save alarm
|
||||
return {
|
||||
on : en,
|
||||
timer : (hrs*60)+mins,
|
||||
hr : hr,
|
||||
rp : false, as: false
|
||||
};
|
||||
}
|
||||
menu["> Save"] = function() {
|
||||
if (newAlarm) alarms.push(getTimer());
|
||||
else alarms[alarmIndex] = getTimer();
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
if (!newAlarm) {
|
||||
menu["> Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
}
|
||||
menu['< Back'] = showMainMenu;
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
showMainMenu();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
(() => {
|
||||
var alarms = require('Storage').readJSON('alarm.json',1)||[];
|
||||
alarms = alarms.filter(alarm=>alarm.on);
|
||||
if (!alarms.length) return; // no alarms, no widget!
|
||||
delete alarms;
|
||||
// add the widget
|
||||
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
}};
|
||||
})()
|
||||
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
|
||||
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
},reload:function() {
|
||||
WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
|
||||
}
|
||||
};
|
||||
WIDGETS["alarm"].reload();
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Load widgets after setUI so widclk knows when to hide
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ function queueDraw() {
|
|||
function draw() {
|
||||
var x = g.getWidth()/2;
|
||||
var y = g.getHeight()/2;
|
||||
g.reset();
|
||||
g.reset();
|
||||
var date = new Date();
|
||||
var timeStr = require("locale").time(date,1);
|
||||
var dateStr = require("locale").date(date).toUpperCase();
|
||||
|
|
@ -33,7 +33,7 @@ function draw() {
|
|||
g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background
|
||||
g.drawString(dateStr,x,y);
|
||||
// queue draw in one minute
|
||||
queueDraw();
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// Clear the screen once, at startup
|
||||
|
|
@ -49,9 +49,8 @@ Bangle.on('lcdPower',on=>{
|
|||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Clock does not start if app Languages is not installed
|
||||
0.06: Improve accuracy
|
||||
0.07: Update to use Bangle.setUI instead of setWatch
|
||||
0.08: Use theme colors, Layout library
|
||||
|
|
@ -3,167 +3,102 @@
|
|||
* A simple digital clock showing seconds as a bar
|
||||
**/
|
||||
// Check settings for what type our clock should be
|
||||
const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour']
|
||||
let locale = require('locale')
|
||||
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
|
||||
let locale = require("locale");
|
||||
{ // add some more info to locale
|
||||
let date = new Date()
|
||||
date.setFullYear(1111)
|
||||
date.setMonth(1, 3) // februari: months are zero-indexed
|
||||
const localized = locale.date(date, true)
|
||||
locale.dayFirst = /3.*2/.test(localized)
|
||||
let date = new Date();
|
||||
date.setFullYear(1111);
|
||||
date.setMonth(1, 3); // februari: months are zero-indexed
|
||||
const localized = locale.date(date, true);
|
||||
locale.dayFirst = /3.*2/.test(localized);
|
||||
|
||||
locale.hasMeridian = false
|
||||
if(typeof locale.meridian === 'function') { // function does not exists if languages app is not installed
|
||||
locale.hasMeridian = (locale.meridian(date) !== '')
|
||||
locale.hasMeridian = false;
|
||||
if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
|
||||
locale.hasMeridian = (locale.meridian(date)!=="");
|
||||
}
|
||||
|
||||
}
|
||||
const screen = {
|
||||
width: g.getWidth(),
|
||||
height: g.getWidth(),
|
||||
middle: g.getWidth() / 2,
|
||||
center: g.getHeight() / 2,
|
||||
Bangle.loadWidgets();
|
||||
function renderBar(l) {
|
||||
if (!this.fraction) {
|
||||
// zero-size fillRect stills draws one line of pixels, we don't want that
|
||||
return;
|
||||
}
|
||||
const width = this.fraction*l.w;
|
||||
g.fillRect(l.x, l.y, width-1, l.y+l.height-1);
|
||||
}
|
||||
|
||||
// hardcoded "settings"
|
||||
const settings = {
|
||||
time: {
|
||||
color: -1,
|
||||
font: '6x8',
|
||||
size: (is12Hour && locale.hasMeridian) ? 6 : 8,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
ampm: {
|
||||
color: -1,
|
||||
font: '6x8',
|
||||
size: 2,
|
||||
const Layout = require("Layout");
|
||||
const layout = new Layout({
|
||||
type: "v", c: [
|
||||
{
|
||||
type: "h", c: [
|
||||
{id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below
|
||||
{id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg},
|
||||
],
|
||||
},
|
||||
},
|
||||
date: {
|
||||
color: -1,
|
||||
font: 'Vector',
|
||||
size: 20,
|
||||
middle: screen.height - 20, // at bottom of screen
|
||||
center: screen.center,
|
||||
},
|
||||
bar: {
|
||||
color: -1,
|
||||
top: 155, // just below time
|
||||
thickness: 6, // matches 24h time "pixel" size
|
||||
},
|
||||
{id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
|
||||
{height: 40},
|
||||
{id: "date", type: "txt", font: "10%", valign: 1},
|
||||
],
|
||||
}, false, {lazy: true});
|
||||
// adjustments based on screen size and whether we display am/pm
|
||||
let thickness; // bar thickness, same as time font "pixel block" size
|
||||
if (is12Hour) {
|
||||
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (5chars * 6px)
|
||||
thickness = Math.floor((g.getWidth()-24)/(5*6));
|
||||
} else {
|
||||
layout.ampm.label = "";
|
||||
thickness = Math.floor(g.getWidth()/(5*6));
|
||||
}
|
||||
layout.bar.height = thickness+1;
|
||||
layout.time.font = "6x8:"+thickness;
|
||||
layout.update();
|
||||
|
||||
const SECONDS_PER_MINUTE = 60
|
||||
|
||||
const timeText = function (date) {
|
||||
function timeText(date) {
|
||||
if (!is12Hour) {
|
||||
return locale.time(date, true)
|
||||
return locale.time(date, true);
|
||||
}
|
||||
const date12 = new Date(date.getTime())
|
||||
const hours = date12.getHours()
|
||||
if (hours === 0) {
|
||||
date12.setHours(12)
|
||||
} else if (hours > 12) {
|
||||
date12.setHours(hours - 12)
|
||||
const date12 = new Date(date.getTime());
|
||||
const hours = date12.getHours();
|
||||
if (hours===0) {
|
||||
date12.setHours(12);
|
||||
} else if (hours>12) {
|
||||
date12.setHours(hours-12);
|
||||
}
|
||||
return locale.time(date12, true)
|
||||
return locale.time(date12, true);
|
||||
}
|
||||
const ampmText = function (date) {
|
||||
return is12Hour ? locale.meridian(date) : ''
|
||||
function ampmText(date) {
|
||||
return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
|
||||
}
|
||||
|
||||
const dateText = function (date) {
|
||||
function dateText(date) {
|
||||
const dayName = locale.dow(date, true),
|
||||
month = locale.month(date, true),
|
||||
day = date.getDate()
|
||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`
|
||||
return `${dayName} ${dayMonth}`
|
||||
day = date.getDate();
|
||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`;
|
||||
return `${dayName} ${dayMonth}`;
|
||||
}
|
||||
|
||||
const drawDateTime = function (date) {
|
||||
const t = settings.time
|
||||
g.setColor(t.color)
|
||||
g.setFont(t.font, t.size)
|
||||
g.setFontAlign(0, 0) // centered
|
||||
g.drawString(timeText(date), t.center, t.middle, true)
|
||||
if (is12Hour && locale.hasMeridian) {
|
||||
const a = settings.time.ampm
|
||||
g.setColor(a.color)
|
||||
g.setFont(a.font, a.size)
|
||||
g.setFontAlign(1, -1) // right top
|
||||
// at right edge of screen, aligned with time bottom
|
||||
const left = screen.width - a.size * 2,
|
||||
top = t.middle + t.size - a.size
|
||||
g.drawString(ampmText(date), left, top, true)
|
||||
}
|
||||
draw = function draw() {
|
||||
if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
|
||||
const date = new Date();
|
||||
layout.time.label = timeText(date);
|
||||
layout.ampm.label = ampmText(date);
|
||||
layout.date.label = dateText(date);
|
||||
const SECONDS_PER_MINUTE = 60;
|
||||
layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
|
||||
layout.render();
|
||||
// schedule update at start of next second
|
||||
const millis = date.getMilliseconds();
|
||||
setTimeout(draw, 1000-millis);
|
||||
};
|
||||
|
||||
const d = settings.date
|
||||
g.setColor(d.color)
|
||||
g.setFont(d.font, d.size)
|
||||
g.setFontAlign(0, 0) // centered
|
||||
g.drawString(dateText(date), d.center, d.middle, true)
|
||||
}
|
||||
|
||||
const drawBar = function (date) {
|
||||
const b = settings.bar
|
||||
const seconds = date.getSeconds()
|
||||
if (seconds === 0) {
|
||||
// zero-size rect stills draws one line of pixels, we don't want that
|
||||
return
|
||||
}
|
||||
const fraction = seconds / SECONDS_PER_MINUTE,
|
||||
width = fraction * screen.width
|
||||
g.setColor(b.color)
|
||||
g.fillRect(0, b.top, width, b.top + b.thickness)
|
||||
}
|
||||
|
||||
const clearScreen = function () {
|
||||
g.setColor(0)
|
||||
const timeTop = settings.time.middle - (settings.time.size * 4)
|
||||
g.fillRect(0, timeTop, screen.width, screen.height)
|
||||
}
|
||||
|
||||
let lastSeconds, tTick
|
||||
const tick = function () {
|
||||
g.reset()
|
||||
const date = new Date()
|
||||
const seconds = date.getSeconds()
|
||||
if (lastSeconds > seconds) {
|
||||
// new minute
|
||||
clearScreen()
|
||||
drawDateTime(date)
|
||||
}
|
||||
// the bar only gets larger, so drawing on top of the previous one is fine
|
||||
drawBar(date)
|
||||
lastSeconds = seconds
|
||||
// schedule next update
|
||||
const millis = date.getMilliseconds()
|
||||
tTick = setTimeout(tick, 1000-millis)
|
||||
}
|
||||
|
||||
const start = function () {
|
||||
lastSeconds = 99 // force redraw
|
||||
tick()
|
||||
}
|
||||
const stop = function () {
|
||||
if (tTick) {
|
||||
clearTimeout(tTick)
|
||||
tTick = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// clean app screen
|
||||
g.clear()
|
||||
Bangle.loadWidgets()
|
||||
Bangle.drawWidgets()
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
Bangle.on("lcdPower", function(on) {
|
||||
if (on) {
|
||||
start()
|
||||
} else {
|
||||
stop()
|
||||
draw();
|
||||
}
|
||||
})
|
||||
start()
|
||||
});
|
||||
g.reset().clear();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 7.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.3 KiB |
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Minor adjustment to fix out of memory errors
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ var scenes = [
|
|||
};
|
||||
},
|
||||
function() {
|
||||
Bangle.setLCDMode("120x120");
|
||||
var img = require("heatshrink").decompress(atob("oNBxH+5wA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHGpAAoQKv4ADCBQAeqsrAAejBw9/B4oABqt/IGepHw5CEQspALH5hBC5pAvv4/MAALFkIBWpPI6IHqpAu0Z3GfYOpRYdPQEhALYIp2FBYNVI4JAvvL4LH0yBYAFJAQQQ5Ay1JAFftBAQBYxCDv+qIGiCHIQiGnIBfOv5BJIQRAyIJkrvKEkIBrFBB4qEGIGRCNYsZAQIQV/IZDEiICRCDQVJAUIQVPC4lVIF6yJQYpAZ5t/FYvNIBepqtVIJGjIDoqBDY2pdYo3DfAhBIQLmpvIcDvIrC5oJEIAhTCGQmj5qgEC4t5e7YrBqt5BI6UFBg15v4XHbQwAQb4oAKv7NKABdVRoYATUAwnICqjZFIMdVE4+jXI4XGYCxBFFZN/M5OpCxUrvJ/ZFYmjvNVAAY+KCwpDBC6YAV5vNC9oA/AH4A/AHYA=="));
|
||||
|
||||
g.clear();
|
||||
y = 0;
|
||||
var step = 4;
|
||||
|
|
@ -70,8 +70,7 @@ var scenes = [
|
|||
g.clear();
|
||||
g.drawImage(img,60,60,{rotate:Math.sin(y*0.03)*0.5});
|
||||
g.flip();
|
||||
}, 20);
|
||||
Bangle.setLCDMode("120x120");
|
||||
}, 20);
|
||||
return function() {
|
||||
if (i) clearInterval(i);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: First Version
|
||||
0.02: Remove HID requirement, update screen
|
||||
0.03: Fix for Bangle 2, toggle find with top half of screen, exit touch bottom half of screen
|
||||
|
|
|
|||
|
|
@ -6,3 +6,8 @@ Ring your phone via GadgetBridge if you lost it somewhere.
|
|||
2. Lose phone
|
||||
3. Open app
|
||||
4. Click any button or screen
|
||||
|
||||
## On a Bangle 2
|
||||
|
||||
- You can touch the top half of the screen to toggle Find / Stop
|
||||
- You can touch the bottom half of the screen to exit the app.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
//notify your phone
|
||||
|
||||
const fontSize = g.getWidth() / 8;
|
||||
var finding = false;
|
||||
|
||||
function draw() {
|
||||
// show message
|
||||
g.clear(1);
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12",3);
|
||||
g.clear(g.theme.bg);
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector", fontSize);
|
||||
g.setFontAlign(0,0);
|
||||
|
||||
if (finding) {
|
||||
g.drawString("Finding...", g.getWidth()/2, (g.getHeight()/2)-20);
|
||||
g.drawString("Click to stop", g.getWidth()/2, (g.getHeight()/2)+20);
|
||||
|
|
@ -17,17 +19,37 @@ function draw() {
|
|||
g.flip();
|
||||
}
|
||||
|
||||
function findPhone(v) {
|
||||
Bluetooth.println(JSON.stringify({t:"findPhone", n:v}));
|
||||
}
|
||||
|
||||
function find(){
|
||||
finding = !finding;
|
||||
draw();
|
||||
Bluetooth.println("\n"+JSON.stringify({t:"findPhone", n:finding}));
|
||||
findPhone(finding);
|
||||
}
|
||||
|
||||
draw();
|
||||
|
||||
//register all buttons and screen to find phone
|
||||
setWatch(find, BTN1, {repeat:true});
|
||||
setWatch(find, BTN2, {repeat:true});
|
||||
setWatch(find, BTN3, {repeat:true});
|
||||
setWatch(find, BTN4, {repeat:true});
|
||||
setWatch(find, BTN5, {repeat:true});
|
||||
|
||||
if (process.env.HWVERSION == 1) {
|
||||
setWatch(find, BTN2, {repeat:true});
|
||||
setWatch(find, BTN3, {repeat:true});
|
||||
setWatch(find, BTN4, {repeat:true});
|
||||
setWatch(find, BTN5, {repeat:true});
|
||||
}
|
||||
|
||||
if (process.env.HWVERSION == 2) {
|
||||
Bangle.on('touch', function(button, xy) {
|
||||
|
||||
// click top part of the screen to stop start
|
||||
if (xy.y < g.getHeight() / 2) {
|
||||
find();
|
||||
} else {
|
||||
findPhone(false);
|
||||
setTimeout(load, 100); // exit in 100ms
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,3 +52,62 @@ Activity reporting
|
|||
You'll need a Gadgetbridge release *after* version 0.50.0 for Actvity Reporting to be enabled.
|
||||
|
||||
By default heart rate isn't reported, but it can be enabled from `Settings`, `App/Widget Settings`, `Gadgetbridge`, `Record HRM`
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. Switch to using one of the stock watch faces like s7clk or wave on a Bangle 2.
|
||||
|
||||
2. Check that the battery charge level is being seen by the Gadgetbridge App on the phone. This proves that data is getting to your phone.
|
||||
|
||||
### You can test the notifications on the Bangle
|
||||
|
||||
First disconnect from Gadgetbridge on your phone. Then connect
|
||||
through the IDE and enter the following code. You should get a pop
|
||||
up screen on your Bangle. This proves that the watch is correctly
|
||||
setup for notifications.
|
||||
|
||||
|
||||
GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"})
|
||||
|
||||
|
||||
NOTE: On a Bangle 2, this will fail if you have not installed 'Notifications Fullscreen'.
|
||||
|
||||
### Check that notifications are getting through to your Bangle
|
||||
|
||||
* Disconnect your Bangle from Gadgetbridge on your phone.
|
||||
* Connect through the IDE
|
||||
* Run the following bit of code
|
||||
|
||||
var log = [];
|
||||
function GB(d) {
|
||||
log.push(JSON.stringify(d));
|
||||
}
|
||||
|
||||
* Disconnect from the IDE
|
||||
* Connect your Bangle to Gadgetbridge
|
||||
* Call your phone to get a missed call
|
||||
* Disonnect your Bangle to Gadgetbridge
|
||||
* Connect through the IDE
|
||||
* Run the following bit of code
|
||||
|
||||
log;
|
||||
|
||||
If notifications are getting through then you should see something like.
|
||||
|
||||
|
||||
>log
|
||||
=[
|
||||
"{\"t\":\"call\",\"cmd\"" ... "r\":\"0191xxxxxxx\"}",
|
||||
"{\"t\":\"call\",\"cmd\"" ... "r\":\"0191xxxxxxx\"}"
|
||||
]
|
||||
|
||||
|
||||
IMPORTANT: Now reset your Bangle using a BTN3 long press so that the GB() function is restored.
|
||||
|
||||
## References
|
||||
|
||||
[Bangle Gadgetbridge Page](https://www.espruino.com/Gadgetbridge)
|
||||
|
||||
[Gadgetbridge Project Home](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Home)
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Refactor code to store grocery list in separate file
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
var filename = 'grocery_list.json';
|
||||
var settings = require("Storage").readJSON(filename,1)|| { products: [] };
|
||||
|
||||
function updateSettings() {
|
||||
require("Storage").writeJSON(filename, settings);
|
||||
Bangle.buzz();
|
||||
}
|
||||
|
||||
function twoChat(n){
|
||||
if(n<10) return '0'+n;
|
||||
return ''+n;
|
||||
}
|
||||
|
||||
const mainMenu = settings.products.reduce(function(m, p, i){
|
||||
const name = twoChat(p.quantity)+' '+p.name;
|
||||
m[name] = {
|
||||
value: p.ok,
|
||||
format: v => v?'[x]':'[ ]',
|
||||
onchange: v => {
|
||||
settings.products[i].ok = v;
|
||||
updateSettings();
|
||||
}
|
||||
};
|
||||
return m;
|
||||
}, {
|
||||
'': { 'title': 'Grocery list' }
|
||||
});
|
||||
mainMenu['< Back'] = ()=>{load();};
|
||||
E.showMenu(mainMenu);
|
||||
|
|
@ -105,56 +105,9 @@
|
|||
}
|
||||
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
|
||||
|
||||
var app = `
|
||||
var newTime = ${Date.now()}
|
||||
var products = ${JSON.stringify(products)}
|
||||
var newTime = newTime;
|
||||
var filename = 'grocery';
|
||||
var settings = require("Storage").readJSON(filename,1)|| null;
|
||||
function getSettings(){
|
||||
return {
|
||||
products : products,
|
||||
date: newTime
|
||||
};
|
||||
}
|
||||
if(!settings || !settings.date || settings.date < newTime){
|
||||
settings = getSettings();
|
||||
Bangle.buzz(500);
|
||||
}
|
||||
function updateSettings() {
|
||||
require("Storage").writeJSON(filename, settings);
|
||||
Bangle.buzz();
|
||||
}
|
||||
function twoChat(n){
|
||||
if(n<10) return '0'+n;
|
||||
return ''+n;
|
||||
}
|
||||
const mainMenu = settings.products.reduce(function(m, p, i){
|
||||
const name = twoChat(p.quantity)+' '+p.name;
|
||||
m[name] = {
|
||||
value: p.ok,
|
||||
format: v => v?'[x]':'[ ]',
|
||||
onchange: v => {
|
||||
settings.products[i].ok = v;
|
||||
updateSettings();
|
||||
}
|
||||
};
|
||||
return m;
|
||||
}, {
|
||||
'': { 'title': 'Grocery list' }
|
||||
});
|
||||
mainMenu['< Back'] = ()=>{load();};
|
||||
E.showMenu(mainMenu);
|
||||
`;
|
||||
|
||||
var icon = `require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQ0QACF1nGAAIxpFoYwqFwwwnRggwGB4eFAggACLzwHCMAeF1WGAgOGw2x2IGCLzYGEF4YpBwotCFwJfWFwo1GSAYtBAIIABRq4vFMhAwBzoAFdzIuKAAOc4IAGGC4qEMZOiF44wXFxovleBYvIGCwmB0WjE4V/AgfG1IvCzujFQOjwoECF6WFwovBDYOFEwN/AgIwCAgOFBwYrBBAQEBzodCF6AAHww1CBpIODAAYvRDAWG2IEBAYYJFBxICCF6Ox1WxAAQfBAYQlCAAIOJAQIvUADQvn1WGR4RfbP4gAFBwgFCF7a5EdwQADF46/cL9wAQF94AGF85bB1TvmF47vdJ4bvFF8qPRFgLv/L7jPCaQq/fYYrvgJgoAGd/7v/F/4v/F5oAdF54weFyAA/AH4A3A="))`;
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"grocery.app.js", url:"app.js", content:app},
|
||||
{name:"grocery.img", content:icon, evaluate:true},
|
||||
{name:"grocery"}
|
||||
{ name:"grocery_list.json", content: JSON.stringify({products: products}) }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Now displays widgets
|
||||
0.05: Use g.theme for colours
|
||||
0.06: Use Bangle.setUI for buttons
|
||||
0.07: Theme colours fix
|
||||
|
|
@ -33,8 +33,10 @@ function drawMenu() {
|
|||
if (i+menuScroll==selected) {
|
||||
g.setColor(g.theme.bgH).fillRect(0,y,w-1,y+63);
|
||||
g.setColor(g.theme.fgH).drawRect(0,y,w-1,y+63);
|
||||
} else
|
||||
g.clearRect(0,y,w-1,y+63);
|
||||
} else {
|
||||
g.clearRect(0, y, w-1, y+63);
|
||||
g.setColor(g.theme.fg);
|
||||
}
|
||||
g.drawString(app.name,64,y+32);
|
||||
var icon=undefined;
|
||||
if (app.icon) icon = s.read(app.icon);
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@
|
|||
0.09: Add onHide callback
|
||||
0.10: Ensure dismissing a notification dismissal doesn't enter launcher if in clock mode
|
||||
0.11: Improvements to help notifications work with themes, Bangle.js 2 support
|
||||
0.12: More use of themes, title now uses theme highlight colors, font adjusts
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
let oldg;
|
||||
let id = null;
|
||||
let hideCallback = null;
|
||||
|
||||
const titleFont = g.getWidth() / 8;
|
||||
/**
|
||||
* See notify/notify.js
|
||||
*/
|
||||
|
|
@ -63,8 +63,8 @@ exports.show = function(options) {
|
|||
// top bar
|
||||
if (options.title||options.src) {
|
||||
const title = options.title || options.src;
|
||||
g.setColor(options.titleBgColor||"#333").fillRect(x, y, x+w-1, y+30);
|
||||
g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 3);
|
||||
g.setColor(options.titleBgColor||g.theme.bgH).fillRect(x, y, x+w-1, y+30);
|
||||
g.setColor(g.theme.fgH).setFontAlign(-1, -1, 0).setFont("Vector", titleFont);
|
||||
g.drawString(title.trim().substring(0, 13), x+5, y+3);
|
||||
if (options.title && options.src) {
|
||||
g.setColor(g.theme.fg).setFontAlign(1, 1, 0).setFont("6x8", 2);
|
||||
|
|
|
|||
|
|
@ -32,3 +32,4 @@
|
|||
0.27: Add Theme menu
|
||||
0.28: Update Quiet Mode widget (if present)
|
||||
0.29: Add Customize to Theme menu
|
||||
0.30: Move '< Back' to the top of menus
|
||||
|
|
@ -75,6 +75,7 @@ function showMainMenu() {
|
|||
var beepN = ["Off", "Piezo", "Vibrate"];
|
||||
const mainmenu = {
|
||||
'': { 'title': 'Settings' },
|
||||
'< Back': ()=>load(),
|
||||
'Make Connectable': ()=>makeConnectable(),
|
||||
'App/Widget Settings': ()=>showAppSettingsMenu(),
|
||||
'BLE': ()=>showBLEMenu(),
|
||||
|
|
@ -117,7 +118,6 @@ function showMainMenu() {
|
|||
'Theme': ()=>showThemeMenu(),
|
||||
'Reset Settings': ()=>showResetMenu(),
|
||||
'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() },
|
||||
'< Back': ()=>load()
|
||||
};
|
||||
return E.showMenu(mainmenu);
|
||||
}
|
||||
|
|
@ -126,6 +126,7 @@ function showBLEMenu() {
|
|||
var hidV = [false, "kbmedia", "kb", "joy"];
|
||||
var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"];
|
||||
E.showMenu({
|
||||
'< Back': ()=>showMainMenu(),
|
||||
'BLE': {
|
||||
value: settings.ble,
|
||||
format: boolFormat,
|
||||
|
|
@ -158,8 +159,7 @@ function showBLEMenu() {
|
|||
'Whitelist': {
|
||||
value: settings.whitelist?(settings.whitelist.length+" devs"):"off",
|
||||
onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call
|
||||
},
|
||||
'< Back': ()=>showMainMenu()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -180,6 +180,7 @@ function showThemeMenu() {
|
|||
function cl(x) { return g.setColor(x).getColor(); }
|
||||
m = E.showMenu({
|
||||
'':{title:'Theme'},
|
||||
'< Back': ()=>showMainMenu(),
|
||||
'Dark BW': ()=>{
|
||||
updT({
|
||||
fg:cl("#fff"), bg:cl("#000"),
|
||||
|
|
@ -197,7 +198,6 @@ function showThemeMenu() {
|
|||
});
|
||||
},
|
||||
'Customize': ()=>showCustomThemeMenu(),
|
||||
'< Back': ()=>{delete m;showMainMenu();},
|
||||
});
|
||||
}
|
||||
function showCustomThemeMenu() {
|
||||
|
|
@ -255,6 +255,7 @@ function showCustomThemeMenu() {
|
|||
|
||||
function showPasskeyMenu() {
|
||||
var menu = {
|
||||
"< Back" : ()=>showBLEMenu(),
|
||||
"Disable" : () => {
|
||||
settings.passkey = undefined;
|
||||
updateSettings();
|
||||
|
|
@ -275,12 +276,12 @@ function showPasskeyMenu() {
|
|||
}
|
||||
};
|
||||
})(i);
|
||||
menu['< Back']=()=>showBLEMenu();
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showWhitelistMenu() {
|
||||
var menu = {
|
||||
"< Back" : ()=>showBLEMenu(),
|
||||
"Disable" : () => {
|
||||
settings.whitelist = undefined;
|
||||
updateSettings();
|
||||
|
|
@ -290,7 +291,7 @@ function showWhitelistMenu() {
|
|||
if (settings.whitelist) settings.whitelist.forEach(function(d){
|
||||
menu[d.substr(0,17)] = function() {
|
||||
E.showPrompt('Remove\n'+d).then((v) => {
|
||||
if (v) {
|
||||
if (v)
|
||||
settings.whitelist.splice(settings.whitelist.indexOf(d),1);
|
||||
updateSettings();
|
||||
}
|
||||
|
|
@ -312,7 +313,6 @@ function showWhitelistMenu() {
|
|||
showWhitelistMenu();
|
||||
});
|
||||
};
|
||||
menu['< Back']=()=>showBLEMenu();
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const spanishNumberStr = [ ["ZERO"], // 0
|
|||
["CUATRO",''], //4
|
||||
["CINCO",''], //5
|
||||
["SEIS",''], //6
|
||||
["SEITO",''], //7
|
||||
["SIETE",''], //7
|
||||
["OCHO",''], //8
|
||||
["NUEVE",''], // 9,
|
||||
["DIEZ",''], // 10
|
||||
|
|
@ -20,7 +20,7 @@ const spanishNumberStr = [ ["ZERO"], // 0
|
|||
["DIECI",'SIETE'], // 17
|
||||
["DIECI",'OCHO'], // 18
|
||||
["DIECI",'NEUVE'], // 19
|
||||
["VEINTA",''], // 20
|
||||
["VEINTE",''], // 20
|
||||
["VEINTI",'UNO'], // 21
|
||||
["VEINTI",'DOS'], // 22
|
||||
["VEINTI",'TRES'], // 23
|
||||
|
|
@ -74,4 +74,4 @@ class SpanishDateFormatter extends DateFormatter {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = SpanishDateFormatter;
|
||||
module.exports = SpanishDateFormatter;
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Load widgets after setUI so widclk knows when to hide
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function queueDraw() {
|
|||
function draw() {
|
||||
var x = g.getWidth()/2;
|
||||
var y = 24+20;
|
||||
|
||||
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
|
||||
if (g.getWidth() == IMAGEWIDTH)
|
||||
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
|
||||
|
|
@ -65,8 +65,8 @@ Bangle.on('lcdPower',on=>{
|
|||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
|
@ -4,4 +4,6 @@
|
|||
0.05: Add wind direction.
|
||||
0.06: Use setUI for launcher.
|
||||
0.07: Add theme support and unknown icon.
|
||||
0.08: Refactor and reduce widget ram usage.
|
||||
0.08: Refactor and reduce widget ram usage.
|
||||
0.09: Fix crash when weather.json is absent.
|
||||
0.10: Use new Layout library
|
||||
|
|
@ -1,96 +1,106 @@
|
|||
(() => {
|
||||
const weather = require('weather');
|
||||
let current = weather.get();
|
||||
const Layout = require('Layout');
|
||||
const locale = require('locale');
|
||||
const weather = require('weather');
|
||||
let current = weather.get();
|
||||
|
||||
function formatDuration(millis) {
|
||||
let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s");
|
||||
if (millis < 60000) return "< 1 minute";
|
||||
if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute");
|
||||
if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour");
|
||||
return pluralize(Math.floor(millis/86400000), "day");
|
||||
}
|
||||
Bangle.loadWidgets();
|
||||
|
||||
function draw() {
|
||||
g.reset();
|
||||
g.clearRect(0, 24, 239, 239);
|
||||
var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
|
||||
{filly: 1},
|
||||
{type: "h", filly: 0, c: [
|
||||
{type: "custom", width: g.getWidth()/2, height: g.getWidth()/2, valign: -1, txt: "unknown", id: "icon",
|
||||
render: l => weather.drawIcon(l.txt, l.x+l.w/2, l.y+l.h/2, l.w/2-5)},
|
||||
{type: "v", fillx: 1, c: [
|
||||
{type: "h", pad: 2, c: [
|
||||
{type: "txt", font: "18%", id: "temp", label: "000"},
|
||||
{type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"},
|
||||
]},
|
||||
{filly: 1},
|
||||
{type: "txt", font: "6x8", pad: 2, halign: 1, label: "Humidity"},
|
||||
{type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"},
|
||||
{filly: 1},
|
||||
{type: "txt", font: "6x8", pad: 2, halign: -1, label: "Wind"},
|
||||
{type: "h", halign: -1, c: [
|
||||
{type: "txt", font: "9%", pad: 2, id: "wind", label: "00"},
|
||||
{type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"},
|
||||
]},
|
||||
]},
|
||||
]},
|
||||
{filly: 1},
|
||||
{type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"},
|
||||
{filly: 1},
|
||||
{type: "h", c: [
|
||||
{type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"},
|
||||
{fillx: 1},
|
||||
{type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"},
|
||||
]},
|
||||
{filly: 1},
|
||||
]}, null, {lazy: true});
|
||||
|
||||
weather.drawIcon(current.txt, 65, 90, 55);
|
||||
const locale = require("locale");
|
||||
function formatDuration(millis) {
|
||||
let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s");
|
||||
if (millis < 60000) return "< 1 minute";
|
||||
if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute");
|
||||
if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour");
|
||||
return pluralize(Math.floor(millis/86400000), "day");
|
||||
}
|
||||
|
||||
g.reset();
|
||||
function draw() {
|
||||
layout.icon.txt = current.txt;
|
||||
const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||
layout.temp.label = temp[1];
|
||||
layout.tempUnit.label = temp[2];
|
||||
layout.hum.label = current.hum+"%";
|
||||
const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/);
|
||||
layout.wind.label = wind[1];
|
||||
layout.windUnit.label = wind[2] + " " + current.wrose.toUpperCase();
|
||||
layout.cond.label = current.txt.charAt(0).toUpperCase()+current.txt.slice(1);
|
||||
layout.loc.label = current.loc;
|
||||
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`;
|
||||
layout.update();
|
||||
layout.render();
|
||||
}
|
||||
|
||||
const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||
let width = g.setFont("Vector", 40).stringWidth(temp[1]);
|
||||
width += g.setFont("Vector", 20).stringWidth(temp[2]);
|
||||
g.setFont("Vector", 40).setFontAlign(-1, -1, 0);
|
||||
g.drawString(temp[1], 180-width/2, 70);
|
||||
g.setFont("Vector", 20).setFontAlign(1, -1, 0);
|
||||
g.drawString(temp[2], 180+width/2, 70);
|
||||
function drawUpdateTime() {
|
||||
if (!current || !current.time) return;
|
||||
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`;
|
||||
layout.update();
|
||||
layout.render();
|
||||
}
|
||||
|
||||
g.setFont("6x8", 1);
|
||||
g.setFontAlign(-1, 0, 0);
|
||||
g.drawString("Humidity", 135, 130);
|
||||
g.setFontAlign(1, 0, 0);
|
||||
g.drawString(current.hum+"%", 225, 130);
|
||||
if ('wind' in current) {
|
||||
g.setFontAlign(-1, 0, 0);
|
||||
g.drawString("Wind", 135, 142);
|
||||
g.setFontAlign(1, 0, 0);
|
||||
g.drawString(locale.speed(current.wind)+' '+current.wrose.toUpperCase(), 225, 142);
|
||||
}
|
||||
|
||||
g.setFont("6x8", 2).setFontAlign(0, 0, 0);
|
||||
g.drawString(current.loc, 120, 170);
|
||||
|
||||
g.setFont("6x8", 1).setFontAlign(0, 0, 0);
|
||||
g.drawString(current.txt.charAt(0).toUpperCase()+current.txt.slice(1), 120, 190);
|
||||
|
||||
drawUpdateTime();
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function drawUpdateTime() {
|
||||
if (!current || !current.time) return;
|
||||
let text = `Last update received ${formatDuration(Date.now() - current.time)} ago`;
|
||||
g.reset();
|
||||
g.clearRect(0, 202, 239, 210);
|
||||
g.setFont("6x8", 1).setFontAlign(0, 0, 0);
|
||||
g.drawString(text, 120, 206);
|
||||
}
|
||||
|
||||
function update() {
|
||||
current = weather.get();
|
||||
NRF.removeListener("connect", update);
|
||||
if (current) {
|
||||
draw();
|
||||
} else if (NRF.getSecurityStatus().connected) {
|
||||
E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?");
|
||||
function update() {
|
||||
current = weather.get();
|
||||
NRF.removeListener("connect", update);
|
||||
if (current) {
|
||||
draw();
|
||||
} else {
|
||||
layout.forgetLazyState();
|
||||
if (NRF.getSecurityStatus().connected) {
|
||||
E.showMessage("Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?");
|
||||
} else {
|
||||
E.showMessage("Weather unknown\n\nGadgetbridge\nnot connected");
|
||||
E.showMessage("Weather\nunknown\n\nGadgetbridge\nnot connected");
|
||||
NRF.on("connect", update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let interval = setInterval(drawUpdateTime, 60000);
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
}
|
||||
if (on) {
|
||||
drawUpdateTime();
|
||||
interval = setInterval(drawUpdateTime, 60000);
|
||||
}
|
||||
});
|
||||
let interval = setInterval(drawUpdateTime, 60000);
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
}
|
||||
if (on) {
|
||||
drawUpdateTime();
|
||||
interval = setInterval(drawUpdateTime, 60000);
|
||||
}
|
||||
});
|
||||
|
||||
weather.on("update", update);
|
||||
weather.on("update", update);
|
||||
|
||||
update();
|
||||
update();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
})()
|
||||
Bangle.drawWidgets();
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ global.GB = (event) => {
|
|||
};
|
||||
|
||||
exports.get = function() {
|
||||
return storage.readJSON('weather.json').weather;
|
||||
return (storage.readJSON('weather.json')||{}).weather;
|
||||
}
|
||||
|
||||
scheduleExpiry(storage.readJSON('weather.json')||{});
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.03: Ensure redrawing works with variable size widget system
|
||||
0.04: Fix regression stopping correct widget updates
|
||||
0.05: Don't show clock widget if already showing clock app
|
||||
0.06: Use 7 segment font, update *on* the minute, use less memory
|
||||
|
|
|
|||
|
|
@ -1,30 +1,16 @@
|
|||
(function() {
|
||||
// don't show widget if we know we have a clock app running
|
||||
if (Bangle.CLOCK) return;
|
||||
/* Simple clock that appears in the widget bar if no other clock
|
||||
is running. We update once per minute, but don't bother stopping
|
||||
if the */
|
||||
|
||||
let intervalRef = null;
|
||||
var width = 5 * 6*2
|
||||
|
||||
function draw() {
|
||||
g.reset().setFont("6x8", 2).setFontAlign(-1, 0);
|
||||
var time = require("locale").time(new Date(),1);
|
||||
g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60
|
||||
}
|
||||
function clearTimers(){
|
||||
if(intervalRef) {
|
||||
clearInterval(intervalRef);
|
||||
intervalRef = null;
|
||||
}
|
||||
}
|
||||
function startTimers(){
|
||||
intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
|
||||
WIDGETS["wdclk"].draw();
|
||||
}
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
clearTimers();
|
||||
if (on) startTimers();
|
||||
});
|
||||
|
||||
WIDGETS["wdclk"]={area:"tr",width:width,draw:draw};
|
||||
if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
|
||||
})()
|
||||
// don't show widget if we know we have a clock app running
|
||||
if (!Bangle.CLOCK) WIDGETS["wdclk"]={area:"tl",width:52/* g.stringWidth("00:00") */,draw:function() {
|
||||
g.reset().setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 512+9);
|
||||
var time = require("locale").time(new Date(),1);
|
||||
g.drawString(time, this.x, this.y+3, true); // 5 * 6*2 = 60
|
||||
// queue draw in one minute
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
this.drawTimeout = setTimeout(()=>{
|
||||
this.drawTimeout = undefined;
|
||||
this.draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}};
|
||||
|
|
|
|||
|
|
@ -11,16 +11,35 @@ var SETTINGS = {
|
|||
pretokenise : true
|
||||
};
|
||||
|
||||
var DEVICE = process.argv[2];
|
||||
|
||||
var path = require('path');
|
||||
var ROOTDIR = path.join(__dirname, '..');
|
||||
var APPDIR = ROOTDIR+'/apps';
|
||||
var APPJSON = ROOTDIR+'/apps.json';
|
||||
var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c');
|
||||
var APPS = [ // IDs of apps to install
|
||||
"boot","launchb2","s7clk","setting",
|
||||
"about","alarm","widlock","widbat","widbt"
|
||||
];
|
||||
var MINIFY = true;
|
||||
var OUTFILE, APPS;
|
||||
|
||||
if (DEVICE=="BANGLEJS") {
|
||||
var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs1_storage_default.c');
|
||||
var APPS = [ // IDs of apps to install
|
||||
"boot","launch","mclock","setting",
|
||||
"about","alarm","widbat","widbt","welcome"
|
||||
];
|
||||
} else if (DEVICE=="BANGLEJS2") {
|
||||
var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c');
|
||||
var APPS = [ // IDs of apps to install
|
||||
"boot","launchb2","s7clk","setting",
|
||||
"about","alarm","widlock","widbat","widbt"
|
||||
];
|
||||
} else {
|
||||
console.log("USAGE:");
|
||||
console.log(" bin/firmwaremaker_c.js BANGLEJS");
|
||||
console.log(" bin/firmwaremaker_c.js BANGLEJS2");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("Device = ",DEVICE);
|
||||
|
||||
|
||||
var fs = require("fs");
|
||||
global.Const = {
|
||||
|
|
@ -36,6 +36,8 @@ layoutObject has:
|
|||
* A `id` field. If specified the object is added with this name to the
|
||||
returned `layout` object, so can be referenced as `layout.foo`
|
||||
* A `font` field, eg `6x8` or `30%` to use a percentage of screen height
|
||||
* A `wrap` field to enable line wrapping. Requires some combination of `width`/`height`
|
||||
and `fillx`/`filly` to be set. Not compatible with text rotation.
|
||||
* A `col` field, eg `#f00` for red
|
||||
* A `bgCol` field for background color (will automatically fill on render)
|
||||
* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center
|
||||
|
|
@ -71,6 +73,7 @@ Other functions:
|
|||
* `layout.update()` - update positions of everything if contents have changed
|
||||
* `layout.debug(obj)` - draw outlines for objects on screen
|
||||
* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
|
||||
* `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render
|
||||
|
||||
*/
|
||||
|
||||
|
|
@ -138,7 +141,7 @@ function Layout(layout, buttons, options) {
|
|||
if (l.c) l.c.forEach(idRecurser);
|
||||
}
|
||||
idRecurser(layout);
|
||||
this.update();
|
||||
this.updateNeeded = true;
|
||||
}
|
||||
|
||||
Layout.prototype.remove = function (l) {
|
||||
|
|
@ -152,6 +155,24 @@ Layout.prototype.remove = function (l) {
|
|||
}
|
||||
};
|
||||
|
||||
function wrappedLines(str, maxWidth) {
|
||||
var lines = [];
|
||||
for (var unwrappedLine of str.split("\n")) {
|
||||
var words = unwrappedLine.split(" ");
|
||||
var line = words.shift();
|
||||
for (var word of words) {
|
||||
if (g.stringWidth(line + " " + word) > maxWidth) {
|
||||
lines.push(line);
|
||||
line = word;
|
||||
} else {
|
||||
line += " " + word;
|
||||
}
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) {
|
||||
var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol);
|
||||
if (bgCol != parentBg || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") {
|
||||
|
|
@ -176,6 +197,7 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) {
|
|||
|
||||
Layout.prototype.render = function (l) {
|
||||
if (!l) l = this._l;
|
||||
if (this.updateNeeded) this.update();
|
||||
|
||||
function render(l) {"ram"
|
||||
g.reset();
|
||||
|
|
@ -187,7 +209,14 @@ Layout.prototype.render = function (l) {
|
|||
var cb = {
|
||||
"":function(){},
|
||||
"txt":function(l){
|
||||
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1));
|
||||
if (l.wrap) {
|
||||
g.setFont(l.font,l.fsz).setFontAlign(0,-1);
|
||||
var lines = wrappedLines(l.label, l.w);
|
||||
var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1);
|
||||
lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i));
|
||||
} else {
|
||||
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1));
|
||||
}
|
||||
}, "btn":function(l){
|
||||
var x = l.x+(0|l.pad);
|
||||
var y = l.y+(0|l.pad);
|
||||
|
|
@ -230,6 +259,10 @@ Layout.prototype.render = function (l) {
|
|||
}
|
||||
};
|
||||
|
||||
Layout.prototype.forgetLazyState = function () {
|
||||
this.rects = {};
|
||||
}
|
||||
|
||||
Layout.prototype.layout = function (l) {
|
||||
// l = current layout element
|
||||
// exw,exh = extra width/height available
|
||||
|
|
@ -282,6 +315,7 @@ Layout.prototype.debug = function(l,c) {
|
|||
if (l.c) l.c.forEach(n => this.debug(n,c));
|
||||
};
|
||||
Layout.prototype.update = function() {
|
||||
delete this.updateNeeded;
|
||||
var l = this._l;
|
||||
var w = g.getWidth();
|
||||
var y = this.yOffset;
|
||||
|
|
@ -305,9 +339,13 @@ Layout.prototype.update = function() {
|
|||
l.font = f[0];
|
||||
l.fsz = f[1];
|
||||
}
|
||||
g.setFont(l.font,l.fsz);
|
||||
l._h = g.getFontHeight();
|
||||
l._w = g.stringWidth(l.label);
|
||||
if (l.wrap) {
|
||||
l._h = l._w = 0;
|
||||
} else {
|
||||
g.setFont(l.font,l.fsz);
|
||||
l._h = g.getFontHeight();
|
||||
l._w = g.stringWidth(l.label);
|
||||
}
|
||||
}, "btn": function(l) {
|
||||
l._h = 24;
|
||||
l._w = 14 + l.label.length*8;
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,7 @@
|
|||
var layout = new Layout({type:"v", c: [
|
||||
{type:"h", c: [
|
||||
{type:"txt", font:"10%", wrap: true, fillx: true, filly: true, label:"This is wrapping text that fills remaining space"},
|
||||
{type:"txt", font:"6x8", wrap: true, width: 60, filly: true, label:"This is wrapping text in a narrow column"},
|
||||
]},
|
||||
{type:"txt", font:"6x8", wrap: true, fillx: true, height: 20, label:"This doesn't need to wrap"},
|
||||
]});
|
||||
Loading…
Reference in New Issue