1
0
Fork 0

Merge branch 'espruino:master' into master

master
Noah Howard 2022-05-23 09:00:30 -04:00 committed by GitHub
commit fb73d65176
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
225 changed files with 6322 additions and 943 deletions

View File

@ -2,3 +2,4 @@
0.02: Fix the settings bug and some tweaking 0.02: Fix the settings bug and some tweaking
0.03: Do not alarm while charging 0.03: Do not alarm while charging
0.04: Obey system quiet mode 0.04: Obey system quiet mode
0.05: Battery optimisation, add the pause option, bug fixes

View File

@ -7,8 +7,8 @@ Different settings can be personalized:
- Enable : Enable/Disable the app - Enable : Enable/Disable the app
- Start hour: Hour to start the reminder - Start hour: Hour to start the reminder
- End hour: Hour to end the reminder - End hour: Hour to end the reminder
- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 60 min - Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 120 min
- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min - Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min
Notice: If Dissmiss delay > Max inactivity then it will be equal Max inactivity - Pause delay: Same as Dismiss delay but longer (usefull for meetings and such). From 30 to 240 min
- Min steps: Minimal amount of steps to count as an activity - Min steps: Minimal amount of steps to count as an activity

View File

@ -1,40 +1,42 @@
function drawAlert(){ function drawAlert() {
E.showPrompt("Inactivity detected",{ E.showPrompt("Inactivity detected", {
title:"Activity reminder", title: "Activity reminder",
buttons : {"Ok": true,"Dismiss": false} buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 }
}).then(function(v) { }).then(function (v) {
if(v == true){ if (v == 1) {
stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - 3); activityreminder_data.okDate = new Date();
require("activityreminder").saveStepsArray(stepsArray); }
} if (v == 2) {
if(v == false){ activityreminder_data.dismissDate = new Date();
stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - activityreminder.dismissDelayMin); }
require("activityreminder").saveStepsArray(stepsArray); if (v == 3) {
} activityreminder_data.pauseDate = new Date();
}
activityreminder.saveData(activityreminder_data);
load(); load();
}); });
// Obey system quiet mode: // Obey system quiet mode:
if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) { if (!(storage.readJSON('setting.json', 1) || {}).quiet) {
Bangle.buzz(400); Bangle.buzz(400);
} }
setTimeout(load, 20000); setTimeout(load, 20000);
} }
function run(){ function run() {
if(stepsArray.length == activityreminder.maxInnactivityMin){ if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) {
if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){ drawAlert();
drawAlert(); } else {
} eval(storage.read("activityreminder.settings.js"))(() => load());
}else{ }
eval(require("Storage").read("activityreminder.settings.js"))(()=>load());
}
} }
const activityreminder = require("activityreminder");
const storage = require("Storage");
g.clear(); g.clear();
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
activityreminder = require("activityreminder").loadSettings(); const activityreminder_settings = activityreminder.loadSettings();
stepsArray = require("activityreminder").loadStepsArray(); const activityreminder_data = activityreminder.loadData();
run(); run();

View File

@ -1,30 +1,45 @@
function run(){ function run() {
if (Bangle.isCharging()) return; if (isNotWorn()) return;
var now = new Date(); let now = new Date();
var h = now.getHours(); let h = now.getHours();
if(h >= activityreminder.startHour && h < activityreminder.endHour){ let health = Bangle.getHealthStatus("day");
var health = Bangle.getHealthStatus("day");
stepsArray.unshift(health.steps); if (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour) {
stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin); if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed
require("activityreminder").saveStepsArray(stepsArray); || health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch
} activityreminder_data.stepsOnDate = health.steps;
else{ activityreminder_data.stepsDate = now;
if(stepsArray != []){ activityreminder.saveData(activityreminder_data);
stepsArray = []; /* todo in a futur release
require("activityreminder").saveStepsArray(stepsArray); add settimer to trigger like 10 secs after the stepsDate + minSteps
cancel all other timers of this app
*/
} }
}
if(stepsArray.length >= activityreminder.maxInnactivityMin){ if(activityreminder.mustAlert(activityreminder_data, activityreminder_settings)){
if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){
load('activityreminder.app.js'); load('activityreminder.app.js');
} }
} }
} }
function isNotWorn() {
// todo in a futur release check temperature and mouvement in a futur release
return Bangle.isCharging();
}
activityreminder = require("activityreminder").loadSettings(); const activityreminder = require("activityreminder");
if(activityreminder.enabled) { const activityreminder_settings = activityreminder.loadSettings();
stepsArray = require("activityreminder").loadStepsArray(); if (activityreminder_settings.enabled) {
const activityreminder_data = activityreminder.loadData();
if(activityreminder_data.firstLoad){
activityreminder_data.firstLoad =false;
activityreminder.saveData(activityreminder_data);
}
setInterval(run, 60000); setInterval(run, 60000);
/* todo in a futur release
increase setInterval time to something that is still sensible (5 mins ?)
add settimer to trigger like 10 secs after the stepsDate + minSteps
cancel all other timers of this app
*/
} }

View File

@ -1,22 +1,57 @@
exports.loadSettings = function() { const storage = require("Storage");
exports.loadSettings = function () {
return Object.assign({ return Object.assign({
enabled: true, enabled: true,
startHour: 9, startHour: 9,
endHour: 20, endHour: 20,
maxInnactivityMin: 30, maxInnactivityMin: 30,
dismissDelayMin: 15, dismissDelayMin: 15,
pauseDelayMin: 120,
minSteps: 50 minSteps: 50
}, require("Storage").readJSON("activityreminder.s.json", true) || {}); }, storage.readJSON("activityreminder.s.json", true) || {});
}; };
exports.writeSettings = function(settings){ exports.writeSettings = function (settings) {
require("Storage").writeJSON("activityreminder.s.json", settings); storage.writeJSON("activityreminder.s.json", settings);
}; };
exports.saveStepsArray = function(stepsArray) { exports.saveData = function (data) {
require("Storage").writeJSON("activityreminder.sa.json", stepsArray); storage.writeJSON("activityreminder.data.json", data);
}; };
exports.loadStepsArray = function(){ exports.loadData = function () {
return require("Storage").readJSON("activityreminder.sa.json") || []; let health = Bangle.getHealthStatus("day");
}; const data = Object.assign({
firstLoad: true,
stepsDate: new Date(),
stepsOnDate: health.steps,
okDate: new Date(1970),
dismissDate: new Date(1970),
pauseDate: new Date(1970),
},
storage.readJSON("activityreminder.data.json") || {});
if(typeof(data.stepsDate) == "string")
data.stepsDate = new Date(data.stepsDate);
if(typeof(data.okDate) == "string")
data.okDate = new Date(data.okDate);
if(typeof(data.dismissDate) == "string")
data.dismissDate = new Date(data.dismissDate);
if(typeof(data.pauseDate) == "string")
data.pauseDate = new Date(data.pauseDate);
return data;
};
exports.mustAlert = function(activityreminder_data, activityreminder_settings) {
let now = new Date();
if ((now - activityreminder_data.stepsDate) / 60000 > activityreminder_settings.maxInnactivityMin) { // inactivity detected
if ((now - activityreminder_data.okDate) / 60000 > 3 && // last alert anwsered with ok was more than 3 min ago
(now - activityreminder_data.dismissDate) / 60000 > activityreminder_settings.dismissDelayMin && // last alert was more than dismissDelayMin ago
(now - activityreminder_data.pauseDate) / 60000 > activityreminder_settings.pauseDelayMin) { // last alert was more than pauseDelayMin ago
return true;
}
}
return false;
}

View File

@ -3,7 +3,7 @@
"name": "Activity Reminder", "name": "Activity Reminder",
"shortName":"Activity Reminder", "shortName":"Activity Reminder",
"description": "A reminder to take short walks for the ones with a sedentary lifestyle", "description": "A reminder to take short walks for the ones with a sedentary lifestyle",
"version":"0.04", "version":"0.05",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",
"tags": "tool,activity", "tags": "tool,activity",
@ -18,6 +18,6 @@
], ],
"data": [ "data": [
{"name": "activityreminder.s.json"}, {"name": "activityreminder.s.json"},
{"name": "activityreminder.sa.json"} {"name": "activityreminder.data.json"}
] ]
} }

View File

@ -1,64 +1,76 @@
(function(back) { (function (back) {
// Load settings // Load settings
var settings = require("activityreminder").loadSettings(); const activityreminder = require("activityreminder");
const settings = activityreminder.loadSettings();
// Show the menu // Show the menu
E.showMenu({ E.showMenu({
"" : { "title" : "Activity Reminder" }, "": { "title": "Activity Reminder" },
"< Back" : () => back(), "< Back": () => back(),
'Enable': { 'Enable': {
value: settings.enabled, value: settings.enabled,
format: v => v?"Yes":"No", format: v => v ? "Yes" : "No",
onchange: v => { onchange: v => {
settings.enabled = v; settings.enabled = v;
require("activityreminder").writeSettings(settings); activityreminder.writeSettings(settings);
} }
},
'Start hour': {
value: settings.startHour,
min: 0, max: 24,
onchange: v => {
settings.startHour = v;
activityreminder.writeSettings(settings);
}
},
'End hour': {
value: settings.endHour,
min: 0, max: 24,
onchange: v => {
settings.endHour = v;
activityreminder.writeSettings(settings);
}
},
'Max inactivity': {
value: settings.maxInnactivityMin,
min: 15, max: 120,
onchange: v => {
settings.maxInnactivityMin = v;
activityreminder.writeSettings(settings);
}, },
'Start hour': { format: x => {
value: settings.startHour, return x + " min";
min: 0, max: 24, }
onchange: v => { },
settings.startHour = v; 'Dismiss delay': {
require("activityreminder").writeSettings(settings); value: settings.dismissDelayMin,
} min: 5, max: 60,
}, onchange: v => {
'End hour': { settings.dismissDelayMin = v;
value: settings.endHour, activityreminder.writeSettings(settings);
min: 0, max: 24, },
onchange: v => { format: x => {
settings.endHour = v; return x + " min";
require("activityreminder").writeSettings(settings); }
} },
}, 'Pause delay': {
'Max inactivity': { value: settings.pauseDelayMin,
value: settings.maxInnactivityMin, min: 30, max: 240,
min: 15, max: 120, onchange: v => {
onchange: v => { settings.pauseDelayMin = v;
settings.maxInnactivityMin = v; activityreminder.writeSettings(settings);
require("activityreminder").writeSettings(settings); },
}, format: x => {
format: x => { return x + " min";
return x + " min"; }
} },
}, 'Min steps': {
'Dismiss delay': { value: settings.minSteps,
value: settings.dismissDelayMin, min: 10, max: 500,
min: 5, max: 60, onchange: v => {
onchange: v => { settings.minSteps = v;
settings.dismissDelayMin = v; activityreminder.writeSettings(settings);
require("activityreminder").writeSettings(settings); }
}, }
format: x => {
return x + " min";
}
},
'Min steps': {
value: settings.minSteps,
min: 10, max: 500,
onchange: v => {
settings.minSteps = v;
require("activityreminder").writeSettings(settings);
}
}
}); });
}) })

View File

@ -26,3 +26,5 @@
Add "Enable All", "Disable All" and "Remove All" actions Add "Enable All", "Disable All" and "Remove All" actions
0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu 0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu
0.26: Add support for Monday as first day of the week (#1780) 0.26: Add support for Monday as first day of the week (#1780)
0.27: New UI!
0.28: Fix bug with alarms not firing when configured to fire only once

View File

@ -1,7 +1,31 @@
Alarms & Timers # Alarms & Timers
===============
This app allows you to add/modify any alarms and timers. This app allows you to add/modify any alarms and timers.
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
to handle the alarm scheduling in an efficient way that can work alongside other apps.
## Menu overview
- `New...`
- `New Alarm` &rarr; Configure a new alarm
- `Repeat` &rarr; Select when the alarm will fire. You can select a predefined option (_Once_, _Every Day_, _Workdays_ or _Weekends_ or you can configure the days freely)
- `New Timer` &rarr; Configure a new timer
- `Advanced`
- `Scheduler settings` &rarr; Open the [Scheduler](https://github.com/espruino/BangleApps/tree/master/apps/sched) settings page, see its [README](https://github.com/espruino/BangleApps/blob/master/apps/sched/README.md) for details
- `Enable All` &rarr; Enable _all_ disabled alarms & timers
- `Disable All` &rarr; Disable _all_ enabled alarms & timers
- `Delete All` &rarr; Delete _all_ alarms & timers
## Creator
- [Gordon Williams](https://github.com/gfwilliams)
## Main Contributors
- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
- [storm64](https://github.com/storm64) - Fix redrawing in submenus
## Attributions
All icons used in this app are from [icons8](https://icons8.com).

View File

@ -1,20 +1,160 @@
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
// 0 = Sunday (default), 1 = Monday
const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0;
const WORKDAYS = 62
const WEEKEND = firstDayOfWeek ? 192 : 65;
const EVERY_DAY = firstDayOfWeek ? 254 : 127;
const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA==");
const iconAlarmOff = "\0" + (g.theme.dark
? atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=")
: atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="));
const iconTimerOn = "\0" + (g.theme.dark
? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=")
: atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="));
const iconTimerOff = "\0" + (g.theme.dark
? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=")
: atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="));
// An array of alarm objects (see sched/README.md) // An array of alarm objects (see sched/README.md)
var alarms = require("sched").getAlarms(); var alarms = require("sched").getAlarms();
// 0 = Sunday function handleFirstDayOfWeek(dow) {
// 1 = Monday if (firstDayOfWeek == 1) {
var firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0; if ((dow & 1) == 1) {
// In the scheduler API Sunday is 1.
// Here the week starts on Monday and Sunday is ON so
// when I read the dow I need to move Sunday to 128...
dow += 127;
} else if ((dow & 128) == 128) {
// ... and then when I write the dow I need to move Sunday back to 1.
dow -= 127;
}
}
return dow;
}
function getCurrentTime() { // Check the first day of week and update the dow field accordingly.
var time = new Date(); alarms.forEach(alarm => alarm.dow = handleFirstDayOfWeek(alarm.dow));
return (
time.getHours() * 3600000 + function showMainMenu() {
time.getMinutes() * 60000 + const menu = {
time.getSeconds() * 1000 "": { "title": /*LANG*/"Alarms & Timers" },
); "< Back": () => load(),
/*LANG*/"New...": () => showNewMenu()
};
alarms.forEach((e, index) => {
var label = e.timer
? require("time_utils").formatDuration(e.timer)
: require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
menu[label] = {
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
};
});
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
E.showMenu(menu);
}
function showNewMenu() {
E.showMenu({
"": { "title": /*LANG*/"New..." },
"< Back": () => showMainMenu(),
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
});
}
function showEditAlarmMenu(selectedAlarm, alarmIndex) {
var isNew = alarmIndex === undefined;
var alarm = require("sched").newDefaultAlarm();
alarm.dow = handleFirstDayOfWeek(alarm.dow);
if (selectedAlarm) {
Object.assign(alarm, selectedAlarm);
}
var time = require("time_utils").decodeTime(alarm.t);
const menu = {
"": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
"< Back": () => {
saveAlarm(alarm, alarmIndex, time);
showMainMenu();
},
/*LANG*/"Hour": {
value: time.h,
format: v => ("0" + v).substr(-2),
min: 0,
max: 23,
wrap: true,
onchange: v => time.h = v
},
/*LANG*/"Minute": {
value: time.m,
format: v => ("0" + v).substr(-2),
min: 0,
max: 59,
wrap: true,
onchange: v => time.m = v
},
/*LANG*/"Enabled": {
value: alarm.on,
onchange: v => alarm.on = v
},
/*LANG*/"Repeat": {
value: decodeDOW(alarm),
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
alarm.rp = repeat;
alarm.dow = dow;
alarm.t = require("time_utils").encodeTime(time);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
})
},
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
/*LANG*/"Auto Snooze": {
value: alarm.as,
onchange: v => alarm.as = v
},
/*LANG*/"Cancel": () => showMainMenu()
};
if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
if (confirm) {
alarms.splice(alarmIndex, 1);
saveAndReload();
showMainMenu();
} else {
alarm.t = require("time_utils").encodeTime(time);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
}
});
};
}
E.showMenu(menu);
}
function saveAlarm(alarm, alarmIndex, time) {
alarm.t = require("time_utils").encodeTime(time);
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
if (alarmIndex === undefined) {
alarms.push(alarm);
} else {
alarms[alarmIndex] = alarm;
}
saveAndReload();
} }
function saveAndReload() { function saveAndReload() {
@ -23,249 +163,196 @@ function saveAndReload() {
require("sched").setAlarms(alarms); require("sched").setAlarms(alarms);
require("sched").reload(); require("sched").reload();
// Fix after save
alarms.forEach(a => a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek));
} }
function showMainMenu() { function decodeDOW(alarm) {
// Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w") return alarm.rp
// Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA") ? require("date_utils")
const menu = { .dows(firstDayOfWeek, 2)
'': { 'title': /*LANG*/'Alarms&Timers' }, .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
/*LANG*/'< Back': () => { load(); }, .join("")
/*LANG*/'New Alarm': () => editAlarm(-1), .toLowerCase()
/*LANG*/'New Timer': () => editTimer(-1) : "Once"
}; }
alarms.forEach((alarm, idx) => {
alarm.dow = handleFirstDayOfWeek(alarm.dow, firstDayOfWeek);
var type, txt; // a leading space is currently required (JS error in Espruino 2v12) function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
if (alarm.timer) { var originalRepeat = repeat;
type = /*LANG*/"Timer"; var originalDow = dow;
txt = " " + require("sched").formatTime(alarm.timer); var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
} else {
type = /*LANG*/"Alarm"; const menu = {
txt = " " + require("sched").formatTime(alarm.t); "": { "title": /*LANG*/"Repeat Alarm" },
"< Back": () => dowChangeCallback(repeat, dow),
/*LANG*/"Once": {
// The alarm will fire once. Internally it will be saved
// as "fire every days" BUT the repeat flag is false so
// we avoid messing up with the scheduler.
value: !repeat,
onchange: () => dowChangeCallback(false, EVERY_DAY)
},
/*LANG*/"Workdays": {
value: repeat && dow == WORKDAYS,
onchange: () => dowChangeCallback(true, WORKDAYS)
},
/*LANG*/"Weekends": {
value: repeat && dow == WEEKEND,
onchange: () => dowChangeCallback(true, WEEKEND)
},
/*LANG*/"Every Day": {
value: repeat && dow == EVERY_DAY,
onchange: () => dowChangeCallback(true, EVERY_DAY)
},
/*LANG*/"Custom": {
value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow)
} }
if (alarm.rp) txt += "\0" + atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA="); };
// rename duplicate alarms
if (menu[type + txt]) {
var n = 2;
while (menu[type + " " + n + txt]) n++;
txt = type + " " + n + txt;
} else txt = type + txt;
// add to menu
menu[txt] = {
value: "\0" + atob(alarm.on ? "EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g" : "EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
onchange: function () {
setTimeout(alarm.timer ? editTimer : editAlarm, 10, idx, alarm);
}
};
});
if (alarms.some(e => !e.on)) { E.showMenu(menu);
menu[/*LANG*/"Enable All"] = () => enableAll(true);
}
if (alarms.some(e => e.on)) {
menu[/*LANG*/"Disable All"] = () => enableAll(false);
}
if (alarms.length > 0) {
menu[/*LANG*/"Delete All"] = () => deleteAll();
}
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
return E.showMenu(menu);
} }
function editDOW(dow, onchange) { function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) {
const menu = { const menu = {
'': { 'title': /*LANG*/'Days of Week' }, "": { "title": /*LANG*/"Custom Days" },
/*LANG*/'< Back': () => onchange(dow) "< Back": () => {
// If the user unchecks all the days then we assume repeat = once
// and we force the dow to every day.
var repeat = dow > 0;
dowChangeCallback(repeat, repeat ? dow : EVERY_DAY)
}
}; };
require("date_utils").dows(firstDayOfWeek).forEach((day, i) => { require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
menu[day] = { menu[day] = {
value: !!(dow & (1 << (i + firstDayOfWeek))), value: !!(dow & (1 << (i + firstDayOfWeek))),
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek))) onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek)))
}; };
}); });
menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback)
E.showMenu(menu); E.showMenu(menu);
} }
function editAlarm(alarmIndex, alarm) { function showEditTimerMenu(selectedTimer, timerIndex) {
var newAlarm = alarmIndex < 0; var isNew = timerIndex === undefined;
var a = require("sched").newDefaultAlarm();
a.dow = handleFirstDayOfWeek(a.dow, firstDayOfWeek);
if (!newAlarm) Object.assign(a, alarms[alarmIndex]); var timer = require("sched").newDefaultTimer();
if (alarm) Object.assign(a, alarm);
var t = require("sched").decodeTime(a.t); if (selectedTimer) {
Object.assign(timer, selectedTimer);
}
var time = require("time_utils").decodeTime(timer.timer);
const menu = { const menu = {
'': { 'title': /*LANG*/'Alarm' }, "": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
/*LANG*/'< Back': () => { "< Back": () => {
saveAlarm(newAlarm, alarmIndex, a, t); saveTimer(timer, timerIndex, time);
showMainMenu(); showMainMenu();
}, },
/*LANG*/'Hours': { /*LANG*/"Hours": {
value: t.hrs, min: 0, max: 23, wrap: true, value: time.h,
onchange: v => t.hrs = v min: 0,
max: 23,
wrap: true,
onchange: v => time.h = v
}, },
/*LANG*/'Minutes': { /*LANG*/"Minutes": {
value: t.mins, min: 0, max: 59, wrap: true, value: time.m,
onchange: v => t.mins = v min: 0,
max: 59,
wrap: true,
onchange: v => time.m = v
}, },
/*LANG*/'Enabled': { /*LANG*/"Enabled": {
value: a.on, value: timer.on,
format: v => v ? /*LANG*/"On" : /*LANG*/"Off", onchange: v => timer.on = v
onchange: v => a.on = v
}, },
/*LANG*/'Repeat': { /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v),
value: a.rp,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: v => a.rp = v
},
/*LANG*/'Days': {
value: decodeDOW(a.dow),
onchange: () => setTimeout(editDOW, 100, a.dow, d => {
a.dow = d;
a.t = require("sched").encodeTime(t);
editAlarm(alarmIndex, a);
})
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v),
/*LANG*/'Auto Snooze': {
value: a.as,
format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
onchange: v => a.as = v
}
}; };
menu[/*LANG*/"Cancel"] = () => showMainMenu(); if (!isNew) {
menu[/*LANG*/"Delete"] = () => {
if (!newAlarm) { E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
menu[/*LANG*/"Delete"] = function () { if (confirm) {
alarms.splice(alarmIndex, 1); alarms.splice(timerIndex, 1);
saveAndReload(); saveAndReload();
showMainMenu(); showMainMenu();
} else {
timer.timer = require("time_utils").encodeTime(time);
setTimeout(showEditTimerMenu, 10, timer, timerIndex)
}
});
}; };
} }
return E.showMenu(menu); E.showMenu(menu);
} }
function saveAlarm(newAlarm, alarmIndex, a, t) { function saveTimer(timer, timerIndex, time) {
a.t = require("sched").encodeTime(t); timer.timer = require("time_utils").encodeTime(time);
a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0; timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
timer.last = 0;
if (newAlarm) { if (timerIndex === undefined) {
alarms.push(a); alarms.push(timer);
} else { } else {
alarms[alarmIndex] = a; alarms[timerIndex] = timer;
} }
saveAndReload(); saveAndReload();
} }
function editTimer(alarmIndex, alarm) { function showAdvancedMenu() {
var newAlarm = alarmIndex < 0; E.showMenu({
var a = require("sched").newDefaultTimer(); "": { "title": /*LANG*/"Advanced" },
if (!newAlarm) Object.assign(a, alarms[alarmIndex]); "< Back": () => showMainMenu(),
if (alarm) Object.assign(a, alarm); /*LANG*/"Scheduler Settings": () => eval(require("Storage").read("sched.settings.js"))(() => showAdvancedMenu()),
var t = require("sched").decodeTime(a.timer); /*LANG*/"Enable All": () => enableAll(true),
/*LANG*/"Disable All": () => enableAll(false),
const menu = { /*LANG*/"Delete All": () => deleteAll()
'': { 'title': /*LANG*/'Timer' }, });
/*LANG*/'< Back': () => {
saveTimer(newAlarm, alarmIndex, a, t);
showMainMenu();
},
/*LANG*/'Hours': {
value: t.hrs, min: 0, max: 23, wrap: true,
onchange: v => t.hrs = v
},
/*LANG*/'Minutes': {
value: t.mins, min: 0, max: 59, wrap: true,
onchange: v => t.mins = v
},
/*LANG*/'Enabled': {
value: a.on,
format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
onchange: v => a.on = v
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate = v),
};
menu[/*LANG*/"Cancel"] = () => showMainMenu();
if (!newAlarm) {
menu[/*LANG*/"Delete"] = function () {
alarms.splice(alarmIndex, 1);
saveAndReload();
showMainMenu();
};
}
return E.showMenu(menu);
}
function saveTimer(newAlarm, alarmIndex, a, t) {
a.timer = require("sched").encodeTime(t);
a.t = getCurrentTime() + a.timer;
a.last = 0;
if (newAlarm) {
alarms.push(a);
} else {
alarms[alarmIndex] = a;
}
saveAndReload();
}
function handleFirstDayOfWeek(dow, firstDayOfWeek) {
if (firstDayOfWeek == 1) {
if ((dow & 1) == 1) {
// By default 1 = Sunday.
// Here the week starts on Monday and Sunday is ON so move Sunday to 128.
dow += 127;
} else if ((dow & 128) == 128) {
dow -= 127;
}
}
return dow;
}
function decodeDOW(dow) {
return require("date_utils")
.dows(firstDayOfWeek, 2)
.map((day, index) => dow & (1 << (index + firstDayOfWeek)) ? day : "_")
.join("");
} }
function enableAll(on) { function enableAll(on) {
E.showPrompt(/*LANG*/"Are you sure?", { if (alarms.filter(e => e.on == !on).length == 0) {
title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" E.showAlert(
}).then((confirm) => { on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable",
if (confirm) { on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
alarms.forEach(alarm => alarm.on = on); ).then(() => showAdvancedMenu());
saveAndReload(); } else {
} E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
if (confirm) {
showMainMenu(); alarms.forEach(alarm => alarm.on = on);
}); saveAndReload();
showMainMenu();
} else {
showAdvancedMenu();
}
});
}
} }
function deleteAll() { function deleteAll() {
E.showPrompt(/*LANG*/"Are you sure?", { if (alarms.length == 0) {
title: /*LANG*/"Delete All" E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu());
}).then((confirm) => { } else {
if (confirm) { E.showPrompt(/*LANG*/"Are you sure?", {
alarms = []; title: /*LANG*/"Delete All"
saveAndReload(); }).then((confirm) => {
} if (confirm) {
alarms = [];
showMainMenu(); saveAndReload();
}); showMainMenu();
} else {
showAdvancedMenu();
}
});
}
} }
showMainMenu(); showMainMenu();

View File

@ -2,16 +2,29 @@
"id": "alarm", "id": "alarm",
"name": "Alarms & Timers", "name": "Alarms & Timers",
"shortName": "Alarms", "shortName": "Alarms",
"version": "0.26", "version": "0.28",
"description": "Set alarms and timers on your Bangle", "description": "Set alarms and timers on your Bangle",
"icon": "app.png", "icon": "app.png",
"tags": "tool,alarm,widget", "tags": "tool,alarm,widget",
"supports": ["BANGLEJS","BANGLEJS2"], "supports": [ "BANGLEJS", "BANGLEJS2" ],
"readme": "README.md", "readme": "README.md",
"dependencies": {"scheduler":"type"}, "dependencies": { "scheduler":"type" },
"storage": [ "storage": [
{"name":"alarm.app.js","url":"app.js"}, { "name": "alarm.app.js", "url": "app.js" },
{"name":"alarm.img","url":"app-icon.js","evaluate":true}, { "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
{"name":"alarm.wid.js","url":"widget.js"} { "name": "alarm.wid.js", "url": "widget.js" }
],
"screenshots": [
{ "url": "screenshot-1.png" },
{ "url": "screenshot-2.png" },
{ "url": "screenshot-3.png" },
{ "url": "screenshot-4.png" },
{ "url": "screenshot-5.png" },
{ "url": "screenshot-6.png" },
{ "url": "screenshot-7.png" },
{ "url": "screenshot-8.png" },
{ "url": "screenshot-9.png" },
{ "url": "screenshot-10.png" },
{ "url": "screenshot-11.png" }
] ]
} }

BIN
apps/alarm/screenshot-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
apps/alarm/screenshot-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
apps/alarm/screenshot-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
apps/alarm/screenshot-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
apps/alarm/screenshot-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
apps/alarm/screenshot-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
apps/alarm/screenshot-7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
apps/alarm/screenshot-8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
apps/alarm/screenshot-9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -7,3 +7,4 @@
0.06: Option to keep messages after a disconnect (default false) (fix #1186) 0.06: Option to keep messages after a disconnect (default false) (fix #1186)
0.07: Include charging state in battery updates to phone 0.07: Include charging state in battery updates to phone
0.08: Handling of alarms 0.08: Handling of alarms
0.09: Alarm vibration, repeat, and auto-snooze now handled by sched

View File

@ -21,7 +21,6 @@ of Gadgetbridge - making your phone make noise so you can find it.
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js * `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
keep any messages it has received, or should it delete them? keep any messages it has received, or should it delete them?
* `Messages` - launches the messages app, showing a list of messages * `Messages` - launches the messages app, showing a list of messages
* `Alarms` - opens a submenu where you can set default settings for alarms such as vibration pattern, repeat, and auto snooze
## How it works ## How it works

View File

@ -67,17 +67,13 @@
var dow = event.d[j].rep; var dow = event.d[j].rep;
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0; var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
var a = { var a = require("sched").newDefaultAlarm();
id : "gb"+j, a.id = "gb"+j;
appid : "gbalarms", a.appid = "gbalarms";
on : true, a.on = true;
t : event.d[j].h * 3600000 + event.d[j].m * 60000, a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
dow : ((dow&63)<<1) | (dow>>6), // Gadgetbridge sends DOW in a different format a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
last : last, a.last = last;
rp : settings.rp,
as : settings.as,
vibrate : settings.vibrate
};
alarms.push(a); alarms.push(a);
} }
sched.setAlarms(alarms); sched.setAlarms(alarms);

View File

@ -2,7 +2,7 @@
"id": "android", "id": "android",
"name": "Android Integration", "name": "Android Integration",
"shortName": "Android", "shortName": "Android",
"version": "0.08", "version": "0.09",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge", "tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -25,27 +25,6 @@
} }
}, },
/*LANG*/"Messages" : ()=>load("messages.app.js"), /*LANG*/"Messages" : ()=>load("messages.app.js"),
/*LANG*/"Alarms" : () => E.showMenu({
"" : { "title" : /*LANG*/"Alarms" },
"< Back" : ()=>E.showMenu(mainmenu),
/*LANG*/"Vibrate": require("buzz_menu").pattern(settings.vibrate, v => {settings.vibrate = v; updateSettings();}),
/*LANG*/"Repeat": {
value: settings.rp,
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
onchange: v => {
settings.rp = v;
updateSettings();
}
},
/*LANG*/"Auto snooze": {
value: settings.as,
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
onchange: v => {
settings.as = v;
updateSettings();
}
},
})
}; };
E.showMenu(mainmenu); E.showMenu(mainmenu);
}) })

View File

@ -7,3 +7,5 @@
0.07: Update to use Bangle.setUI instead of setWatch 0.07: Update to use Bangle.setUI instead of setWatch
0.08: Use theme colors, Layout library 0.08: Use theme colors, Layout library
0.09: Fix time/date disappearing after fullscreen notification 0.09: Fix time/date disappearing after fullscreen notification
0.10: Use ClockFace library
0.11: Use ClockFace.is12Hour

View File

@ -3,7 +3,6 @@
* A simple digital clock showing seconds as a bar * A simple digital clock showing seconds as a bar
**/ **/
// Check settings for what type our clock should be // Check settings for what type our clock should be
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
let locale = require("locale"); let locale = require("locale");
{ // add some more info to locale { // add some more info to locale
let date = new Date(); let date = new Date();
@ -11,13 +10,9 @@ let locale = require("locale");
date.setMonth(1, 3); // februari: months are zero-indexed date.setMonth(1, 3); // februari: months are zero-indexed
const localized = locale.date(date, true); const localized = locale.date(date, true);
locale.dayFirst = /3.*2/.test(localized); locale.dayFirst = /3.*2/.test(localized);
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)!=="");
}
} }
Bangle.loadWidgets();
function renderBar(l) { function renderBar(l) {
if (!this.fraction) { if (!this.fraction) {
// zero-size fillRect stills draws one line of pixels, we don't want that // zero-size fillRect stills draws one line of pixels, we don't want that
@ -27,35 +22,9 @@ function renderBar(l) {
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1); g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
} }
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},
],
},
{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},
],
}, {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();
function timeText(date) { function timeText(date) {
if (!is12Hour) { if (!clock.is12Hour) {
return locale.time(date, true); return locale.time(date, true);
} }
const date12 = new Date(date.getTime()); const date12 = new Date(date.getTime());
@ -68,7 +37,7 @@ function timeText(date) {
return locale.time(date12, true); return locale.time(date12, true);
} }
function ampmText(date) { function ampmText(date) {
return (is12Hour && locale.hasMeridian)? locale.meridian(date) : ""; return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
} }
function dateText(date) { function dateText(date) {
const dayName = locale.dow(date, true), const dayName = locale.dow(date, true),
@ -78,31 +47,48 @@ function dateText(date) {
return `${dayName} ${dayMonth}`; return `${dayName} ${dayMonth}`;
} }
draw = function draw(force) {
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;
if (force) {
Bangle.drawWidgets();
layout.forgetLazyState();
}
layout.render();
// schedule update at start of next second
const millis = date.getMilliseconds();
setTimeout(draw, 1000-millis);
};
// Show launcher when button pressed const ClockFace = require("ClockFace"),
Bangle.setUI("clock"); clock = new ClockFace({
Bangle.on("lcdPower", function(on) { precision:1,
if (on) { init: function() {
draw(true); const Layout = require("Layout");
} this.layout = new Layout({
}); type: "v", c: [
g.reset().clear(); {
Bangle.drawWidgets(); type: "h", c: [
draw(); {id: "time", label: "88:88", type: "txt", font: "6x8:5", col:g.theme.fg, bgCol: g.theme.bg}, // size updated below
{id: "ampm", label: " ", type: "txt", font: "6x8:2", col:g.theme.fg, bgCol: g.theme.bg},
],
},
{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},
],
}, {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 (this.is12Hour) {
// Maximum font size = (<screen width> - <ampm: 2chars * (2*6)px>) / (5chars * 6px)
thickness = Math.floor((Bangle.appRect.w-24)/(5*6));
} else {
this.layout.ampm.label = "";
thickness = Math.floor(Bangle.appRect.w/(5*6));
}
this.layout.bar.height = thickness+1;
this.layout.time.font = "6x8:"+thickness;
this.layout.update();
},
update: function(date, c) {
if (c.m) this.layout.time.label = timeText(date);
if (c.h) this.layout.ampm.label = ampmText(date);
if (c.d) this.layout.date.label = dateText(date);
const SECONDS_PER_MINUTE = 60;
if (c.s) this.layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
this.layout.render();
},
resume: function() {
this.layout.forgetLazyState();
},
});
clock.start();

View File

@ -1,7 +1,7 @@
{ {
"id": "barclock", "id": "barclock",
"name": "Bar Clock", "name": "Bar Clock",
"version": "0.09", "version": "0.11",
"description": "A simple digital clock showing seconds as a bar", "description": "A simple digital clock showing seconds as a bar",
"icon": "clock-bar.png", "icon": "clock-bar.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],

View File

@ -51,3 +51,4 @@
0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk) 0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk)
0.46: Fix no clock found error on Bangle.js 2 0.46: Fix no clock found error on Bangle.js 2
0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed) 0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed)
0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks)

View File

@ -197,8 +197,18 @@ bootFiles.forEach(bootFile=>{
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset); require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
fileOffset+=2+bootFile.length+1; fileOffset+=2+bootFile.length+1;
var bf = require('Storage').read(bootFile); var bf = require('Storage').read(bootFile);
require('Storage').write('.boot0',bf,fileOffset); // we can't just write 'bf' in one go because at least in 2v13 and earlier
fileOffset+=bf.length; // Espruino wants to read the whole file into RAM first, and on Bangle.js 1
// it can be too big (especially BTHRM).
var bflen = bf.length;
var bfoffset = 0;
while (bflen) {
var bfchunk = Math.min(bflen, 2048);
require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
fileOffset+=bfchunk;
bfoffset+=bfchunk;
bflen-=bfchunk;
}
require('Storage').write('.boot0',";\n",fileOffset); require('Storage').write('.boot0',";\n",fileOffset);
fileOffset+=2; fileOffset+=2;
}); });

View File

@ -1,7 +1,7 @@
{ {
"id": "boot", "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"version": "0.47", "version": "0.48",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png", "icon": "bootloader.png",
"type": "bootloader", "type": "bootloader",

View File

@ -1,2 +1,3 @@
0.01: New App 0.01: New App
0.02: app keeps track of statistics now 0.02: app keeps track of statistics now
0.03: Fix bug in valid word detection

View File

@ -110,7 +110,12 @@ class Wordle {
} }
} }
addGuess(w) { addGuess(w) {
if ((this.words.indexOf(w.toLowerCase())%5)!=0) { let idx = -1;
do{
idx = this.words.indexOf(w.toLowerCase(), idx+1);
}
while(idx !== -1 && idx%5 !== 0);
if(idx%5 !== 0) {
E.showAlert(w+"\nis not a word", "Invalid word").then(function() { E.showAlert(w+"\nis not a word", "Invalid word").then(function() {
layout = getKeyLayout(""); layout = getKeyLayout("");
wordle.render(true); wordle.render(true);

View File

@ -2,7 +2,7 @@
"name": "Bordle", "name": "Bordle",
"shortName":"Bordle", "shortName":"Bordle",
"icon": "app.png", "icon": "app.png",
"version":"0.02", "version":"0.03",
"description": "Bangle version of a popular word search game", "description": "Bangle version of a popular word search game",
"supports" : ["BANGLEJS2"], "supports" : ["BANGLEJS2"],
"readme": "README.md", "readme": "README.md",

View File

@ -1,14 +1,18 @@
{ "id": "bowserWF", {
"id": "bowserWF",
"name": "Bowser Watchface", "name": "Bowser Watchface",
"shortName":"Bowser Watchface", "shortName":"Bowser Watchface",
"version":"0.01", "version":"0.02",
"description": "Let bowser show you the time", "description": "Let bowser show you the time",
"icon": "app.png", "icon": "app.png",
"tags": "", "type": "clock",
"supports" : ["BANGLEJS2"], "tags": "clock",
"supports" : ["BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"bowserWF.app.js","url":"app.js"}, {"name":"bowserWF.app.js","url":"app.js"},
{"name":"bowserWF.img","url":"app-icon.js","evaluate":true} {"name":"bowserWF.img","url":"app-icon.js","evaluate":true}
] ],
"data": [{"name":"bowserWF.json"}]
} }

View File

@ -3,4 +3,7 @@
0.03: Adapt colors based on the theme of the user. 0.03: Adapt colors based on the theme of the user.
0.04: Steps can be hidden now such that the time is even larger. 0.04: Steps can be hidden now such that the time is even larger.
0.05: Included icons for information. 0.05: Included icons for information.
0.06: Design and usability improvements. 0.06: Design and usability improvements.
0.07: Improved positioning.
0.08: Select the color of widgets correctly. Additional settings to hide colon.
0.09: Larger font size if colon is hidden to improve readability further.

View File

@ -8,6 +8,7 @@
- Enable / disable lock icon in the settings. - Enable / disable lock icon in the settings.
- If the "sched" app is installed tab top / bottom of the screen to set the timer. - If the "sched" app is installed tab top / bottom of the screen to set the timer.
- The design is adapted to the theme of your bangle. - The design is adapted to the theme of your bangle.
- The colon (e.g. 7:35 = 735) can be hidden now in the settings.
## Thanks to ## Thanks to
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a> <a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>

View File

@ -18,6 +18,7 @@ const H = g.getHeight();
let settings = { let settings = {
fullscreen: false, fullscreen: false,
showLock: true, showLock: true,
hideColon: false,
showInfo: 0, showInfo: 0,
}; };
@ -33,11 +34,25 @@ for (const key in saved_settings) {
// Manrope font // Manrope font
Graphics.prototype.setLargeFont = function(scale) { Graphics.prototype.setLargeFont = function(scale) {
// Actual height 49 (50 - 2) // Actual height 48 (49 - 2)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAfwAAAAAAAAf/AAAAAAAAf/8AAAAAAAf//wAAAAAAP///AAAAAAP///8AAAAAP////wAAAAP////4AAAAP////8AAAAH////8AAAAH////8AAAAB////8AAAAAH///+AAAAAAf//+AAAAAAB//+AAAAAAAH/+AAAAAAAAf+AAAAAAAAB/AAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///gAAAAAD////4AAAAA/////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAA///////4AAD/4AAAH/wAAP+AAAAP/AAB/wAAAAf8AAH/AAAAA/4AAf4AAAAB/gAB/gAAAAH+AAP8AAAAAf4AA/wAAAAB/gAD/AAAAAH+AAP8AAAAAf4AAf4AAAAB/gAB/gAAAAH+AAH+AAAAA/4AAf8AAAAH/AAB/4AAAA/8AAD/4AAAH/wAAP/8AAH/+AAAf//////4AAA///////AAAB//////4AAAD//////AAAAH/////4AAAAP////+AAAAAP////gAAAAAD///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAAB/wAAAAAAAAH/AAAAAAAAA/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAD/gAAAAAAAAP+AAAAAAAAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAP///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAfwAAAH8AAAD/AAAB/wAAAf8AAAP/AAAD/wAAB/8AAAf/AAAP/wAAD/8AAB//AAAf/wAAH/8AAD//AAA//gAAf/8AAD/wAAB//wAAf+AAAP//AAB/wAAB//8AAH+AAAP//wAAf4AAB///AAD/AAAP/v8AAP8AAB/8/wAA/wAAP/j/AAD/AAB/8P8AAH+AAH/g/wAAf4AA/8D/AAB/wAH/gP8AAH/AA/+A/wAAf/AP/wD/AAA//D/+AP8AAD////wA/wAAH///+AD/AAAP///wAP8AAAf//+AA/wAAA///wAD/AAAB//+AAP8AAAB//gAA/wAAAB/4AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAD+AAAAHwAAAf4AAAAfwAAB/gAAAB/gAAH+AAAAP/AAAf4AAAA/8AAB/gAAAD/4AAH+ADAAH/wAAf4AeAAP/AAB/gD+AAP8AAH+Af+AA/4AAf4D/4AB/gAB/gP/AAH+AAH+B/8AAf4AAf4P/wAB/gAB/h//AAH+AAH+P/8AAf4AAf5//wAB/gAB/v//gAP+AAH+//+AA/4AAf//f8AH/AAB//5/8B/8AAH//D////gAAf/4P///+AAB//Af///wAAH/4A///+AAAf/AB///wAAB/4AD//+AAAH/AAH//gAAAP4AAD/4AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAA/+AAAAAAAAP/4AAAAAAAH//gAAAAAAB//+AAAAAAAf//4AAAAAAH///gAAAAAB///+AAAAAAf///4AAAAAH//9/gAAAAD///H+AAAAA///wf4AAAAP//8B/gAAAD///AH+AAAA///wAf4AAAH//8AB/gAAAf//AAH+AAAB//gAAf4AAAH/4AAB/gAAAf+AAAH+AAAB/gAf///8AAH4AB////wAAeAAH////AABgAAf///8AAAAAB////wAAAAAH////AAAAAAf///8AAAAAB////wAAAAAH////AAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAeAAAAAf//AB+AAAH///+AP8AAAf///4A/4AAB////gD/wAAH////Af/gAAf///8B/+AAB////wB/8AAH///+AB/wAAf4Af4AD/gAB/gB/AAP+AAH+AP8AAf4AAf4A/wAB/gAB/gD+AAH+AAH+AP4AAf4AAf4A/gAB/gAB/gD/AAH+AAH+AP8AAf4AAf4A/wAD/gAB/gD/gAf8AAH+AH/AD/wAAf4Af/Af+AAB/gB////4AAH+AD////AAAf4AH///8AAB/gAP///gAAH+AA///8AAAAAAA///AAAAAAAB//4AAAAAAAB/+AAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///gAAAAAB////4AAAAAf////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAAf//////4AAD/4D/wH/wAAP+AP8AP/AAB/wB/gAf8AAH/AH8AA/4AAf4A/wAB/gAB/gD/AAH+AAH8AP4AAf4AA/wA/gAB/gAD/AD+AAH+AAH8AP8AAf4AAf4A/wAB/gAB/gD/AAP+AAH+AP+AB/wAAf8Af8AP/AAA/4B/8B/8AAD/gH////gAAP8AP///8AAAfgAf///wAAA8AB///+AAADgAD///wAAAAAAD//+AAAAAAAH//gAAAAAAAH/4AAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAAAH+AAAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAf4AAAAADgAB/gAAAAA+AAH+AAAAAf4AAf4AAAAH/gAB/gAAAD/+AAH+AAAA//4AAf4AAAf//gAB/gAAH//+AAH+AAD///wAAf4AA///8AAB/gAf//+AAAH+AH///gAAAf4D///wAAAB/g///8AAAAH+f//+AAAAAf////gAAAAB////wAAAAAH///8AAAAAAf//+AAAAAAB///gAAAAAAH//wAAAAAAAf/8AAAAAAAB/+AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAB/wB//wAAAAf/wf//gAAAD//z///AAAAf/////+AAAD//////8AAAf//////4AAD///////gAAP////w//AAB/+f/8Af8AAH/Af/gA/4AAf4A/8AD/gAB/gB/wAH+AAP8AH+AAf4AA/wAf4AB/gAD/AB/gAH+AAP8AH+AAf4AA/wAf4AB/gAB/gB/wAH+AAH+AP/AAf4AAf8A/+AD/gAB/8f/8Af8AAD////4H/wAAP//////+AAAf//////4AAA///////AAAD//////8AAAH//z///gAAAH/+H//4AAAAH/gH//AAAAAAAAH/wAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAP/wAAAAAAAD//wAAAAAAAf//wAAAAAAH///gABgAAA////AAPAAAD///+AB+AAAf///4AP4AAD////wB/wAAP/AP/AH/AAB/4Af+AP+AAH/AA/4A/4AAf4AB/gB/gAB/gAH+AH+AAP8AAP4Af4AA/wAA/gB/gAD/AAD+AH+AAP8AAP4Af4AA/4AB/gB/gAB/gAH+AH+AAH+AAfwA/4AAf8AD/AH/AAB/4Af4A/8AAD/4H/gP/wAAP//////+AAAf//////wAAA///////AAAB//////4AAAD//////AAAAH/////wAAAAH////+AAAAAH////AAAAAAAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ExwqHCYlJyYoIicoFg=="), 64+(scale<<8)+(1<<16)); this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('AFcH+AHFh/gA4sf4AHFn+AA4t/E43+AwsB/gHFgf4PH4AMgJ9Ngf/Pot//6bF/59F///PokfA4J9DEgIABEwYkB/7DDEgIlFCoRMDEgQsEDoRLEEgpoBA4JhGOIsHZ40PdwwA/L4SjHNAgGCP4cHA4wWDA4aVCA4gGDA4SNBe4IiBA4MPHYRBBEwScCA4d/EQUBaoRKDA4UBLQYECgb+EAgMHYYcHa4MPHoLBCBgMfYgcfBgM/PIc/BgN/A4YECIIQEDHwkDHwQHDGwQHENQUHA4d/QIQnCRIJJCSgYTCA4hqCA4hqCA4hiCA4ZCEA4RFBGYbrFAHxDGSohdDcgagFAAjPCEzicDToU/A4jPCAwbQCBwgrBgIHEFYKrDWoa7DaggA/AC0PAYV+AYSBCgKpCg4DDVIUfAYZ9BToIDDPoKVBAYfARoQDDXgMPFwTIBdYSYCv4LCv7zCXgYKCXAK8CHoUPXgY9Cn/vEYMPEwX/z46Bj4mBgf+n77CDwX4v54EIIIzCOgX/4I+CAQI9BHYQCCQ4I7CRASDBHYQHCv/Aj4+BGYIeBGAI+Bj/8AIIRBQIZjCRIiWBXgYHCPQgHBBgJ6DA4IEBPQaKBGYQ+BbgiCCAGZFDIIUBaAZBCgYHCQAQTBA4SACUwS8DDYQHBQAbVCQAYwBA4SABgYEBPoQCBFgU/CQWACgRDCHwKVCIYX+aYRDCHwMPAgY+Cn4EDHwX/AgY+B8bEFj/HA4RGCn+f94MBv45Cv+fA4J6C//+j5gBGIMBFoJWBQoRMB8E//4DBHIJcBv4HBEwJUCA4ImCj5MBA4KZCPYQHBZgRBCE4LICvwaCXAYA5PgQAEMIQAEUwQADQAJlCAARlBWYIACT4JtDAAMPA4IWESgg8CAwI+EEoPhHwYlCgY+DEoP4g4+DEoPAh4+CEoReBHwUfLYU/CwgMBXARqBHYQCCGoIjBgI+CgZSCHwcHAYY+Ch4lBJ4IbCjhACPwqUBPwqFCPwhQBIQZ+DOAKVFXooHCXop9DFAi8EFAT0GPoYAygwFEgOATISLDwBWDTQc/A4L6CTQKkCVQX+BYIHBDwX+BYIHBVQX8B4KqD+/wA4aBBj/AgK8CQIIJBA4a/BBIMBAgL/BAgUDYgL/BAII7BAQXgAII7BAQXAYQQxBYARrCMwQ0BAgV/HwYECHwgEBgY+EA4MPGwI8BA4UfGwI8BgYHBPofAQYOHPoeAR4QmBHwQHCEwI+CA4RVBHwQHCaggnBDwQHEHoIAEEQIA6v5NFfgSECBwZtEf4IHFOYQHEj4HGDwYHCDwPgv/jA4UHXQS8E/ED/AHDZ4MPSYKlCv+AYwIHDDwL7EgL7DAgTzCEwIpCeYTZBg4CBeYIJBAgICBFgIJBAgICBeYIEDHII0BAgg+EgI5CMocHGwJBCA4MfGwMD/h/BwF/PoQHC451CJIMDSgIjBA4PAA4QmBA4IhBA4JVBgEMA4bUDV4QeCAAf/HoIAENIIApOoIAEW4QAEW4QAEW4QAEWQRSFNIcDfYQMDny8DO4Q7BAQQjCewh+EHwcPToQ+Dv//ewkHUoI+En68DeIS0EHwMf/46CeYYlCHwQ0BKIY+BGgJ4Dh/nGgZZCAwKPEHYLpFDoKuFGgj4JgY0EHwQ0EYhIA6MAkf+BRBLIa5BQAJSCBgP4R4iVB/YHERoIACA4QGDE4SFBAoV/A4MH/ggBWIL7C8EfVoL4DwBHBFYIHBfYIRBAgT7CDgQEBgP4BgUBEIMDDgIMBgYMBg/gBgS5Ch/ABgUPFIMf4EHA4IEBHwUPCgJGCIIM/CgLgCAQJlBFIQFB44HBEIUBQYc/EIIHDAAIuBA4oeBRoSfBLAIHC/gHBEwIXC+AHBZghHBDwQADj4WCAHEPAwpWBKYYOCLwIHELYJUBghlDA4UcQogHBvgeDD4K0DDwIHBWgQeB4CyBh68CUAMf8DeCdIYHDdIfAfYjxCAgj2BAgbHCvwJCIIYCBBIMDHIX4BgUHFwMD+AMCA4Q0BAgg5CHwxICAQY5BdgQHBEgMDIYV/DgR1CA4PwP4KvDRgIACEYIHFWggABMQQHEZwd/Dwq1DHoTFEdooA/ACrBBcAZmC8DTCAATGBaYR+DwDTCRwbYDAASLBCIIGCFgQRBAG4='))),
46,
atob("EhooGyUkJiUnISYnFQ=="),
63+(scale<<8)+(1<<16)
);
return this; return this;
}; };
Graphics.prototype.setXLargeFont = function(scale) {
// Actual height 53 (55 - 3)
this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('AHM/8AIG/+AA4sD/wQGh/4EWQA/AC8YA40HNA0BRY8/RY0P/6LFgf//4iFA4IiFj4HBEQkHCAQiDHIIZGv4HCFQY5BDAo5CAAIpDDAfACA3wLYv//hsFKYxcCMgoiBOooiBQwwiBS40AHIgA/ACS/DLYjYCBAjQEBAYQDBAgHDUAbyDZQi3CegoHEVQQZFagUfW4Y0DaAgECaIJSEFYMPbIYNDv5ACGAIrBCgJ1EFYILCAAQWCj4zDGgILCegcDEQRNDHIIiCHgZ2BEQShFIqUDFYidCh5ODg4NCn40DAgd/AYR5BDILZEAAIMDAAYVCh7aHdYhKDbQg4Dv7rGBAihFCAwIDCAgA/AB3/eoa7GAAk/dgbVGDJrvCDK67DDIjaGdYpbCdYonCcQjjDEVUBEQ4A/AEMcAYV/NAUHcYUDawd/cYUPRYSmBBgaLBToP8BgYiBSgIiCj4iCg//EQSuDW4IMDVwYiCBgIiBBgrRDCATeBaIYqCv70DCgT4CEQMfIgQZBBoRnDv/3EQIvBDIffEQMHFwReBRYUfOgX/+IiDKIeHEQRRECwUHKwIuB8AiDIoJEBCwZFCv/4HIZaBIgPAEQS2CUYQiCD4SABEQcfOwIZBEQaHBO4RcEAAI/BEQQgBSIQiDTIRZBEQZuBVYQiDHoKWCEQQICFQIiDBAQeCEQQA/AANwA40BLIJ5BO4JWCBAUPAYR5En7RBUIQECN4SYCQQIiEh6CCEQk/BoQiBgYeCBoTrCAgT0CCgIfCFYQiBg4IBGgIiDj6rBg4rCBYLRDFYIiBbYIfBLgQiBIQYiD4JCCLgf/bQIWDBYV/EQV/BYXz/5FBgIiD5//IowZBD4M/NAX/BIPgDIJoC//5GgKUDn//4f/8KLE/wTBAAI8BEQPwj4HBVwYmBDgIZDN4QZCGYKJCHQP/JoSgCBATrCh5dBKITVDG4gICAAbvDAH5SCL4QADK4J5CCAiTCCAp1BCAqCDCAgiGCAIiFCAQiFeoIiFg6/FCAgiECAXnEQgQB/kfEQYQC4F/EQYQCgIiDfoIQBg4iDCAUAEQZUCcgIiDDIIQBEQhuBBoIiENoYiFDwQiECAQiFwEBPQQNCAQKDDEYMDDoMfRh4iGUwqvEESBiBaQ5oEbgr0FNAo+EEIwA+oAHGgJoFRAMHe4L0CAALNBBAT0BfwScDCAXweAL0DWgUPQYQiDwF/QYQiC/zTB+C0FBAL0CEQYIBGgMPCgIxBg4rCJIKsCh5IBBwTPCj4WBgYLBZ4V/MAIiBBQQrBEQYtCBYQiCO4QLFCwgiDIQIiGIoMHEQpFBn5FFD4JoENwRoGDgSUCAoKfBw//DgIiCT4auCFwN/T4RRET4TaCEQKoCDIQiCGgK/DAAQICdYQACHoIqCBAoQFEwIhFAH4AFQIROEj4IGXwIIGNwIACbgIhEBAiRCVwoqDTogHEW4QZFXgIZB/z9Cv49CF4MPBwI0Ca4LlB8ATCJoP4AoINDfQPAg7PBg4cBBwUfD4MfFYILCCwgOCf4QLEwEPCwILCgJaBn4WBBYQxCIQQiD+EDCYI5CBYRQBIo4fBMQIuBC4N/NAv8AoIcBSgU/FYIIBZIYrCW4hOCXIQZCgYUBv7jEh4uBZAscewZ8CgEgUYT0EEoQIBA4gICFQQIEHYQA+KQzdDAArdCAArpCEScHaIQiEvwiGe4QiFUwQiEbgIiFYIL0DEQTkBEQrJEEQc/cYYiCg4HBDIQiCfoRoEHQLaDEQQHBbQYiBCAT8Dn/BCAoXBJYP/OgZKC/6OEEARLCEQZLEEQZLEEQjKFEQI6EEQZLDEQbsGEQLjGYYYA/JIxzEg/AfgJSDAoPgfgiDC8COFAoPnaQj6CAAR+CW4TCFA4i6CDIqhCDIfwHoYHCYIN/GgKuBJ4JDBFYUf/C5CBYIZBv/Ag4ZBg4rBBYQTBAQIcBg4FBn5UBAQUfFwIfCEQeAgYfBAQUBFAKbCAQQiCGwIiE+A2BwBFNwE/AoM/EQJoIWwKCCh4cBFYKUERYV/W46uHFYIZGaJA0B/glBGYT0JIITiEMIJvCFQQAEHYQA/ABBlEOIhdGQAIRFSgQIBgQICn4IB8EAjiBCUYglCbQYeBEoQZCTwM/CYIZD/gEBUwIzBJ4UHYAU/EwIrBh4rCAoIXCn4rBCgUDAQN/FYMfBYIXBCYJnCBYXggf8HgQLCwEPEQQuBgJOECwILDCwgiLHIUHBYJFGD4IxBgYWCn4rBBwJoFDIYNBCgPADgKHBRYfDBQN/GAIrBToTLDVwYACDILiCWAb8DAAYzBYAjTCAAI9BAARNCBAoqCBAgQDFgbYCAH4AufgQACf4T8CAAT/CfgQACBwITCAAYOBCYQioh4iEAHQA=='))),
46,
atob("FR4uHyopKyksJSssGA=="),
70+(scale<<8)+(1<<16)
);
};
Graphics.prototype.setMediumFont = function(scale) { Graphics.prototype.setMediumFont = function(scale) {
// Actual height 41 (42 - 2) // Actual height 41 (42 - 2)
@ -259,11 +274,12 @@ function draw() {
function drawDate(){ function drawDate(){
// Draw background // Draw background
var y = H/5*2 + (settings.fullscreen ? 0 : 8); var y = H/5*2;
g.reset().clearRect(0,0,W,W); g.reset().clearRect(0,0,W,W);
// Draw date // Draw date
y -= settings.fullscreen ? 8 : 0; y = parseInt(y/2);
y += settings.fullscreen ? 2 : 15;
var date = new Date(); var date = new Date();
var dateStr = date.getDate(); var dateStr = date.getDate();
dateStr = ("0" + dateStr).substr(-2); dateStr = ("0" + dateStr).substr(-2);
@ -276,14 +292,14 @@ function drawDate(){
var dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr)); var dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr));
var fullDateW = dateW + 10 + dayW; var fullDateW = dateW + 10 + dayW;
g.setFontAlign(-1,1); g.setFontAlign(-1,0);
g.setMediumFont(); g.setMediumFont();
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
g.drawString(dateStr, W/2 - fullDateW / 2, y+5); g.drawString(dateStr, W/2 - fullDateW / 2, y+1);
g.setSmallFont(); g.setSmallFont();
g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+3); g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-23); g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
} }
@ -296,9 +312,16 @@ function drawTime(){
// Draw time // Draw time
g.setColor(g.theme.bg); g.setColor(g.theme.bg);
g.setFontAlign(0,-1); g.setFontAlign(0,0);
var timeStr = locale.time(date,1);
y += settings.fullscreen ? 14 : 10; var hours = String(date.getHours());
var minutes = date.getMinutes();
minutes = minutes < 10 ? String("0") + minutes : minutes;
var colon = settings.hideColon ? "" : ":";
var timeStr = hours + colon + minutes;
// Set y coordinates correctly
y += parseInt((H - y)/2) + 5;
var infoEntry = getInfoEntry(); var infoEntry = getInfoEntry();
var infoStr = infoEntry[0]; var infoStr = infoEntry[0];
@ -307,9 +330,13 @@ function drawTime(){
// Show large or small time depending on info entry // Show large or small time depending on info entry
if(infoStr == null){ if(infoStr == null){
y += 10; if(settings.hideColon){
g.setLargeFont(); g.setXLargeFont();
} else {
g.setLargeFont();
}
} else { } else {
y -= 15;
g.setMediumFont(); g.setMediumFont();
} }
g.drawString(timeStr, W/2, y); g.drawString(timeStr, W/2, y);
@ -319,7 +346,7 @@ function drawTime(){
return; return;
} }
y += H/5*2-5; y += 35;
g.setFontAlign(0,0); g.setFontAlign(0,0);
g.setSmallFont(); g.setSmallFont();
var imgWidth = 0; var imgWidth = 0;
@ -370,17 +397,6 @@ function queueDraw() {
} }
/*
* Load clock, widgets and listen for events
*/
Bangle.loadWidgets();
// Clear the screen once, at startup and set the correct theme.
var bgOrig = g.theme.bg
var fgOrig = g.theme.fg
g.setTheme({bg:fgOrig,fg:bgOrig}).clear();
draw();
// Stop updates when LCD is off, restart when on // Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{ Bangle.on('lcdPower',on=>{
if (on) { if (on) {
@ -446,5 +462,17 @@ E.on("kill", function(){
}); });
/*
* Draw clock the first time
*/
// The upper part is inverse i.e. light if dark and dark if light theme
// is enabled. In order to draw the widgets correctly, we invert the
// dark/light theme as well as the colors.
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
// Load widgets and draw clock the first time
Bangle.loadWidgets();
draw();
// Show launcher when middle button pressed // Show launcher when middle button pressed
Bangle.setUI("clock"); Bangle.setUI("clock");

View File

@ -1,7 +1,7 @@
{ {
"id": "bwclk", "id": "bwclk",
"name": "BW Clock", "name": "BW Clock",
"version": "0.06", "version": "0.09",
"description": "BW Clock.", "description": "BW Clock.",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -6,6 +6,7 @@
let settings = { let settings = {
fullscreen: false, fullscreen: false,
showLock: true, showLock: true,
hideColon: false,
}; };
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings; let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) { for (const key in saved_settings) {
@ -35,6 +36,14 @@
settings.showLock = !settings.showLock; settings.showLock = !settings.showLock;
save(); save();
}, },
},
'Hide Colon': {
value: settings.hideColon,
format: () => (settings.hideColon ? 'Yes' : 'No'),
onchange: () => {
settings.hideColon = !settings.hideColon;
save();
},
} }
}); });
}) })

View File

@ -5,3 +5,5 @@
0.05: Update calendar weekend colors for start on Sunday 0.05: Update calendar weekend colors for start on Sunday
0.06: Use larger font for dates 0.06: Use larger font for dates
0.07: Fix off-by-one-error on previous month 0.07: Fix off-by-one-error on previous month
0.08: Do not register as watch, manually start clock on button
read start of week from system settings

View File

@ -18,8 +18,7 @@ const blue = "#0000ff";
const yellow = "#ffff00"; const yellow = "#ffff00";
let settings = require('Storage').readJSON("calendar.json", true) || {}; let settings = require('Storage').readJSON("calendar.json", true) || {};
if (settings.startOnSun === undefined) let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0;
settings.startOnSun = false;
if (settings.ndColors === undefined) if (settings.ndColors === undefined)
if (process.env.HWVERSION == 2) { if (process.env.HWVERSION == 2) {
settings.ndColors = true; settings.ndColors = true;
@ -50,14 +49,14 @@ function getDowLbls(locale) {
case "de_AT": case "de_AT":
case "de_CH": case "de_CH":
case "de_DE": case "de_DE":
if (settings.startOnSun) { if (startOnSun) {
dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"]; dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
} else { } else {
dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
} }
break; break;
case "nl_NL": case "nl_NL":
if (settings.startOnSun) { if (startOnSun) {
dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"]; dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"];
} else { } else {
dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"]; dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"];
@ -66,14 +65,14 @@ function getDowLbls(locale) {
case "fr_BE": case "fr_BE":
case "fr_CH": case "fr_CH":
case "fr_FR": case "fr_FR":
if (settings.startOnSun) { if (startOnSun) {
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"]; dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
} else { } else {
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"]; dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
} }
break; break;
case "sv_SE": case "sv_SE":
if (settings.startOnSun) { if (startOnSun) {
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"]; dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
} else { } else {
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"]; dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
@ -81,21 +80,21 @@ function getDowLbls(locale) {
break; break;
case "it_CH": case "it_CH":
case "it_IT": case "it_IT":
if (settings.startOnSun) { if (startOnSun) {
dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"]; dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"];
} else { } else {
dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"]; dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
} }
break; break;
case "oc_FR": case "oc_FR":
if (settings.startOnSun) { if (startOnSun) {
dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"]; dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"];
} else { } else {
dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"]; dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"];
} }
break; break;
default: default:
if (settings.startOnSun) { if (startOnSun) {
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
} else { } else {
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
@ -110,7 +109,7 @@ function drawCalendar(date) {
g.clearRect(0, 0, maxX, maxY); g.clearRect(0, 0, maxX, maxY);
g.setBgColor(bgColorMonth); g.setBgColor(bgColorMonth);
g.clearRect(0, 0, maxX, headerH); g.clearRect(0, 0, maxX, headerH);
if (settings.startOnSun){ if (startOnSun){
g.setBgColor(bgColorWeekend); g.setBgColor(bgColorWeekend);
g.clearRect(0, headerH + rowH, colW, maxY); g.clearRect(0, headerH + rowH, colW, maxY);
g.setBgColor(bgColorDow); g.setBgColor(bgColorDow);
@ -150,7 +149,7 @@ function drawCalendar(date) {
}); });
date.setDate(1); date.setDate(1);
const dow = date.getDay() + (settings.startOnSun ? 1 : 0); const dow = date.getDay() + (startOnSun ? 1 : 0);
const dowNorm = dow === 0 ? 7 : dow; const dowNorm = dow === 0 ? 7 : dow;
const monthMaxDayMap = { const monthMaxDayMap = {
@ -242,5 +241,5 @@ Bangle.on("touch", area => {
}); });
// Show launcher when button pressed // Show launcher when button pressed
Bangle.setUI("clock"); // TODO: ideally don't set 'clock' mode setWatch(() => load(), process.env.HWVERSION === 2 ? BTN : BTN3, { repeat: false, edge: "falling" });
// No space for widgets! // No space for widgets!

View File

@ -1,7 +1,7 @@
{ {
"id": "calendar", "id": "calendar",
"name": "Calendar", "name": "Calendar",
"version": "0.07", "version": "0.08",
"description": "Simple calendar", "description": "Simple calendar",
"icon": "calendar.png", "icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}], "screenshots": [{"url":"screenshot_calendar.png"}],

View File

@ -1,8 +1,6 @@
(function (back) { (function (back) {
var FILE = "calendar.json"; var FILE = "calendar.json";
var settings = require('Storage').readJSON(FILE, true) || {}; var settings = require('Storage').readJSON(FILE, true) || {};
if (settings.startOnSun === undefined)
settings.startOnSun = false;
if (settings.ndColors === undefined) if (settings.ndColors === undefined)
if (process.env.HWVERSION == 2) { if (process.env.HWVERSION == 2) {
settings.ndColors = true; settings.ndColors = true;
@ -17,14 +15,6 @@
E.showMenu({ E.showMenu({
"": { "title": "Calendar" }, "": { "title": "Calendar" },
"< Back": () => back(), "< Back": () => back(),
'Start Sunday': {
value: settings.startOnSun,
format: v => v ? "Yes" : "No",
onchange: v => {
settings.startOnSun = v;
writeSettings();
}
},
'B2 Colors': { 'B2 Colors': {
value: settings.ndColors, value: settings.ndColors,
format: v => v ? "Yes" : "No", format: v => v ? "Yes" : "No",

View File

@ -0,0 +1,11 @@
# Banglejs - Touchscreen calibration
A simple calibration app for the touchscreen
## Usage
Once lauched touch the cross that appear on the screen to make
another spawn elsewhere.
each new touch on the screen will help to calibrate the offset
of your finger on the screen. After five or more input, press
the button to save the calibration and close the application.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkB/4AJ+EPBhQXg+BBDCyJaGGR5zIDBoQEL4QYOLYR3GBIouJR5AYBGBILBU5QMGFwgiFX4wwIEI4XGGBAgHd44+HD44XHNw4XWM5IIHCIoXWV5IXICQgXvLxAAKCYYXh5nMC6n8C4PPC5MAAA8PC4ZxBACAXOI653hU5zvJABASEC5PwHI4XcMBIXICIoXXJBAXHCAwXXJBAXHB5AfGC4ygJEAwXGQ5BoIQxoiDBYgXECwIuIBgb5ECIQJFGBQmCC4QHEDBwAFCxoYICx5ZELZoZJFiIXpA="))

85
apps/calibration/app.js Normal file
View File

@ -0,0 +1,85 @@
class BanglejsApp {
constructor() {
this.x = 0;
this.y = 0;
this.settings = {
xoffset: 0,
yoffset: 0,
};
}
load_settings() {
let settings = require('Storage').readJSON('calibration.json', true) || {active: false};
// do nothing if the calibration is deactivated
if (settings.active === true) {
// cancel the calibration offset
Bangle.on('touch', function(button, xy) {
xy.x += settings.xoffset;
xy.y += settings.yoffset;
});
}
if (!settings.xoffset) settings.xoffset = 0;
if (!settings.yoffset) settings.yoffset = 0;
console.log('loaded settings:');
console.log(settings);
return settings;
}
save_settings() {
this.settings.active = true;
this.settings.reload = false;
require('Storage').writeJSON('calibration.json', this.settings);
console.log('saved settings:');
console.log(this.settings);
}
explain() {
/*
* TODO:
* Present how to use the application
*
*/
}
drawTarget() {
this.x = 16 + Math.floor(Math.random() * (g.getWidth() - 32));
this.y = 40 + Math.floor(Math.random() * (g.getHeight() - 80));
g.clearRect(0, 24, g.getWidth(), g.getHeight() - 24);
g.drawLine(this.x, this.y - 5, this.x, this.y + 5);
g.drawLine(this.x - 5, this.y, this.x + 5, this.y);
g.setFont('Vector', 10);
g.drawString('current offset: ' + this.settings.xoffset + ', ' + this.settings.yoffset, 0, 24);
}
setOffset(xy) {
this.settings.xoffset = Math.round((this.settings.xoffset + (this.x - Math.floor((this.x + xy.x)/2)))/2);
this.settings.yoffset = Math.round((this.settings.yoffset + (this.y - Math.floor((this.y + xy.y)/2)))/2);
}
}
E.srand(Date.now());
Bangle.loadWidgets();
Bangle.drawWidgets();
calibration = new BanglejsApp();
calibration.load_settings();
let modes = {
mode : 'custom',
btn : function(n) {
calibration.save_settings(this.settings);
load();
},
touch : function(btn, xy) {
calibration.setOffset(xy);
calibration.drawTarget();
},
};
Bangle.setUI(modes);
calibration.drawTarget();

14
apps/calibration/boot.js Normal file
View File

@ -0,0 +1,14 @@
let cal_settings = require('Storage').readJSON("calibration.json", true) || {active: false};
Bangle.on('touch', function(button, xy) {
// do nothing if the calibration is deactivated
if (cal_settings.active === false) return;
// reload the calibration offset at each touch event /!\ bad for the flash memory
if (cal_settings.reload === true) {
cal_settings = require('Storage').readJSON("calibration.json", true);
}
// apply the calibration offset
xy.x += cal_settings.xoffset;
xy.y += cal_settings.yoffset;
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

View File

@ -0,0 +1,17 @@
{ "id": "calibration",
"name": "Touchscreen Calibration",
"shortName":"Calibration",
"icon": "calibration.png",
"version":"1.00",
"description": "A simple calibration app for the touchscreen",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"tags": "tool",
"storage": [
{"name":"calibration.app.js","url":"app.js"},
{"name":"calibration.boot.js","url":"boot.js"},
{"name":"calibration.settings.js","url":"settings.js"},
{"name":"calibration.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"calibration.json"}]
}

View File

@ -0,0 +1,23 @@
(function(back) {
var FILE = "calibration.json";
var settings = Object.assign({
active: true,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
E.showMenu({
"" : { "title" : "Calibration" },
"< Back" : () => back(),
'Active': {
value: !!settings.active,
format: v => v? "On":"Off",
onchange: v => {
settings.active = v;
writeSettings();
}
},
});
})

1
apps/diceroll/ChangeLog Normal file
View File

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

View File

@ -0,0 +1 @@
E.toArrayBuffer(atob("ICABAAAAAAAAAAAAAAAAAAHAAAAP8AAAfn4AA/APwA+DwfAPg8HwD+AH8Az4HzAMPnwwDAfgMAwBgDAMCYAwDA2YMAwhmDAMIZAwDCGDMA2BgzAMgYAwDAGAMA8BgPADwYPAAPGPgAB9ngAAH/gAAAfgAAABgAAAAAAAAAAAAAAAAAA="))

108
apps/diceroll/app.js Normal file
View File

@ -0,0 +1,108 @@
var init_message = true;
var acc_data;
var die_roll = 1;
var selected_die = 0;
var roll = 0;
const dices = [4, 6, 10, 12, 20];
g.setFontAlign(0,0);
Bangle.on('touch', function(button, xy) {
// Change die if not rolling
if(roll < 1){
if(selected_die <= 3){
selected_die++;
}else{
selected_die = 0;
}
}
//Disable initial message
init_message = false;
});
function rect(){
x1 = g.getWidth()/2 - 35;
x2 = g.getWidth()/2 + 35;
y1 = g.getHeight()/2 - 35;
y2 = g.getHeight()/2 + 35;
g.drawRect(x1, y1, x2, y2);
}
function pentagon(){
x1 = g.getWidth()/2;
y1 = g.getHeight()/2 - 50;
x2 = g.getWidth()/2 - 50;
y2 = g.getHeight()/2 - 10;
x3 = g.getWidth()/2 - 30;
y3 = g.getHeight()/2 + 30;
x4 = g.getWidth()/2 + 30;
y4 = g.getHeight()/2 + 30;
x5 = g.getWidth()/2 + 50;
y5 = g.getHeight()/2 - 10;
g.drawPoly([x1, y1, x2, y2, x3, y3, x4, y4, x5, y5], true);
}
function triangle(){
x1 = g.getWidth()/2;
y1 = g.getHeight()/2 - 57;
x2 = g.getWidth()/2 - 50;
y2 = g.getHeight()/2 + 23;
x3 = g.getWidth()/2 + 50;
y3 = g.getHeight()/2 + 23;
g.drawPoly([x1, y1, x2, y2, x3, y3], true);
}
function drawDie(variant) {
if(variant == 1){
//Rect, 6
rect();
}else if(variant == 3){
//Pentagon, 12
pentagon();
}else{
//Triangle, 4, 10, 20
triangle();
}
}
function initMessage(){
g.setFont("6x8", 2);
g.drawString("Dice-n-Roll", g.getWidth()/2, 20);
g.drawString("Shake to roll", g.getWidth()/2, 60);
g.drawString("Tap to change", g.getWidth()/2, 80);
g.drawString("Tap to start", g.getWidth()/2, 150);
}
function rollDie(){
acc_data = Bangle.getAccel();
if(acc_data.diff > 0.3){
roll = 3;
}
//Mange the die "roll" by chaning the number a few times
if(roll > 0){
g.drawString("Rolling!", g.getWidth()/2, 150);
die_roll = Math.abs(E.hwRand()) % dices[selected_die] + 1;
roll--;
}
//Draw dice graphics
drawDie(selected_die);
//Draw dice number
g.setFontAlign(0,0);
g.setFont("Vector", 45);
g.drawString(die_roll, g.getWidth()/2, g.getHeight()/2);
//Draw selected die in right corner
g.setFont("6x8", 2);
g.drawString(dices[selected_die], g.getWidth()-15, 15);
}
function main() {
g.clear();
if(init_message){
initMessage();
}else{
rollDie();
}
Bangle.setLCDPower(1);
}
var interval = setInterval(main, 300);

BIN
apps/diceroll/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,14 @@
{ "id": "diceroll",
"name": "Dice-n-Roll",
"shortName":"Dice-n-Roll",
"icon": "app.png",
"version":"0.01",
"description": "A dice app with a few different dice.",
"screenshots": [{"url":"diceroll_screenshot.png"}],
"tags": "game",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"diceroll.app.js","url":"app.js"},
{"name":"diceroll.img","url":"app-icon.js","evaluate":true}
]
}

17
apps/dinoClock/README.md Normal file
View File

@ -0,0 +1,17 @@
# dinoClock
Watchface with T-Rex Dinosaur from Chrome.
It displays current temperature and weather.
**Warning**: Element position and styles can change in the future.
Based on the [Weather Clock](https://github.com/espruino/BangleApps/tree/master/apps/weatherClock).
# Requirements
**This clock requires Gadgetbridge and the weather app in order to get weather data!**
See the [Bangle.js Gadgetbridge documentation](https://www.espruino.com/Gadgetbridge) for instructions on setting up Gadgetbridge and weather.
![Screenshot](screens/screen1.png)

219
apps/dinoClock/app.js Normal file
View File

@ -0,0 +1,219 @@
const storage = require('Storage');
const locale = require("locale");
// add modifiied 4x5 numeric font
(function(graphics) {
graphics.prototype.setFont4x5NumPretty = function() {
this.setFontCustom(atob("IQAQDJgH4/An4QXr0Fa/BwnwdrcH63BCHwfr8Ha/"),45,atob("AwIEBAQEBAQEBAQEBA=="),5);
};
})(Graphics);
// add font for days of the week
(function(graphics) {
graphics.prototype.setFontDoW = function() {
this.setFontCustom(atob("///////ADgB//////+AHAD//////gAAAH//////4D8B+A///////4AcAOAH//////4AcAOAAAAAB//////wA4AcAP//////wAAAAAAAA//////4AcAP//////wA4Af//////gAAAH//////5z85+c/OfnOAA4AcAOAH//////4AcAOAAAAAB//////wcAOAHB//////wAAAAAAAA///////ODnBzg5wc4AAAAD//////84OcH//8/+fAAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////84OcH//////AAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////gBwA///////AAAAAAAAAAAAA"),48,24,13);
};
})(Graphics);
const SUN = 1;
const PART_SUN = 2;
const CLOUD = 3;
const SNOW = 4;
const RAIN = 5;
const STORM = 6;
const ERR = 7;
/**
Choose weather icon based on weather const
Weather icons from https://icons8.com/icon/set/weather/ios-glyphs
Error icon from https://icons8.com/icon/set/error-cloud/ios-glyphs
**/
function weatherIcon(weather) {
switch (weather) {
case SUN:
return atob("Hh4BAAAAAAAMAAAAMAAAAMAAAAMAABgMBgBwADgA4AHAAY/GAAB/gAAD/wAAH/4AAP/8AAP/8AfP/8+fP/8+AP/8AAP/8AAH/4AAD/wAAB/gAAY/GAA4AHABwADgBgMBgAAMAAAAMAAAAMAAAAMAAAAAAAA=");
case PART_SUN:
return atob("Hh4BAAAAAAAAAAAMAAAAMAAAEMIAAOAcAAGAYAAAeAAAA/AAAB/gAA5/gAA5/g+AB+D/gA4H/wAR//wGD//4OD//4EH//4AH//4Af//+Af//+A////A////A////A///+Af//+AH//4AAAAAAAAAAAAAAAA=");
case CLOUD:
return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB///wD///wD///wP///8f///+f///+////////////////////f///+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA=");
case SNOW:
return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf8/AA/8/AB/gHgH/wP4H/wP4P/gH8P/8/8P/8/8P///4H///4B///gAAAAAAMAAAAMAAAB/gGAA/AfgA/AfgB/gfgAMAfgAMAGAAAAAAAAAAAA=");
case RAIN:
return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H///4P///8P///8P///8P///4H///4B///gAAAAAAAAAABgBgABgBgABhhhgABgBgABgBgAAAAAAAAAAAAAAAAAAAAA=");
case STORM:
return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H/x/4P/g/8P/k/8P/E/8P/M/4H+MP4B+cHgAAfgAAA/gABg/AABgHAABgGBgAAGBgAAEBgAAEAAAAAAAAAAAAAAAAAA=");
case ERR:
default:
return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB/z/wD/z/wD/z/wP/z/8f/z/+f/z/+//z//////////////z//f/z/+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA=");
}
}
/**
Choose weather icon to display based on condition.
Based on function from the Bangle weather app so it should handle all of the conditions
sent from gadget bridge.
*/
function chooseIcon(condition) {
condition = condition.toLowerCase();
if (condition.includes("thunderstorm")) return weatherIcon(STORM);
if (condition.includes("freezing")||condition.includes("snow")||
condition.includes("sleet")) {
return weatherIcon(SNOW);
}
if (condition.includes("drizzle")||
condition.includes("shower")) {
return weatherIcon(RAIN);
}
if (condition.includes("rain")) return weatherIcon(RAIN);
if (condition.includes("clear")) return weatherIcon(SUN);
if (condition.includes("few clouds")) return weatherIcon(PART_SUN);
if (condition.includes("scattered clouds")) return weatherIcon(CLOUD);
if (condition.includes("clouds")) return weatherIcon(CLOUD);
if (condition.includes("mist") ||
condition.includes("smoke") ||
condition.includes("haze") ||
condition.includes("sand") ||
condition.includes("dust") ||
condition.includes("fog") ||
condition.includes("ash") ||
condition.includes("squalls") ||
condition.includes("tornado")) {
return weatherIcon(CLOUD);
}
return weatherIcon(CLOUD);
}
/*
* Choose weather icon to display based on weather conditition code
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
*/
function chooseIconByCode(code) {
const codeGroup = Math.round(code / 100);
switch (codeGroup) {
case 2: return weatherIcon(STORM);
case 3: return weatherIcon(RAIN);
case 5: return weatherIcon(RAIN);
case 6: return weatherIcon(SNOW);
case 7: return weatherIcon(CLOUD);
case 8:
switch (code) {
case 800: return weatherIcon(SUN);
case 801: return weatherIcon(PART_SUN);
default: return weatherIcon(CLOUD);
}
default: return weatherIcon(CLOUD);
}
}
/**
Get weather stored in json file by weather app.
*/
function getWeather() {
let jsonWeather = storage.readJSON('weather.json');
return jsonWeather;
}
// timeout used to update every minute
var drawTimeout;
// schedule a draw for the next minute
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
},60000-(Date.now()%60000));
}
// only draw the first time
function drawBg() {
var bgImg = require("heatshrink").decompress(atob("2E7wINKn///+AEaIVUgIUB//wCs/5CtRXrCvMD8AVTg4LFCv4VZ/iSLCrwWMCrMOAQMPCp7cBCojjFCo/xFgIVQgeHCopABCpcH44Vuh/AQQX/wAV7+F/Cq/nCsw/CCqyvRCvgODCqfAgEDCp4QCSIIVQgIOBDQgGDABX/NgIECCp8HCrM/CgP4CqKaCCqSfCCqq1BCqBuB54VqgYVG/gCECp0BwgCDCp8HgYCDCo/wCo0MgHAjACBj7rDABS1Bv4lBv4rPAAsPCo3+gbbPJAIVFiAXMFZ2AUQsAuAQHiOAgJeEA"));
g.reset();
g.drawImage(bgImg,0,101);
}
function square(x,y,w,e) {
g.setColor("#000").fillRect(x,y,x+w,y+w);
g.setColor("#fff").fillRect(x+e,y+e,x+w-e,y+w-e);
}
function draw() {
var d = new Date();
var h = d.getHours(), m = d.getMinutes();
h = ("0"+h).substr(-2);
m = ("0"+m).substr(-2);
var day = d.getDate(), mon = d.getMonth(), dow = d.getDay();
day = ("0"+day).substr(-2);
mon = ("0"+(mon+1)).substr(-2);
dow = ((dow+6)%7).toString();
date = day+"."+mon;
var weatherJson = getWeather();
var wIcon;
var temp;
if(weatherJson && weatherJson.weather){
var currentWeather = weatherJson.weather;
temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/);
const code = currentWeather.code||-1;
if (code > 0) {
wIcon = chooseIconByCode(code);
} else {
wIcon = chooseIcon(currentWeather.txt);
}
}else{
temp = "";
wIcon = weatherIcon(ERR);
}
g.reset();
g.clearRect(22,35,153,75);
g.setFont("4x5NumPretty",8);
g.fillRect(84,42,92,49);
g.fillRect(84,60,92,67);
g.drawString(h,22,35);
g.drawString(m,98,35);
g.clearRect(22,95,22+4*2*4+2*4,95+2*5);
g.setFont("4x5NumPretty",2);
g.drawString(date,22,95);
g.clearRect(22,79,22+24,79+13);
g.setFont("DoW");
g.drawString(dow,22,79);
g.drawImage(wIcon,126,81);
g.clearRect(108,114,176,114+4*5);
if (temp != "") {
var tempWidth;
const mid=126+15;
if (temp[1][0]=="-") {
// do not account for - when aligning
const minusWidth=3*4;
tempWidth = minusWidth+(temp[1].length-1)*4*4;
x = mid-Math.round((tempWidth-minusWidth)/2)-minusWidth;
} else {
tempWidth = temp[1].length*4*4;
x = mid-Math.round(tempWidth/2);
}
g.setFont("4x5NumPretty",4);
g.drawString(temp[1],x,114);
square(x+tempWidth,114,6,2);
}
// queue draw in one minute
queueDraw();
}
g.clear();
drawBg();
Bangle.setUI("clock"); // Show launcher when middle button pressed
Bangle.loadWidgets();
Bangle.drawWidgets();
draw();

BIN
apps/dinoClock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

1
apps/dinoClock/icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgJC/AAVh/E/hgFC/O/AoMB8EZwc8AoUYgYFBgFgjAXDAowXBAo8B/ARBn4FGAAsBmAFE2ADBhwFEj4VEn+AgPvAontgfwv+ABIMCMwIVCgf4FIWAAoN3sAFCwERoEB0MHwF3gEF0MPwFEAoW/4ALD/4tCg/hAoYhB/5ZDwF+Aok0gEIkEf/4AB8eMBoM2bkw="))

View File

@ -0,0 +1,17 @@
{
"id": "dinoClock",
"name": "Dino Clock",
"description": "Clock with dino from Chrome",
"screenshots": [{"url":"screens/screen1.png"}],
"icon": "app.png",
"version": "0.01",
"type": "clock",
"tags": "clock, weather, dino, trex, chrome",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name":"dinoClock.app.js","url":"app.js"},
{"name":"dinoClock.img","url":"icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -11,3 +11,4 @@
0.11: Fix bangle.js 1 white icons not displaying 0.11: Fix bangle.js 1 white icons not displaying
0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock. 0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock.
0.13: Added swipeExit setting so that left-right to exit is an option 0.13: Added swipeExit setting so that left-right to exit is an option
0.14: Don't move pages when doing exit swipe.

View File

@ -29,6 +29,6 @@ Bangle 2:
**Touch** - icon to select, scond touch launches app **Touch** - icon to select, scond touch launches app
**Swipe Left** - move to next page of app icons **Swipe Left/Up** - move to next page of app icons
**Swipe Right** - move to previous page of app icons **Swipe Right/Down** - move to previous page of app icons

View File

@ -93,7 +93,7 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
if (dirUpDown==-1||dirLeftRight==-1){ if (dirUpDown==-1||dirLeftRight==-1){
++page; if (page>maxPage) page=0; ++page; if (page>maxPage) page=0;
drawPage(page); drawPage(page);
} else if (dirUpDown==1||dirLeftRight==1){ } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
--page; if (page<0) page=maxPage; --page; if (page<0) page=maxPage;
drawPage(page); drawPage(page);
} }

View File

@ -1,7 +1,7 @@
{ {
"id": "dtlaunch", "id": "dtlaunch",
"name": "Desktop Launcher", "name": "Desktop Launcher",
"version": "0.13", "version": "0.14",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.", "description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}], "screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png", "icon": "icon.png",

1
apps/f9lander/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

33
apps/f9lander/README.md Normal file
View File

@ -0,0 +1,33 @@
# F9 Lander
Land a Falcon 9 booster on a drone ship.
## Game play
Attempt to land your Falcon 9 booster on a drone ship before running out of fuel.
A successful landing requires:
* setting down on the ship
* the booster has to be mostly vertical
* the landing speed cannot be too high
## Controls
The angle of the booster is controlled by tilting the watch side-to-side. The
throttle level is controlled by tilting the watch forward and back:
* screen horizontal (face up) means no throttle
* screen vertical corresponds to full throttle
The fuel burn rate is proportional to the throttle level.
## Creators
Liam Kl. B.
Marko Kl. B.
## Screenshots
![](f9lander_screenshot1.png)
![](f9lander_screenshot2.png)
![](f9lander_screenshot3.png)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwcA/4AD/P8yVJkgCCye27dt2wRE//kCIuSuwRIBwgCCpwRQpIRRnYRQkmdCIvPCJICBEZ4RG/IRP/15CJ/z5IRPz4RM/gQB/n+BxICCn/z/P/BxQCDz7mIAX4Cq31/CJ+ebpiYE/IR/CNP/5IROnn//4jP5DFQ5sJCKAjPk3oCMMk4QRQAX4Ckn7jBAA/5CK8nCJPJNHA"))

150
apps/f9lander/app.js Normal file
View File

@ -0,0 +1,150 @@
const falcon9 = Graphics.createImage(`
xxxxx
xxxxx xxxxx
x x
x x
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxx
xxxxxxxxx
xx xxxxx xx
xx xx`);
const droneShip = Graphics.createImage(`
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
`);
const droneX = Math.floor(Math.random()*(g.getWidth()-droneShip.width-40) + 20)
const cloudOffs = Math.floor(Math.random()*g.getWidth()/2);
const oceanHeight = g.getHeight()*0.1;
const targetY = g.getHeight()-oceanHeight-falcon9.height/2;
var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2,
y : 20,
vx : 0,
vy : 0,
mass : 100,
fuel : 100 };
var exploded = false;
var nExplosions = 0;
var landed = false;
const gravity = 4;
const dt = 0.1;
const fuelBurnRate = 20*(176/g.getHeight());
const maxV = 12;
function flameImageGen (throttle) {
var str = " xxx \n xxx \n";
str += "xxxxx\n".repeat(throttle);
str += " xxx \n x \n";
return Graphics.createImage(str);
}
function drawFalcon(x, y, throttle, angle) {
g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle});
if (throttle>0) {
var flameImg = flameImageGen(throttle);
var r = falcon9.height/2 + flameImg.height/2-1;
var xoffs = -Math.sin(angle)*r;
var yoffs = Math.cos(angle)*r;
if (Math.random()>0.7) g.setColor(1, 0.5, 0);
else g.setColor(1, 1, 0);
g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle});
}
}
function drawBG() {
g.setBgColor(0.2, 0.2, 1).clear();
g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1);
g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10);
g.setColor(1, 1, 0).fillCircle(g.getWidth(), 0, 20);
g.setColor(1, 1, 1).drawImage(droneShip, droneX, g.getHeight()-oceanHeight-1);
}
function showFuel() {
g.setColor(0, 0, 0).setFont("4x6:2").setFontAlign(-1, -1, 0).drawString("Fuel: "+Math.abs(booster.fuel).toFixed(0), 4, 4);
}
function renderScreen(input) {
drawBG();
showFuel();
drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle);
}
function getInputs() {
var accel = Bangle.getAccel();
var a = Math.PI/2 + Math.atan2(accel.y, accel.x);
var t = (1+accel.z);
if (t > 1) t = 1;
if (t < 0) t = 0;
if (booster.fuel<=0) t = 0;
return {throttle: t, angle: a};
}
function epilogue(str) {
g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString(str, g.getWidth()/2, g.getHeight()/2).flip();
g.setFont("Vector", 16).drawString("<= again exit =>", g.getWidth()/2, g.getHeight()/2+20);
clearInterval(stepInterval);
Bangle.on("swipe", (d) => { if (d>0) load(); else load('f9lander.app.js'); });
}
function gameStep() {
if (exploded) {
if (nExplosions++ < 15) {
var r = Math.random()*25;
var x = Math.random()*30 - 15;
var y = Math.random()*30 - 15;
g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r);
if (nExplosions==1) Bangle.buzz(600);
}
else epilogue("You crashed!");
}
else {
var input = getInputs();
if (booster.y >= targetY) {
// console.log(booster.x + " " + booster.y + " " + booster.vy + " " + droneX + " " + input.angle);
if (Math.abs(booster.x-droneX-droneShip.width/2)<droneShip.width/2 && Math.abs(input.angle)<Math.PI/8 && booster.vy<maxV) {
renderScreen({angle:0, throttle:0});
epilogue("You landed!");
}
else exploded = true;
}
else {
booster.x += booster.vx*dt;
booster.y += booster.vy*dt;
booster.vy += gravity*dt;
booster.fuel -= input.throttle*dt*fuelBurnRate;
booster.vy += -Math.cos(input.angle)*input.throttle*gravity*3*dt;
booster.vx += Math.sin(input.angle)*input.throttle*gravity*3*dt;
renderScreen(input);
}
}
}
var stepInterval;
Bangle.setLCDTimeout(0);
renderScreen({angle:0, throttle:0});
g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString("Swipe to start", g.getWidth()/2, g.getHeight()/2);
Bangle.on("swipe", () => {
stepInterval = setInterval(gameStep, Math.floor(1000*dt));
Bangle.removeListener("swipe");
});

BIN
apps/f9lander/f9lander.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,15 @@
{ "id": "f9lander",
"name": "Falcon9 Lander",
"shortName":"F9lander",
"version":"0.01",
"description": "Land a rocket booster",
"icon": "f9lander.png",
"screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }],
"readme": "README.md",
"tags": "game",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"storage": [
{"name":"f9lander.app.js","url":"app.js"},
{"name":"f9lander.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -1,2 +1,4 @@
0.01: New Clock Nifty A 0.01: New Clock Nifty A
0.02: Shows the current week number (ISO8601), can be disabled via settings "" 0.02: Shows the current week number (ISO8601), can be disabled via settings
0.03: Call setUI before loading widgets
Improve settings page

View File

@ -1,13 +1,12 @@
# Nifty-A Clock # Nifty-A Clock
Colors are black/white - photos have non correct camera color "blue" Colors are black/white - photos have non correct camera color "blue".
## This is the clock This is the clock:
![](screenshot_nifty.png) ![](screenshot_nifty.png)
## The week number (ISO8601) can be turned of in settings The week number (ISO8601) can be turned off in settings (default is `On`)
(default is **"On"**)
![](screenshot_settings_nifty.png) ![](screenshot_settings_nifty.png)

View File

@ -1,6 +1,6 @@
const locale = require("locale"); const locale = require("locale");
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true}; const showWeekNum = Object.assign({ showWeekNum: true }, require('Storage').readJSON("ffcniftya.json", true))["showWeekNum"];
/* Clock *********************************************/ /* Clock *********************************************/
const scale = g.getWidth() / 176; const scale = g.getWidth() / 176;
@ -17,16 +17,17 @@ const center = {
y: Math.round(((viewport.height - widget) / 2) + widget), y: Math.round(((viewport.height - widget) / 2) + widget),
} }
function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480 // copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
var tdt = new Date(date.valueOf()); function ISO8601_week_no(date) {
var dayn = (date.getDay() + 6) % 7; var tdt = new Date(date.valueOf());
tdt.setDate(tdt.getDate() - dayn + 3); var dayn = (date.getDay() + 6) % 7;
var firstThursday = tdt.valueOf(); tdt.setDate(tdt.getDate() - dayn + 3);
tdt.setMonth(0, 1); var firstThursday = tdt.valueOf();
if (tdt.getDay() !== 4) { tdt.setMonth(0, 1);
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7); if (tdt.getDay() !== 4) {
} tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
return 1 + Math.ceil((firstThursday - tdt) / 604800000); }
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
} }
function d02(value) { function d02(value) {
@ -59,7 +60,7 @@ function draw() {
g.drawString(year, centerDatesScaleX, center.y - 62 * scale); g.drawString(year, centerDatesScaleX, center.y - 62 * scale);
g.drawString(month, centerDatesScaleX, center.y - 44 * scale); g.drawString(month, centerDatesScaleX, center.y - 44 * scale);
g.drawString(day, centerDatesScaleX, center.y - 26 * scale); g.drawString(day, centerDatesScaleX, center.y - 26 * scale);
if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale); if (showWeekNum) g.drawString(weekNum, centerDatesScaleX, center.y + 15 * scale);
g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale); g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale);
g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale); g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale);
} }
@ -79,7 +80,6 @@ function clearTickTimer() {
function queueNextTick() { function queueNextTick() {
clearTickTimer(); clearTickTimer();
tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000)); tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000));
// tickTimer = setTimeout(tick, 3000);
} }
function tick() { function tick() {
@ -91,21 +91,16 @@ function tick() {
// Clear the screen once, at startup // Clear the screen once, at startup
g.clear(); g.clear();
// Start ticking
tick(); tick();
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower', (on) => { Bangle.on('lcdPower', (on) => {
if (on) { if (on) {
tick(); // Start ticking tick();
} else { } else {
clearTickTimer(); // stop ticking clearTickTimer();
} }
}); });
// Load widgets Bangle.setUI("clock");
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
// Show launcher when middle button pressed
Bangle.setUI("clock");

View File

@ -1,7 +1,7 @@
{ {
"id": "ffcniftya", "id": "ffcniftya",
"name": "Nifty-A Clock", "name": "Nifty-A Clock",
"version": "0.02", "version": "0.03",
"description": "A nifty clock with time and date", "description": "A nifty clock with time and date",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot_nifty.png"}], "screenshots": [{"url":"screenshot_nifty.png"}],

View File

@ -1,23 +1,15 @@
(function(back) { (function (back) {
var FILE = "ffcniftya.json"; const settings = Object.assign({ showWeekNum: true }, require("Storage").readJSON("ffcniftya.json", true));
// Load settings
var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true };
function writeSettings() {
require('Storage').writeJSON(FILE, cfg);
}
// Show the menu
E.showMenu({ E.showMenu({
"" : { "title" : "Nifty-A Clock" }, "": { "title": "Nifty-A Clock" },
"< Back" : () => back(), "< Back": () => back(),
'week number?': { /*LANG*/"Show Week Number": {
value: cfg.showWeekNum, value: settings.showWeekNum,
format: v => v?"On":"Off",
onchange: v => { onchange: v => {
cfg.showWeekNum = v; settings.showWeekNum = v;
writeSettings(); require("Storage").writeJSON("ffcniftya.json", settings);
} }
} }
}); });
}) })

View File

@ -1,2 +1,5 @@
0.01: New Clock Nifty B 0.01: New Clock Nifty B
0.02: Added configuration 0.02: Added configuration
0.03: Call setUI before loading widgets
Fix bug with black being unselectable
Improve settings page

View File

@ -1,9 +1,6 @@
# Nifty Series B Clock # Nifty Series B Clock
- Display Time and Date - Display Time and Date
- Color Configuration - Colour Configuration
##
![](screenshot.png) ![](screenshot.png)

View File

@ -1,9 +1,5 @@
const locale = require("locale"); const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
const storage = require('Storage'); const color = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)).color; // Default to RED
const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"];
const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */;
/* Clock *********************************************/ /* Clock *********************************************/
const scale = g.getWidth() / 176; const scale = g.getWidth() / 176;
@ -19,7 +15,7 @@ const center = {
}; };
function d02(value) { function d02(value) {
return ('0' + value).substr(-2); return ("0" + value).substr(-2);
} }
function renderEllipse(g) { function renderEllipse(g) {
@ -35,8 +31,8 @@ function renderText(g) {
const month = d02(now.getMonth() + 1); const month = d02(now.getMonth() + 1);
const year = now.getFullYear(); const year = now.getFullYear();
const month2 = locale.month(now, 3); const month2 = require("locale").month(now, 3);
const day2 = locale.dow(now, 3); const day2 = require("locale").dow(now, 3);
g.setFontAlign(1, 0).setFont("Vector", 90 * scale); g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale);
@ -96,7 +92,6 @@ function startTick(run) {
stopTick(); stopTick();
run(); run();
ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000)); ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000));
// ticker = setTimeout(() => startTick(run), 3000);
} }
/* Init **********************************************/ /* Init **********************************************/
@ -104,7 +99,7 @@ function startTick(run) {
g.clear(); g.clear();
startTick(draw); startTick(draw);
Bangle.on('lcdPower', (on) => { Bangle.on("lcdPower", (on) => {
if (on) { if (on) {
startTick(draw); startTick(draw);
} else { } else {
@ -112,7 +107,6 @@ Bangle.on('lcdPower', (on) => {
} }
}); });
Bangle.setUI("clock");
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
Bangle.setUI("clock");

View File

@ -1,8 +1,8 @@
{ {
"id": "ffcniftyb", "id": "ffcniftyb",
"name": "Nifty-B Clock", "name": "Nifty-B Clock",
"version": "0.02", "version": "0.03",
"description": "A nifty clock (series B) with time, date and color configuration", "description": "A nifty clock (series B) with time, date and colour configuration",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot.png"}], "screenshots": [{"url":"screenshot.png"}],
"type": "clock", "type": "clock",

View File

@ -1,49 +1,31 @@
(function (back) { (function (back) {
const storage = require('Storage'); const settings = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true));
const SETTINGS_FILE = "ffcniftyb.json";
const colors = { const colors = {
65535: 'White', 65535: /*LANG*/"White",
63488: 'Red', 63488: /*LANG*/"Red",
65504: 'Yellow', 65504: /*LANG*/"Yellow",
2047: 'Cyan', 2047: /*LANG*/"Cyan",
2016: 'Green', 2016: /*LANG*/"Green",
31: 'Blue', 31: /*LANG*/"Blue",
0: 'Black', 0: /*LANG*/"Black"
} }
function load(settings) { const menu = {};
return Object.assign(settings, storage.readJSON(SETTINGS_FILE, 1) || {}); menu[""] = { title: "Nifty-B Clock" };
} menu["< Back"] = back;
function save(settings) { Object.keys(colors).forEach(color => {
storage.write(SETTINGS_FILE, settings) var label = colors[color];
} menu[label] = {
value: settings.color == color,
const settings = load({ onchange: () => {
color: 63488 /* red */, settings.color = color;
require("Storage").write("ffcniftyb.json", settings);
setTimeout(load, 10);
}
};
}); });
const saveColor = (color) => () => { E.showMenu(menu);
settings.color = color;
save(settings);
back();
};
function showMenu(items, opt) {
items[''] = opt || {};
items['< Back'] = back;
E.showMenu(items);
}
showMenu(
Object.keys(colors).reduce((menu, color) => {
menu[colors[color]] = saveColor(color);
return menu;
}, {}),
{
title: 'Color',
selected: Object.keys(colors).indexOf(settings.color)
}
);
}); });

View File

@ -6,4 +6,5 @@
0.06: Fixed issue 1609 added a message popup state handler to control unwanted screen redraw 0.06: Fixed issue 1609 added a message popup state handler to control unwanted screen redraw
0.07: Optimized the mover algorithm for efficiency (work in progress) 0.07: Optimized the mover algorithm for efficiency (work in progress)
0.08: Bug fix at end of the game with victorious splash and glorious orchestra 0.08: Bug fix at end of the game with victorious splash and glorious orchestra
0.09: Added settings menu, removed symbol selection button (*), added highscore reset 0.09: Added settings menu, removed symbol selection button (*), added highscore reset
0.10: fixed clockmode in settings

View File

@ -1,7 +1,7 @@
{ "id": "game1024", { "id": "game1024",
"name": "1024 Game", "name": "1024 Game",
"shortName" : "1024 Game", "shortName" : "1024 Game",
"version": "0.09", "version": "0.10",
"icon": "game1024.png", "icon": "game1024.png",
"screenshots": [ {"url":"screenshot.png" } ], "screenshots": [ {"url":"screenshot.png" } ],
"readme":"README.md", "readme":"README.md",

View File

@ -32,10 +32,10 @@
} }
}, },
"Exit press:": { "Exit press:": {
value: !settings.debugMode, // ! converts undefined to true value: !settings.clockMode, // ! converts undefined to true
format: v => v?"short":"long", format: v => v?"short":"long",
onchange: v => { onchange: v => {
settings.debugMode = v; settings.clockMode = v;
writeSettings(); writeSettings();
}, },
}, },
@ -67,4 +67,4 @@
} }
// Show the menu // Show the menu
E.showMenu(settingsMenu); E.showMenu(settingsMenu);
}) })

View File

@ -1 +1,2 @@
0.01: New App! Very limited course support. 0.01: New App! Very limited course support.
0.02: Course search added to BangleApps page

View File

@ -7,30 +7,55 @@
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script> integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
</head> </head>
<style>
#searchresults {
list-style-type: none;
margin: 0;
padding: 0;
}
#searchresults li:hover {
background-color: #ccc;
}
</style>
<body> <body>
<div> <div class="container">
<input type="text" placeholder="Course ID" id="course_id"> <div class="form-group">
<button type="button" onclick="courseSearch();">Search</button> <label class="form-label" for="course_id">Course Search</label>
<p id="status"></p> <div class="input-group">
<input class="form-input" type="text" id="course_id" placeholder="Whistling Straits">
<button type="button" class="btn btn-primary input-group-btn" onclick="courseSearch();">Search</button>
</div>
</div>
<div class="columns" id="searchresults"></div>
<div> <div>
<p id="status">No course loaded, please search for a course then choose 'select'.</p>
<button id="upload" class="btn btn-primary" disabled="true">Upload to Device</button> <button id="upload" class="btn btn-primary" disabled="true">Upload to Device</button>
<button id="download" class="btn btn-primary" disabled="true">Download Course</button> <button id="download" class="btn btn-primary" disabled="true">Download Course</button>
</div> </div>
<p>A course needs a few things to be parsed correctly by this tool.</p> <div>
<ul> <p>A course needs a few things to be parsed correctly by this tool.</p>
<li>See official mapping guidelines <a <ul>
href="https://wiki.openstreetmap.org/wiki/Tag:leisure%3Dgolf_course">here</a>.</li> <li>See official mapping guidelines <a
<li>All holes and features must be within the target course's area.</li> href="https://wiki.openstreetmap.org/wiki/Tag:leisure%3Dgolf_course">here</a>.</li>
<li>Supported features are greens, fairways, tees, bunkers, water hazards and holes.</li> <li>All holes and features must be within the target course's area.</li>
<li>All features for a given hole should have the "ref" tag with the hole number as value. Shared features should <li>Supported features are greens, fairways, tees, bunkers, water hazards and holes.</li>
list ref values separated by ';'. <a href="https://www.openstreetmap.org/way/36896320">example</a>.</li> <li>All features for a given hole should have the "ref" tag with the hole number as value. Shared features
<li>There must be 18 holes and they must have the following tags: handicap, par, ref, dist</li> should
<li>For any mapping assistance or issues, please file in the <a list ref values separated by ';'. <a href="https://www.openstreetmap.org/way/36896320">example</a>.</li>
href="https://github.com/espruino/BangleApps/issues/new?assignees=&labels=bug&template=bangle-bug-report-custom-form.yaml&title=[golfview]+Short+description+of+bug">official <li>There must be 18 holes and they must have the following tags: handicap, par, ref, dist</li>
repo</a></li> <li>For any mapping assistance or issues, please file in the <a
</ul> href="https://github.com/espruino/BangleApps/issues/new?assignees=&labels=bug&template=bangle-bug-report-custom-form.yaml&title=[golfview]+Short+description+of+bug">official
<a href="https://www.openstreetmap.org/way/25447898">Example Course</a> repo</a></li>
<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</p> </ul>
<a href="https://www.openstreetmap.org/way/25447898">Example Course</a>
</div>
<footer>
<hr />
<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</p>
</footer>
</div> </div>
<script src="../../core/lib/customize.js"></script> <script src="../../core/lib/customize.js"></script>
@ -38,13 +63,47 @@
<script> <script>
const url = "https://overpass-api.de/api/interpreter"; const url = "https://overpass-api.de/api/interpreter";
let query = `[out:json][timeout:5];way(25447898);map_to_area ->.golfcourse;way["golf"="hole"](area.golfcourse)->.holes;(relation["golf"="fairway"](area.golfcourse);way["golf"~"^(green|tee|water_hazard|bunker|fairway)"](area.golfcourse);)->.features;.holes out geom;.features out geom;`; const search_url = "https://nominatim.openstreetmap.org/search";
let search_query = null;
let course_input = null; let course_input = null;
let current_course = null;
let search_results = $("#searchresults");
function buildCourseListElement(title, subtitle, element) {
let base_element = $(`<div class="column col-4">
<div class="tile">
<div class="tile-icon">
<figure class="avatar avatar-lg"><img src="./golfview.png" alt="Avatar"></figure>
</div>
<div class="tile-content">
<p class="tile-title">${title}</p>
<p class="tile-subtitle">${subtitle}</p>
<div class="btn-group btn-group-block">
</div>
</div>
</div>
</div>`);
// add the buttons
base_element.find('.btn-group')
.append($('<button>').addClass("btn btn-primary").text("select")
.on('click', function () {
console.log(element);
doQuery(element);
}))
.append($('<a>').attr('href', `https://www.openstreetmap.org/${element.osm_type}/${element.osm_id}`).attr('target', '_blank')
.append('<button>').addClass("btn").text("view")
);
return base_element
}
function courseSearch() { function courseSearch() {
let inputVal = document.getElementById("course_id").value; let inputVal = document.getElementById("course_id").value;
query = `[out:json][timeout:5];way(${inputVal});map_to_area ->.golfcourse;way["golf"="hole"](area.golfcourse)->.holes;(relation["golf"="fairway"](area.golfcourse);way["golf"~"^(green|tee|water_hazard|bunker|fairway)"](area.golfcourse);)->.features;.holes out geom;.features out geom;`; search_query = {
doQuery(); "format": "jsonv2",
"q": inputVal,
};
doSearch();
} }
function processFeatures(course_verbose) { function processFeatures(course_verbose) {
@ -116,7 +175,6 @@
} }
var courses = []; var courses = [];
var course_name = "Davis";
$("#upload").click(function () { $("#upload").click(function () {
sendCustomizedApp({ sendCustomizedApp({
storage: courses, storage: courses,
@ -124,22 +182,45 @@
}); });
$("#download").click(function () { $("#download").click(function () {
downloadObjectAsJSON(courses[0].content, "golfcourse-" + course_name); downloadObjectAsJSON(courses[0].content, "golfcourse-download");
}); });
function testfunc(params) {
console.log(params);
}
// download info from the course // download info from the course
function doQuery() { function doSearch() {
$.get(search_url, search_query, function (result) {
if (result.length === 0) {
$('#status').text("Course not found!");
return;
}
search_results.empty();
for (let index = 0; index < result.length; index++) {
const element = result[index];
if (element.type != "golf_course") continue;
search_results.append(buildCourseListElement(element.display_name.split(",")[0], element.display_name, element));
}
})
}
// download info from the course
function doQuery(course) {
const query = `[out:json][timeout:5];way(${course.osm_id});map_to_area ->.golfcourse;way["golf"="hole"](area.golfcourse)->.holes;(relation["golf"="fairway"](area.golfcourse);way["golf"~"^(green|tee|water_hazard|bunker|fairway)"](area.golfcourse);)->.features;.holes out geom;.features out geom;`;
const course_id = course["osm_id"];
$.post(url, query, function (result) { $.post(url, query, function (result) {
if (result.elements.length === 0) { if (result.elements.length === 0) {
$('#status').text("Course not found!"); $('#status').text("Course not found!");
return; return;
} }
course_input = result; console.log(result);
console.log(course_input); out = processFeatures(result.elements);
out = processFeatures(course_input.elements);
console.log(out); console.log(out);
courses.push({ courses.push({
name: "golfcourse-" + course_name + ".json", name: `golf-${course_id}.json`,
content: JSON.stringify(out), content: JSON.stringify(out),
}); });
$('#status').text("Course retrieved!"); $('#status').text("Course retrieved!");

View File

@ -54,7 +54,9 @@ function distance(a, b) {
// golfview.js // golfview.js
let course = require("Storage").readJSON("golfcourse-Davis.json").holes;//TODO use the course ID let courselist = require("Storage").list(/^golf-\d+\.json$/);
let course = require("Storage").readJSON(courselist[0]).holes; // TODO use the first course for now
let current_hole = 1; let current_hole = 1;
let hole = course[current_hole.toString()]; let hole = course[current_hole.toString()];
let user_position = { let user_position = {

View File

@ -1,6 +1,6 @@
{ "id": "golfview", { "id": "golfview",
"name": "Golf View", "name": "Golf View",
"version":"0.01", "version":"0.02",
"description": "This app will provide you with on course data to support your golf game!", "description": "This app will provide you with on course data to support your golf game!",
"icon": "golfview.png", "icon": "golfview.png",
"tags": "outdoors, gps", "tags": "outdoors, gps",

View File

@ -13,3 +13,4 @@
0.12: Add setting for Daily Step Goal 0.12: Add setting for Daily Step Goal
0.13: Add support for internationalization 0.13: Add support for internationalization
0.14: Move settings 0.14: Move settings
0.15: Fix charts (fix #1366)

View File

@ -46,7 +46,7 @@ function menuHRM() {
function stepsPerHour() { function stepsPerHour() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
let data = new Uint16Array(24); var data = new Uint16Array(24);
require("health").readDay(new Date(), h=>data[h.hr]+=h.steps); require("health").readDay(new Date(), h=>data[h.hr]+=h.steps);
g.clear(1); g.clear(1);
Bangle.drawWidgets(); Bangle.drawWidgets();
@ -57,7 +57,7 @@ function stepsPerHour() {
function stepsPerDay() { function stepsPerDay() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
let data = new Uint16Array(31); var data = new Uint16Array(31);
require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps);
g.clear(1); g.clear(1);
Bangle.drawWidgets(); Bangle.drawWidgets();
@ -68,8 +68,8 @@ function stepsPerDay() {
function hrmPerHour() { function hrmPerHour() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
let data = new Uint16Array(24); var data = new Uint16Array(24);
let cnt = new Uint8Array(23); var cnt = new Uint8Array(23);
require("health").readDay(new Date(), h=>{ require("health").readDay(new Date(), h=>{
data[h.hr]+=h.bpm; data[h.hr]+=h.bpm;
if (h.bpm) cnt[h.hr]++; if (h.bpm) cnt[h.hr]++;
@ -84,8 +84,8 @@ function hrmPerHour() {
function hrmPerDay() { function hrmPerDay() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
let data = new Uint16Array(31); var data = new Uint16Array(31);
let cnt = new Uint8Array(31); var cnt = new Uint8Array(31);
require("health").readDailySummaries(new Date(), h=>{ require("health").readDailySummaries(new Date(), h=>{
data[h.day]+=h.bpm; data[h.day]+=h.bpm;
if (h.bpm) cnt[h.day]++; if (h.bpm) cnt[h.day]++;
@ -100,7 +100,7 @@ function hrmPerDay() {
function movementPerHour() { function movementPerHour() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
let data = new Uint16Array(24); var data = new Uint16Array(24);
require("health").readDay(new Date(), h=>data[h.hr]+=h.movement); require("health").readDay(new Date(), h=>data[h.hr]+=h.movement);
g.clear(1); g.clear(1);
Bangle.drawWidgets(); Bangle.drawWidgets();
@ -111,7 +111,7 @@ function movementPerHour() {
function movementPerDay() { function movementPerDay() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
let data = new Uint16Array(31); var data = new Uint16Array(31);
require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement); require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement);
g.clear(1); g.clear(1);
Bangle.drawWidgets(); Bangle.drawWidgets();
@ -183,7 +183,7 @@ function drawBarChart() {
} }
// draw a fake 0 height bar if chart_index is outside the bounds of the array // draw a fake 0 height bar if chart_index is outside the bounds of the array
if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len) if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len && chart_max_datum > 0)
bar_top = bar_bot - 100 * (chart_data[chart_index + bar - 1]) / chart_max_datum; bar_top = bar_bot - 100 * (chart_data[chart_index + bar - 1]) / chart_max_datum;
else else
bar_top = bar_bot; bar_top = bar_bot;

Some files were not shown because too many files have changed in this diff Show More