mirror of https://github.com/espruino/BangleApps
Merge branch 'espruino:master' into master
commit
f11f1a2538
|
@ -2,3 +2,4 @@
|
|||
0.02: Added settings page to force calendar sync
|
||||
0.03: Disable past events display from settings
|
||||
0.04: Added awareness of allDay field
|
||||
0.05: Displaying calendar colour and name
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
# Agenda
|
||||
|
||||
Basic agenda reading the events synchronised from GadgetBridge
|
||||
Basic agenda reading the events synchronised from GadgetBridge.
|
||||
|
||||
### Functionalities
|
||||
|
||||
* List all events in the next week (or whatever is synchronized)
|
||||
* Optionally view past events (until GB removes them)
|
||||
* Show start time and location of the events in the list
|
||||
* Show the colour of the calendar in the list
|
||||
* Display description, location and calendar name after tapping on events
|
||||
|
||||
### Report a bug
|
||||
|
||||
You can easily open an issue in the espruino repo, but I won't be notified and it might take time.
|
||||
If you want a (hopefully) quicker response, just report [on my fork](https://github.com/glemco/BangleApps).
|
||||
|
||||
### Known Problems
|
||||
|
||||
Any all-day event lasts just one day: that is a GB limitation that we will likely fix in the future.
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
title,
|
||||
description,
|
||||
location,
|
||||
color:int,
|
||||
calName,
|
||||
allDay: bool,
|
||||
}
|
||||
*/
|
||||
|
@ -73,6 +75,8 @@ function showEvent(ev) {
|
|||
lines = lines.concat(/*LANG*/"Location"+": ", g.wrapString(ev.location, g.getWidth()-10));
|
||||
if(ev.description)
|
||||
lines = lines.concat("",g.wrapString(ev.description, g.getWidth()-10));
|
||||
if(ev.calName)
|
||||
lines = lines.concat(/*LANG*/"Calendar"+": ", g.wrapString(ev.calName, g.getWidth()-10));
|
||||
lines = lines.concat(["",/*LANG*/"< Back"]);
|
||||
E.showScroller({
|
||||
h : g.getFontHeight(), // height of each menu item in pixels
|
||||
|
@ -116,17 +120,16 @@ function showList() {
|
|||
var body = formatDateShort(getDate(ev.timestamp),ev.allDay)+"\n"+(ev.location?ev.location:/*LANG*/"No location");
|
||||
if(settings.pastEvents) isPast = ev.timestamp + ev.durationInSeconds < (new Date())/1000;
|
||||
if (title) g.setFontAlign(-1,-1).setFont(fontBig)
|
||||
.setColor(isPast ? "#888" : g.theme.fg).drawString(title, x,r.y+2);
|
||||
.setColor(isPast ? "#888" : g.theme.fg).drawString(title, x+4,r.y+2);
|
||||
if (body) {
|
||||
g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
|
||||
var l = g.wrapString(body, r.w-(x+14));
|
||||
if (l.length>3) {
|
||||
l = l.slice(0,3);
|
||||
l[l.length-1]+="...";
|
||||
}
|
||||
g.drawString(l.join("\n"), x+10,r.y+20);
|
||||
g.drawString(body, x+10,r.y+20);
|
||||
}
|
||||
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
|
||||
if(ev.color) {
|
||||
g.setColor("#"+(0x1000000+Number(ev.color)).toString(16).padStart(6,"0"));
|
||||
g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4);
|
||||
}
|
||||
},
|
||||
select : idx => showEvent(CALENDAR[idx]),
|
||||
back : () => load()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
|
|
|
@ -17,4 +17,5 @@
|
|||
0.17: Fix - Step count was no more shown in the menu.
|
||||
0.18: Set timer for an agenda entry by simply clicking in the middle of the screen. Only one timer can be set.
|
||||
0.19: Fix - Compatibility with "Digital clock widget"
|
||||
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.
|
|
@ -198,6 +198,7 @@ function imgMountain() {
|
|||
var menu = [
|
||||
[
|
||||
function(){ return [ null, null ] },
|
||||
function(){ return [ "Week " + weekOfYear(), null ] },
|
||||
],
|
||||
[
|
||||
function(){ return [ "Bangle", imgWatch() ] },
|
||||
|
@ -423,6 +424,19 @@ function getWeather(){
|
|||
};
|
||||
}
|
||||
|
||||
// From https://weeknumber.com/how-to/javascript
|
||||
function weekOfYear() {
|
||||
var date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
// Thursday in current week decides the year.
|
||||
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
|
||||
// January 4 is always in week 1.
|
||||
var week1 = new Date(date.getFullYear(), 0, 4);
|
||||
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
|
||||
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
|
||||
- 3 + (week1.getDay() + 6) % 7) / 7);
|
||||
}
|
||||
|
||||
|
||||
function isAlarmEnabled(idx){
|
||||
try{
|
||||
|
@ -600,7 +614,7 @@ function drawTime(){
|
|||
}
|
||||
|
||||
var imgWidth = 0;
|
||||
if(menuImg !== undefined){
|
||||
if(menuImg){
|
||||
imgWidth = 24.0;
|
||||
var strWidth = g.stringWidth(menuText);
|
||||
var scale = imgWidth / menuImg.width;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.20",
|
||||
"version": "0.21",
|
||||
"description": "A very minimalistic clock to mainly show date and time.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -70,11 +70,21 @@ let alarm = require("sched").newDefaultAlarm();
|
|||
// Get a new timer with default values
|
||||
let timer = require("sched").newDefaultTimer();
|
||||
|
||||
// Add/update an existing alarm
|
||||
require("sched").setAlarm("mytimer", {
|
||||
// Add/update an existing alarm (using fields from the object shown above)
|
||||
require("sched").setAlarm("mytimer", { // as a timer
|
||||
msg : "Wake up",
|
||||
timer : 10 * 60 * 1000 // 10 minutes
|
||||
});
|
||||
require("sched").setAlarm("myalarm", { // as an alarm
|
||||
msg : "Wake up",
|
||||
t : 9 * 3600000 // 9 o'clock (in ms)
|
||||
});
|
||||
require("sched").setAlarm("mydayalarm", { // as an alarm on a date
|
||||
msg : "Wake up",
|
||||
date : "2022-04-04",
|
||||
t : 9 * 3600000 // 9 o'clock (in ms)
|
||||
});
|
||||
|
||||
// Ensure the widget and alarm timer updates to schedule the new alarm properly
|
||||
require("sched").reload();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env nodejs
|
||||
#!/usr/bin/env node
|
||||
/* Simple Command-line app loader for Node.js
|
||||
===============================================
|
||||
|
||||
|
@ -8,14 +8,13 @@ as a normal dependency) because we want `sanitycheck.js`
|
|||
to be able to run *quickly* in travis for every commit,
|
||||
and we don't want NPM pulling in (and compiling native modules)
|
||||
for Noble.
|
||||
|
||||
*/
|
||||
|
||||
var SETTINGS = {
|
||||
pretokenise : true
|
||||
};
|
||||
var APPSDIR = __dirname+"/../apps/";
|
||||
var Utils = require("../core/js/utils.js");
|
||||
var AppInfo = require("../core/js/appinfo.js");
|
||||
var noble;
|
||||
try {
|
||||
noble = require('@abandonware/noble');
|
||||
|
@ -28,15 +27,22 @@ if (!noble) {
|
|||
console.log(" npm install @abandonware/noble")
|
||||
console.log("or:")
|
||||
console.log(" npm install noble")
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var apps = [];
|
||||
|
||||
function ERROR(msg) {
|
||||
console.error(msg);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
//eval(require("fs").readFileSync(__dirname+"../core/js/utils.js"));
|
||||
var AppInfo = require("../core/js/appinfo.js");
|
||||
global.Const = {
|
||||
/* Are we only putting a single app on a device? If so
|
||||
apps should all be saved as .bootcde and we write info
|
||||
about the current app into app.info */
|
||||
SINGLE_APP_ONLY : false,
|
||||
};
|
||||
var deviceId = "BANGLEJS2";
|
||||
var apps = [];
|
||||
var dirs = require("fs").readdirSync(APPSDIR, {withFileTypes: true});
|
||||
dirs.forEach(dir => {
|
||||
|
@ -69,6 +75,10 @@ apploader.js list
|
|||
apploader.js devices
|
||||
- list available device addresses
|
||||
apploader.js install appname [de:vi:ce:ad:dr:es]
|
||||
|
||||
NOTE: Currently this App Loader expects the device it uploads to
|
||||
(deviceId) to be BANGLEJS2, so it won't work for Bangle.js 1 without
|
||||
modification.
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
@ -104,7 +114,10 @@ function cmdInstallApp(appId, deviceAddress) {
|
|||
fileGetter:function(url) {
|
||||
console.log(__dirname+"/"+url);
|
||||
return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary"));
|
||||
}, settings : SETTINGS}).then(files => {
|
||||
},
|
||||
settings : SETTINGS,
|
||||
device : { id : deviceId }
|
||||
}).then(files => {
|
||||
//console.log(files);
|
||||
var command = files.map(f=>f.cmd).join("\n")+"\n";
|
||||
bangleSend(command, deviceAddress).then(() => process.exit(0));
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
var exports = {};
|
||||
/* Module that allows for loading of clock 'info' displays
|
||||
that can be scrolled through on the clock face.
|
||||
|
||||
`load()` returns an array of menu items:
|
||||
|
||||
* 'item.name' : friendly name
|
||||
* 'item.get' : function that resolves with:
|
||||
{
|
||||
'text' : the text to display for this item
|
||||
'img' : a 24x24px image to display for this item
|
||||
}
|
||||
* 'item.show' : called when item should be shown. Enables updates. Call BEFORE 'get'
|
||||
* 'item.hide' : called when item should be hidden. Disables updates.
|
||||
* .on('redraw', ...) : event that is called when 'get' should be called again (only after 'item.show')
|
||||
* 'item.run' : (optional) called if the info screen is tapped - can perform some action
|
||||
|
||||
See the bottom of this file for example usage...
|
||||
|
||||
example.clkinfo.js :
|
||||
|
||||
(function() {
|
||||
return [
|
||||
{ name : "Example",
|
||||
get : () => ({ text : "Bangle.js",
|
||||
img : atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==") }),
|
||||
show : () => {},
|
||||
hide : () => {}
|
||||
}
|
||||
];
|
||||
}) // must not have a semi-colon!
|
||||
|
||||
*/
|
||||
|
||||
|
||||
exports.load = function() {
|
||||
// info used for drawing...
|
||||
var hrm = "--";
|
||||
var alt = "--";
|
||||
// callbacks (needed for easy removal of listeners)
|
||||
function batteryUpdateHandler() { items[0].emit("redraw"); }
|
||||
function stepUpdateHandler() { items[1].emit("redraw"); }
|
||||
function hrmUpdateHandler() { items[2].emit("redraw"); }
|
||||
function altUpdateHandler() {
|
||||
Bangle.getPressure().then(data=>{
|
||||
if (!data) return;
|
||||
alt = Math.round(data.altitude) + "m";
|
||||
items[3].emit("redraw");
|
||||
});
|
||||
}
|
||||
// actual items
|
||||
var items = [
|
||||
{ name : "Battery",
|
||||
get : () => ({
|
||||
text : E.getBattery() + "%",
|
||||
img : atob(Bangle.isCharging() ? "GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==" : "GBiBAAAAAAAAAAAAAAAAAAAAAD//+P///IAAAr//Ar//Ar//A7//A7//A7//A7//Ar//AoAAAv///D//+AAAAAAAAAAAAAAAAAAAAA==") }),
|
||||
show : function() {
|
||||
this.interval = setInterval(()=>this.emit('redraw'), 60000);
|
||||
Bangle.on("charging", batteryUpdateHandler);
|
||||
},
|
||||
hide : function() {
|
||||
clearInterval(this.interval);
|
||||
delete this.interval;
|
||||
Bangle.removeListener("charging", batteryUpdateHandler);
|
||||
},
|
||||
},
|
||||
{ name : "Steps", get : () => ({
|
||||
text : Bangle.getHealthStatus("day").steps,
|
||||
img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==") }),
|
||||
show : function() { Bangle.on("step", stepUpdateHandler); },
|
||||
hide : function() { Bangle.removeListener("step", stepUpdateHandler); },
|
||||
},
|
||||
{ name : "HRM", get : () => ({
|
||||
text : Math.round(Bangle.getHealthStatus("last").bpm) + " bpm",
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==") }),
|
||||
show : function() { Bangle.setHRMPower(1,"clkinfo"); Bangle.on("HRM", hrmUpdateHandler); hrm = Math.round(Bangle.getHealthStatus("last").bpm); },
|
||||
hide : function() { Bangle.setHRMPower(0,"clkinfo"); Bangle.removeListener("HRM", hrmUpdateHandler); hrm = "--"; },
|
||||
}
|
||||
];
|
||||
if (Bangle.getPressure) // Altimeter may not exist
|
||||
items.push({ name : "Altitude", get : () => ({
|
||||
text : alt,
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==") }),
|
||||
show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); },
|
||||
hide : function() { clearInterval(this.interval); delete this.interval; },
|
||||
});
|
||||
// now load extra data from a third party files
|
||||
require("Storage").list(/clkinfo.js$/).forEach(fn => {
|
||||
items = items.concat(eval(require("Storage").read(fn))());
|
||||
});
|
||||
// return it all!
|
||||
return items;
|
||||
};
|
||||
|
||||
|
||||
// Code for testing
|
||||
/*
|
||||
g.clear();
|
||||
var items = exports.load(); // or require("clock_info").load()
|
||||
items.forEach((itm,i) => {
|
||||
var y = i*24;
|
||||
console.log("Starting", itm.name);
|
||||
function draw() {
|
||||
var info = itm.get();
|
||||
g.reset().setFont("6x8:2").setFontAlign(-1,0);
|
||||
g.clearRect(0,y,g.getWidth(),y+23);
|
||||
g.drawImage(info.img, 0,y);
|
||||
g.drawString(info.text, 48,y+12);
|
||||
}
|
||||
itm.on('redraw', draw); // ensures we redraw when we need to
|
||||
itm.show();
|
||||
draw();
|
||||
});
|
||||
*/
|
|
@ -0,0 +1,20 @@
|
|||
/** Returns a promise that resolves with whether the Bangle
|
||||
is worn or not.
|
||||
|
||||
Usage:
|
||||
|
||||
require("wear_detect").isWorn().then(worn => {
|
||||
console.log(worn ? "is worn" : "not worn");
|
||||
});
|
||||
*/
|
||||
exports.isWorn = function() {
|
||||
return new Promise(resolve => {
|
||||
if (Bangle.isCharging())
|
||||
return resolve(false);
|
||||
if (E.getTemperature() > 24.625)
|
||||
return resolve(true);
|
||||
if (Bangle.getAccel().mag > 1.045)
|
||||
return resolve(true);
|
||||
return resolve(false);
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue