Merge branch 'master' into agenda_today

master
Gordon Williams 2023-01-05 10:25:45 +00:00 committed by GitHub
commit c8a45e3a04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
98 changed files with 1480 additions and 439 deletions

View File

@ -4,4 +4,5 @@ apps/schoolCalendar/fullcalendar/main.js
apps/authentiwatch/qr_packed.js apps/authentiwatch/qr_packed.js
apps/qrcode/qr-scanner.umd.min.js apps/qrcode/qr-scanner.umd.min.js
apps/gipy/pkg/gpconv.js apps/gipy/pkg/gpconv.js
apps/health/chart.min.js
*.test.js *.test.js

View File

@ -58,3 +58,7 @@ body:
validations: validations:
required: true required: true
- type: textarea
id: apps
attributes:
label: Installed apps

View File

@ -98,7 +98,7 @@ This is the best way to test...
**Note:** It's a great idea to get a local copy of the repository on your PC, **Note:** It's a great idea to get a local copy of the repository on your PC,
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
that there might be. that there might be. To get the project running locally, you have to initialize and update the git submodules first: `git submodule --init && git submodule update`.
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect. Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.

View File

@ -1 +1,2 @@
0.01: Beta version for Bangle 2 (2021/11/28) 0.01: Beta version for Bangle 2 (2021/11/28)
0.02: Shows night time on the map (2022/12/28)

View File

@ -7,7 +7,7 @@
* Other time zones * Other time zones
* Currently hardcoded to Paris and Tokyo (this will be customizable in a future version) * Currently hardcoded to Paris and Tokyo (this will be customizable in a future version)
* World Map * World Map
* The yellow line shows the position of the sun * The map shows day and night on Earth and the position of the Sun (yellow line)
![](screenshot.png) ![](screenshot.png)

View File

@ -102,13 +102,24 @@ function queueNextDraw() {
function draw() { function draw() {
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT); g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
var x_sun = 176 - (getGmt().getHours() / 24 * 176 + 4); var x_sun = 176 - (getGmt().getHours() / 24 * 176 + 4);
g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight()); g.setColor('#ff0').drawLine(x_sun, g.getHeight()-IMAGEHEIGHT, x_sun, g.getHeight());
g.reset(); g.reset();
var x_night_start = 176 - (((getGmt().getHours()-6)%24) / 24 * 176 + 4);
var x_night_end = 176 - (((getGmt().getHours()+6)%24) / 24 * 176 + 4);
for (let x = x_night_start; x < 176; x+=2) {
g.setColor('#000').drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
}
if (x_night_end < x_night_start) {
for (let x = 0; x < x_night_end; x+=2) {
g.setColor('#000').drawLine(x, g.getHeight()-IMAGEHEIGHT, x, g.getHeight());
}
}
var locale = require("locale"); var locale = require("locale");
var date = new Date(); var date = new Date();
g.setFontAlign(0,0); g.setFontAlign(0,0);
g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46); g.setFont("Michroma36").drawString(locale.time(date,1), g.getWidth()/2, 46);

View File

@ -1,7 +1,7 @@
{ {
"id": "a_clock_timer", "id": "a_clock_timer",
"name": "A Clock with Timer", "name": "A Clock with Timer",
"version": "0.01", "version": "0.02",
"description": "A Clock with Timer, Map and Time Zones", "description": "A Clock with Timer, Map and Time Zones",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot.png"}], "screenshots": [{"url":"screenshot.png"}],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,2 +1,3 @@
1.00: Release (2021/12/01) 1.00: Release (2021/12/01)
1.01: Grey font when timer is frozen (2021/12/04) 1.01: Grey font when timer is frozen (2021/12/04)
1.02: Force light theme, since the app is not designed for dark theme (2022/12/28)

View File

@ -166,6 +166,7 @@ function draw() {
g.drawRect(88+8,138-24, 176-10, 138+22); g.drawRect(88+8,138-24, 176-10, 138+22);
} }
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
require("FontHaxorNarrow7x17").add(Graphics); require("FontHaxorNarrow7x17").add(Graphics);
g.clear(); g.clear();
Bangle.loadWidgets(); Bangle.loadWidgets();

View File

@ -2,7 +2,7 @@
"id":"a_speech_timer", "id":"a_speech_timer",
"name":"Speech Timer", "name":"Speech Timer",
"icon": "app.png", "icon": "app.png",
"version":"1.01", "version":"1.02",
"description": "A timer designed to help keeping your speeches and presentations to time.", "description": "A timer designed to help keeping your speeches and presentations to time.",
"tags": "tool,timer", "tags": "tool,timer",
"readme":"README.md", "readme":"README.md",

View File

@ -10,3 +10,4 @@
0.09: Ensure Agenda supplies an image for clkinfo items 0.09: Ensure Agenda supplies an image for clkinfo items
0.10: Update clock_info to avoid a redraw 0.10: Update clock_info to avoid a redraw
0.11: Setting to use "Today" and "Yesterday" instead of dates 0.11: Setting to use "Today" and "Yesterday" instead of dates
Added dynamic, short and range fields to clkinfo

View File

@ -1,7 +1,14 @@
(function() { (function() {
function getPassedSec(date) {
var now = new Date();
var passed = (now-date)/1000;
if(passed<0) return 0;
return passed;
}
var agendaItems = { var agendaItems = {
name: "Agenda", name: "Agenda",
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="), img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
dynamic: true,
items: [] items: []
}; };
var locale = require("locale"); var locale = require("locale");
@ -15,11 +22,15 @@
var title = entry.title.slice(0,12); var title = entry.title.slice(0,12);
var date = new Date(entry.timestamp*1000); var date = new Date(entry.timestamp*1000);
var dateStr = locale.date(date).replace(/\d\d\d\d/,""); var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
var shortStr = ((date-now) > 86400000 || entry.allDay) ? dateStr : locale.time(date,1);
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : ""; dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
agendaItems.items.push({ agendaItems.items.push({
name: "Agenda "+i, name: "Agenda "+i,
get: () => ({ text: title + "\n" + dateStr, img: agendaItems.img }), hasRange: true,
get: () => ({ text: title + "\n" + dateStr,
img: agendaItems.img, short: shortStr.trim(),
v: getPassedSec(date), min: 0, max: entry.durationInSeconds}),
show: function() {}, show: function() {},
hide: function () {} hide: function () {}
}); });

View File

@ -3,3 +3,5 @@
0.03: Do not load AGPS data on boot 0.03: Do not load AGPS data on boot
Increase minimum interval to 6 hours Increase minimum interval to 6 hours
0.04: Write AGPS data chunks with delay to improve reliability 0.04: Write AGPS data chunks with delay to improve reliability
0.05: Show last success date
Do not start A-GPS update automatically

View File

@ -23,12 +23,26 @@ Bangle.drawWidgets();
let waiting = false; let waiting = false;
function start() { function start(restart) {
g.reset(); g.reset();
g.clear(); g.clear();
waiting = false; waiting = false;
display("Retry?", "touch to retry"); if (!restart) {
display("Start?", "touch to start");
}
else {
display("Retry?", "touch to retry");
}
Bangle.on("touch", () => { updateAgps(); }); Bangle.on("touch", () => { updateAgps(); });
const file = "agpsdata.json";
let data = require("Storage").readJSON(file, 1) || {};
if (data.lastUpdate) {
g.setFont("Vector", 11);
g.drawString("last success:", 5, g.getHeight() - 22);
g.drawString(new Date(data.lastUpdate).toISOString(), 5, g.getHeight() - 11);
}
} }
function updateAgps() { function updateAgps() {
@ -36,7 +50,7 @@ function updateAgps() {
g.clear(); g.clear();
if (!waiting) { if (!waiting) {
waiting = true; waiting = true;
display("Updating A-GPS...", "takes ~ 10 seconds"); display("Updating A-GPS...", "takes ~10 seconds");
require("agpsdata").pull(function() { require("agpsdata").pull(function() {
waiting = false; waiting = false;
display("A-GPS updated.", "touch to close"); display("A-GPS updated.", "touch to close");
@ -45,10 +59,10 @@ function updateAgps() {
function(error) { function(error) {
waiting = false; waiting = false;
E.showAlert(error, "Error") E.showAlert(error, "Error")
.then(() => { start(); }); .then(() => { start(true); });
}); });
} else { } else {
display("Waiting..."); display("Waiting...");
} }
} }
updateAgps(); start(false);

View File

@ -2,7 +2,7 @@
"name": "A-GPS Data Downloader App", "name": "A-GPS Data Downloader App",
"shortName":"A-GPS Data", "shortName":"A-GPS Data",
"icon": "agpsdata.png", "icon": "agpsdata.png",
"version":"0.04", "version":"0.05",
"description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.", "description": "Once installed, this app allows you to download assisted GPS (A-GPS) data directly to your Bangle.js **via Gadgetbridge on an Android phone** when you run the app. If you just want to upload the latest AGPS data from this app loader, please use the `Assisted GPS Update (AGPS)` app.",
"tags": "boot,tool,assisted,gps,agps,http", "tags": "boot,tool,assisted,gps,agps,http",
"allow_emulator":true, "allow_emulator":true,

View File

@ -2,4 +2,5 @@
0.02: Design improvements and fixes. 0.02: Design improvements and fixes.
0.03: Indicate battery level through line occurrence. 0.03: Indicate battery level through line occurrence.
0.04: Use widget_utils module. 0.04: Use widget_utils module.
0.05: Support for clkinfo. 0.05: Support for clkinfo.
0.06: ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.

View File

@ -29,7 +29,6 @@ var H = g.getHeight();
var cx = W/2; var cx = W/2;
var cy = H/2; var cy = H/2;
var drawTimeout; var drawTimeout;
var lock_input = false;
/************************************************ /************************************************
@ -93,28 +92,6 @@ menu = menu.concat(clockItems);
settings.menuPosY = 0; settings.menuPosY = 0;
} }
// Set draw functions for each item
menu.forEach((menuItm, x) => {
menuItm.items.forEach((item, y) => {
function drawItem() {
// For the clock, we have a special case, as we don't wanna redraw
// immediately when something changes. Instead, we update data each minute
// to save some battery etc. Therefore, we hide (and disable the listener)
// immedeately after redraw...
item.hide();
// After drawing the item, we enable inputs again...
lock_input = false;
var info = item.get();
drawMenuItem(info.text, info.img);
}
item.on('redraw', drawItem);
})
});
function canRunMenuItem(){ function canRunMenuItem(){
if(settings.menuPosY == 0){ if(settings.menuPosY == 0){
return false; return false;
@ -204,8 +181,6 @@ function drawMenuItem(text, image){
drawTime(); drawTime();
return return
} }
// image = atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==");
text = String(text); text = String(text);
g.reset().setBgColor("#fff").setColor("#000"); g.reset().setBgColor("#fff").setColor("#000");
@ -292,7 +267,7 @@ function drawDigits(){
} }
function drawDate(){ function drawMenu(){
var menuEntry = menu[settings.menuPosX]; var menuEntry = menu[settings.menuPosX];
// The first entry is the overview... // The first entry is the overview...
@ -302,9 +277,8 @@ function drawDate(){
} }
// Draw item if needed // Draw item if needed
lock_input = true; var item = menuEntry.items[settings.menuPosY-1].get();
var item = menuEntry.items[settings.menuPosY-1]; drawMenuItem(item.text, item.img);
item.show();
} }
@ -320,7 +294,7 @@ function draw(){
g.setColor(1,1,1); g.setColor(1,1,1);
drawBackground(); drawBackground();
drawDate(); drawMenu();
drawCircle(Bangle.isLocked()); drawCircle(Bangle.isLocked());
} }
@ -353,10 +327,6 @@ Bangle.on('touch', function(btn, e){
var is_right = e.x > right && !is_upper && !is_lower; var is_right = e.x > right && !is_upper && !is_lower;
var is_center = !is_upper && !is_lower && !is_left && !is_right; var is_center = !is_upper && !is_lower && !is_left && !is_right;
if(lock_input){
return;
}
if(is_lower){ if(is_lower){
Bangle.buzz(40, 0.6); Bangle.buzz(40, 0.6);
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1); settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);

View File

@ -3,7 +3,7 @@
"name": "AI Clock", "name": "AI Clock",
"shortName":"AI Clock", "shortName":"AI Clock",
"icon": "aiclock.png", "icon": "aiclock.png",
"version":"0.05", "version":"0.06",
"readme": "README.md", "readme": "README.md",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.", "description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",

View File

@ -37,3 +37,4 @@
0.34: Add "Confirm" option to alarm/timer edit menus 0.34: Add "Confirm" option to alarm/timer edit menus
0.35: Add automatic translation of more strings 0.35: Add automatic translation of more strings
0.36: alarm widget moved out of app 0.36: alarm widget moved out of app
0.37: add message input and dated Events

View File

@ -1,15 +1,18 @@
# Alarms & Timers # Alarms & Timers
This app allows you to add/modify any alarms and timers. This app allows you to add/modify any alarms, timers and events.
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered.
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. 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.
## Menu overview ## Menu overview
- `New...` - `New...`
- `New Alarm` &rarr; Configure a new alarm - `New Alarm` &rarr; Configure a new alarm (triggered based on time and day of week)
- `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) - `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 - `New Timer` &rarr; Configure a new timer (triggered based on amount of time elapsed in hours/minutes/seconds)
- `New Event` &rarr; Configure a new event (triggered based on time and date)
- `Advanced` - `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 - `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 - `Enable All` &rarr; Enable _all_ disabled alarms & timers

View File

@ -48,9 +48,10 @@ function showMainMenu() {
}; };
alarms.forEach((e, index) => { alarms.forEach((e, index) => {
var label = e.timer var label = (e.timer
? require("time_utils").formatDuration(e.timer) ? require("time_utils").formatDuration(e.timer)
: require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""); : (e.date ? `${e.date.substring(5,10)} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
) + (e.msg ? " " + e.msg : "");
menu[label] = { menu[label] = {
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff), value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index) onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
@ -67,11 +68,12 @@ function showNewMenu() {
"": { "title": /*LANG*/"New..." }, "": { "title": /*LANG*/"New..." },
"< Back": () => showMainMenu(), "< Back": () => showMainMenu(),
/*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined), /*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
/*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined) /*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined),
/*LANG*/"Event": () => showEditAlarmMenu(undefined, undefined, true)
}); });
} }
function showEditAlarmMenu(selectedAlarm, alarmIndex) { function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
var isNew = alarmIndex === undefined; var isNew = alarmIndex === undefined;
var alarm = require("sched").newDefaultAlarm(); var alarm = require("sched").newDefaultAlarm();
@ -82,11 +84,16 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
} }
var time = require("time_utils").decodeTime(alarm.t); var time = require("time_utils").decodeTime(alarm.t);
if (withDate && !alarm.date) alarm.date = new Date().toLocalISOString().slice(0,10);
var date = alarm.date ? new Date(alarm.date) : undefined;
var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
var keyboard = "textinput";
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
const menu = { const menu = {
"": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" }, "": { "title": title },
"< Back": () => { "< Back": () => {
prepareAlarmForSave(alarm, alarmIndex, time); prepareAlarmForSave(alarm, alarmIndex, time, date);
saveAndReload(); saveAndReload();
showMainMenu(); showMainMenu();
}, },
@ -106,6 +113,36 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
wrap: true, wrap: true,
onchange: v => time.m = v onchange: v => time.m = v
}, },
/*LANG*/"Day": {
value: date ? date.getDate() : null,
min: 1,
max: 31,
wrap: true,
onchange: v => date.setDate(v)
},
/*LANG*/"Month": {
value: date ? date.getMonth() + 1 : null,
format: v => require("date_utils").month(v),
onchange: v => date.setMonth((v+11)%12)
},
/*LANG*/"Year": {
value: date ? date.getFullYear() : null,
min: new Date().getFullYear(),
max: 2100,
onchange: v => date.setFullYear(v)
},
/*LANG*/"Message": {
value: alarm.msg,
onchange: () => {
setTimeout(() => {
keyboard.input({text:alarm.msg}).then(result => {
alarm.msg = result;
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
});
}, 100);
}
},
/*LANG*/"Enabled": { /*LANG*/"Enabled": {
value: alarm.on, value: alarm.on,
onchange: v => alarm.on = v onchange: v => alarm.on = v
@ -115,8 +152,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => { onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
alarm.rp = repeat; alarm.rp = repeat;
alarm.dow = dow; alarm.dow = dow;
alarm.t = require("time_utils").encodeTime(time); prepareAlarmForSave(alarm, alarmIndex, time, date, true);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex); setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
}) })
}, },
/*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v), /*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
@ -136,6 +173,15 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
} }
}; };
if (!keyboard) delete menu[/*LANG*/"Message"];
if (alarm.date || withDate) {
delete menu[/*LANG*/"Repeat"];
} else {
delete menu[/*LANG*/"Day"];
delete menu[/*LANG*/"Month"];
delete menu[/*LANG*/"Year"];
}
if (!isNew) { if (!isNew) {
menu[/*LANG*/"Delete"] = () => { menu[/*LANG*/"Delete"] = () => {
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => { E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
@ -145,7 +191,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
showMainMenu(); showMainMenu();
} else { } else {
alarm.t = require("time_utils").encodeTime(time); alarm.t = require("time_utils").encodeTime(time);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex); setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate);
} }
}); });
}; };
@ -154,14 +200,17 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
E.showMenu(menu); E.showMenu(menu);
} }
function prepareAlarmForSave(alarm, alarmIndex, time) { function prepareAlarmForSave(alarm, alarmIndex, time, date, temp) {
alarm.t = require("time_utils").encodeTime(time); alarm.t = require("time_utils").encodeTime(time);
alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0; alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
if(date) alarm.date = date.toLocalISOString().slice(0,10);
if (alarmIndex === undefined) { if(!temp) {
alarms.push(alarm); if (alarmIndex === undefined) {
} else { alarms.push(alarm);
alarms[alarmIndex] = alarm; } else {
alarms[alarmIndex] = alarm;
}
} }
} }
@ -255,6 +304,8 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
} }
var time = require("time_utils").decodeTime(timer.timer); var time = require("time_utils").decodeTime(timer.timer);
var keyboard = "textinput";
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
const menu = { const menu = {
"": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" }, "": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
@ -285,6 +336,18 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
wrap: true, wrap: true,
onchange: v => time.s = v onchange: v => time.s = v
}, },
/*LANG*/"Message": {
value: timer.msg,
onchange: () => {
setTimeout(() => {
keyboard.input({text:timer.msg}).then(result => {
timer.msg = result;
prepareTimerForSave(timer, timerIndex, time, true);
setTimeout(showEditTimerMenu, 10, timer, timerIndex);
});
}, 100);
}
},
/*LANG*/"Enabled": { /*LANG*/"Enabled": {
value: timer.on, value: timer.on,
onchange: v => timer.on = v onchange: v => timer.on = v
@ -306,6 +369,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
} }
}; };
if (!keyboard) delete menu[/*LANG*/"Message"];
if (!isNew) { if (!isNew) {
menu[/*LANG*/"Delete"] = () => { menu[/*LANG*/"Delete"] = () => {
E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => { E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
@ -324,15 +388,17 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
E.showMenu(menu); E.showMenu(menu);
} }
function prepareTimerForSave(timer, timerIndex, time) { function prepareTimerForSave(timer, timerIndex, time, temp) {
timer.timer = require("time_utils").encodeTime(time); timer.timer = require("time_utils").encodeTime(time);
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer; timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
timer.last = 0; timer.last = 0;
if (timerIndex === undefined) { if (!temp) {
alarms.push(timer); if (timerIndex === undefined) {
} else { alarms.push(timer);
alarms[timerIndex] = timer; } else {
alarms[timerIndex] = timer;
}
} }
} }

View File

@ -2,7 +2,7 @@
"id": "alarm", "id": "alarm",
"name": "Alarms & Timers", "name": "Alarms & Timers",
"shortName": "Alarms", "shortName": "Alarms",
"version": "0.36", "version": "0.37",
"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", "tags": "tool,alarm",

View File

@ -18,3 +18,4 @@
0.18: Use new message library 0.18: Use new message library
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged) If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
0.19: Add automatic translation for a couple of strings. 0.19: Add automatic translation for a couple of strings.
0.20: Fix wrong event used for forwarded GPS data from Gadgetbridge and add mapper to map longitude value correctly.

View File

@ -134,7 +134,10 @@
event.satellites = NaN; event.satellites = NaN;
event.course = NaN; event.course = NaN;
event.fix = 1; event.fix = 1;
Bangle.emit('gps', event); event.lon = event.long;
delete event.long;
Bangle.emit('GPS', event);
}, },
"is_gps_active": function() { "is_gps_active": function() {
gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 }); gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 });
@ -208,7 +211,7 @@
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone) // Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
Bangle.setGPSPower = (isOn, appID) => { Bangle.setGPSPower = (isOn, appID) => {
// if not connected, use old logic // if not connected, use old logic
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID); if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
// Emulate old GPS power logic // Emulate old GPS power logic
if (!Bangle._PWR) Bangle._PWR={}; if (!Bangle._PWR) Bangle._PWR={};
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[]; if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];

View File

@ -2,7 +2,7 @@
"id": "android", "id": "android",
"name": "Android Integration", "name": "Android Integration",
"shortName": "Android", "shortName": "Android",
"version": "0.19", "version": "0.20",
"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

@ -22,3 +22,6 @@
0.22: Use the new clkinfo module for the menu. 0.22: Use the new clkinfo module for the menu.
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo. 0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
0.24: Update clock_info to avoid a redraw 0.24: Update clock_info to avoid a redraw
0.25: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on fw2v16.
ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.

View File

@ -1,3 +1,5 @@
{ // must be inside our own scope here so that when we are unloaded everything disappears
/************************************************ /************************************************
* Includes * Includes
*/ */
@ -12,8 +14,6 @@ const clock_info = require("clock_info");
const SETTINGS_FILE = "bwclk.setting.json"; const SETTINGS_FILE = "bwclk.setting.json";
const W = g.getWidth(); const W = g.getWidth();
const H = g.getHeight(); const H = g.getHeight();
var lock_input = false;
/************************************************ /************************************************
* Settings * Settings
@ -28,7 +28,7 @@ let settings = {
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) {
settings[key] = saved_settings[key] settings[key] = saved_settings[key];
} }
/************************************************ /************************************************
@ -74,20 +74,20 @@ Graphics.prototype.setMiniFont = function(scale) {
return this; return this;
}; };
function imgLock(){ let imgLock = function() {
return { return {
width : 16, height : 16, bpp : 1, width : 16, height : 16, bpp : 1,
transparent : 0, transparent : 0,
buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w=")) buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
} };
} };
/************************************************ /************************************************
* Menu * Menu
*/ */
// Custom bwItems menu - therefore, its added here and not in a clkinfo.js file. // Custom bwItems menu - therefore, its added here and not in a clkinfo.js file.
var bwItems = { let bwItems = {
name: null, name: null,
img: null, img: null,
items: [ items: [
@ -99,7 +99,7 @@ var bwItems = {
] ]
}; };
function weekOfYear() { let weekOfYear = function() {
var date = new Date(); var date = new Date();
date.setHours(0, 0, 0, 0); date.setHours(0, 0, 0, 0);
// Thursday in current week decides the year. // Thursday in current week decides the year.
@ -109,11 +109,11 @@ function weekOfYear() {
// Adjust to Thursday in week 1 and count number of weeks from date to week1. // Adjust to Thursday in week 1 and count number of weeks from date to week1.
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
- 3 + (week1.getDay() + 6) % 7) / 7); - 3 + (week1.getDay() + 6) % 7) / 7);
} };
// Load menu // Load menu
var menu = clock_info.load(); let menu = clock_info.load();
menu = menu.concat(bwItems); menu = menu.concat(bwItems);
@ -123,29 +123,7 @@ if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPos
settings.menuPosY = 0; settings.menuPosY = 0;
} }
// Set draw functions for each item let canRunMenuItem = function() {
menu.forEach((menuItm, x) => {
menuItm.items.forEach((item, y) => {
function drawItem() {
// For the clock, we have a special case, as we don't wanna redraw
// immediately when something changes. Instead, we update data each minute
// to save some battery etc. Therefore, we hide (and disable the listener)
// immedeately after redraw...
item.hide();
// After drawing the item, we enable inputs again...
lock_input = false;
var info = item.get();
drawMenuItem(info.text, info.img);
}
item.on('redraw', drawItem);
})
});
function canRunMenuItem(){
if(settings.menuPosY == 0){ if(settings.menuPosY == 0){
return false; return false;
} }
@ -153,10 +131,10 @@ function canRunMenuItem(){
var menuEntry = menu[settings.menuPosX]; var menuEntry = menu[settings.menuPosX];
var item = menuEntry.items[settings.menuPosY-1]; var item = menuEntry.items[settings.menuPosY-1];
return item.run !== undefined; return item.run !== undefined;
} };
function runMenuItem(){ let runMenuItem = function() {
if(settings.menuPosY == 0){ if(settings.menuPosY == 0){
return; return;
} }
@ -171,13 +149,13 @@ function runMenuItem(){
} catch (ex) { } catch (ex) {
// Simply ignore it... // Simply ignore it...
} }
} };
/************************************************ /************************************************
* Draw * Draw
*/ */
function draw() { let draw = function() {
// Queue draw again // Queue draw again
queueDraw(); queueDraw();
@ -186,10 +164,10 @@ function draw() {
drawMenuAndTime(); drawMenuAndTime();
drawLock(); drawLock();
drawWidgets(); drawWidgets();
} };
function drawDate(){ let drawDate = function() {
// Draw background // Draw background
var y = H/5*2 + (isFullscreen() ? 0 : 8); var y = H/5*2 + (isFullscreen() ? 0 : 8);
g.reset().clearRect(0,0,W,y); g.reset().clearRect(0,0,W,y);
@ -216,10 +194,10 @@ function drawDate(){
g.setMediumFont(); g.setMediumFont();
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
g.drawString(dateStr, W/2 - fullDateW / 2, y+2); g.drawString(dateStr, W/2 - fullDateW / 2, y+2);
} };
function drawTime(y, smallText){ let drawTime = function(y, smallText) {
// Draw background // Draw background
var date = new Date(); var date = new Date();
@ -244,9 +222,9 @@ function drawTime(y, smallText){
g.setLargeFont(); g.setLargeFont();
} }
g.drawString(timeStr, W/2, y); g.drawString(timeStr, W/2, y);
} };
function drawMenuItem(text, image){ let drawMenuItem = function(text, image) {
// First clear the time region // First clear the time region
var y = H/5*2 + (isFullscreen() ? 0 : 8); var y = H/5*2 + (isFullscreen() ? 0 : 8);
@ -279,10 +257,10 @@ function drawMenuItem(text, image){
// Draw time // Draw time
drawTime(y, hasText); drawTime(y, hasText);
} };
function drawMenuAndTime(){ let drawMenuAndTime = function() {
var menuEntry = menu[settings.menuPosX]; var menuEntry = menu[settings.menuPosX];
// The first entry is the overview... // The first entry is the overview...
@ -292,37 +270,35 @@ function drawMenuAndTime(){
} }
// Draw item if needed // Draw item if needed
lock_input = true; var item = menuEntry.items[settings.menuPosY-1].get();
var item = menuEntry.items[settings.menuPosY-1]; drawMenuItem(item.text, item.img);
item.show(); };
}
let drawLock = function() {
function drawLock(){
if(settings.showLock && Bangle.isLocked()){ if(settings.showLock && Bangle.isLocked()){
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
g.drawImage(imgLock(), W-16, 2); g.drawImage(imgLock(), W-16, 2);
} }
} };
function drawWidgets(){ let drawWidgets = function() {
if(isFullscreen()){ if(isFullscreen()){
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";} for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
} else { } else {
Bangle.drawWidgets(); Bangle.drawWidgets();
} }
} };
function isFullscreen(){ let isFullscreen = function() {
var s = settings.screen.toLowerCase(); var s = settings.screen.toLowerCase();
if(s == "dynamic"){ if(s == "dynamic"){
return Bangle.isLocked() return Bangle.isLocked();
} else { } else {
return s == "full" return s == "full";
} }
} };
@ -330,29 +306,30 @@ function isFullscreen(){
* Listener * Listener
*/ */
// timeout used to update every minute // timeout used to update every minute
var drawTimeout; let drawTimeout;
// schedule a draw for the next minute // schedule a draw for the next minute
function queueDraw() { let queueDraw = function() {
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() { drawTimeout = setTimeout(function() {
drawTimeout = undefined; drawTimeout = undefined;
draw(); draw();
}, 60000 - (Date.now() % 60000)); }, 60000 - (Date.now() % 60000));
} };
// Stop updates when LCD is off, restart when on // Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{ let lcdListenerBw = function(on) {
if (on) { if (on) {
draw(); // draw immediately, queue redraw draw(); // draw immediately, queue redraw
} else { // stop draw timer } else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined; drawTimeout = undefined;
} }
}); };
Bangle.on('lcdPower', lcdListenerBw);
Bangle.on('lock', function(isLocked) { let lockListenerBw = function(isLocked) {
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined; drawTimeout = undefined;
@ -363,9 +340,10 @@ Bangle.on('lock', function(isLocked) {
} }
draw(); draw();
}); };
Bangle.on('lock', lockListenerBw);
Bangle.on('charging',function(charging) { let chargingListenerBw = function(charging) {
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined; drawTimeout = undefined;
@ -373,9 +351,10 @@ Bangle.on('charging',function(charging) {
settings.menuPosX = 0; settings.menuPosX = 0;
settings.menuPosY = 1; settings.menuPosY = 1;
draw(); draw();
}); };
Bangle.on('charging', chargingListenerBw);
Bangle.on('touch', function(btn, e){ let touchListenerBw = function(btn, e) {
var widget_size = isFullscreen() ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better... var widget_size = isFullscreen() ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better...
var left = parseInt(g.getWidth() * 0.22); var left = parseInt(g.getWidth() * 0.22);
var right = g.getWidth() - left; var right = g.getWidth() - left;
@ -388,10 +367,6 @@ Bangle.on('touch', function(btn, e){
var is_right = e.x > right && !is_upper && !is_lower; var is_right = e.x > right && !is_upper && !is_lower;
var is_center = !is_upper && !is_lower && !is_left && !is_right; var is_center = !is_upper && !is_lower && !is_left && !is_right;
if(lock_input){
return;
}
if(is_lower){ if(is_lower){
Bangle.buzz(40, 0.6); Bangle.buzz(40, 0.6);
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1); settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
@ -431,17 +406,11 @@ Bangle.on('touch', function(btn, e){
runMenuItem(); runMenuItem();
} }
} }
}); };
Bangle.on('touch', touchListenerBw);
E.on("kill", function(){
try{
storage.write(SETTINGS_FILE, settings);
} catch(ex){
// If this fails, we still kill the app...
}
});
let save = () => storage.write(SETTINGS_FILE, settings);
E.on("kill", save);
/************************************************ /************************************************
* Startup Clock * Startup Clock
@ -450,10 +419,26 @@ E.on("kill", function(){
// The upper part is inverse i.e. light if dark and dark if light theme // 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 // is enabled. In order to draw the widgets correctly, we invert the
// dark/light theme as well as the colors. // dark/light theme as well as the colors.
let themeBackup = g.theme;
g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear(); g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
// Show launcher when middle button pressed // Show launcher when middle button pressed
Bangle.setUI("clock"); Bangle.setUI({
mode : "clock",
remove : function() {
// Called to unload all of the clock app
Bangle.removeListener('lcdPower', lcdListenerBw);
Bangle.removeListener('lock', lockListenerBw);
Bangle.removeListener('charging', chargingListenerBw);
Bangle.removeListener('touch', touchListenerBw);
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
// save settings
save();
E.removeListener("kill", save);
g.setTheme(themeBackup);
}
});
// Load widgets and draw clock the first time // Load widgets and draw clock the first time
Bangle.loadWidgets(); Bangle.loadWidgets();
@ -464,3 +449,5 @@ for (let wd of WIDGETS) {wd._draw=wd.draw; wd._area=wd.area;}
// Draw first time // Draw first time
draw(); draw();
} // End of app scope

View File

@ -1,7 +1,7 @@
{ {
"id": "bwclk", "id": "bwclk",
"name": "BW Clock", "name": "BW Clock",
"version": "0.24", "version": "0.25",
"description": "A very minimalistic clock to mainly show date and time.", "description": "A very minimalistic clock to mainly show date and time.",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",

View File

@ -39,3 +39,4 @@
0.21: Remade all icons without a palette for dark theme 0.21: Remade all icons without a palette for dark theme
Now re-adds widgets if they were hidden when fast-loading Now re-adds widgets if they were hidden when fast-loading
0.22: Fixed crash if item has no image and cutting long overflowing text 0.22: Fixed crash if item has no image and cutting long overflowing text
0.23: Setting circles colours per clkinfo and not position

View File

@ -20,24 +20,12 @@ let settings = Object.assign(
storage.readJSON("circlesclock.default.json", true) || {}, storage.readJSON("circlesclock.default.json", true) || {},
storage.readJSON(SETTINGS_FILE, true) || {} storage.readJSON(SETTINGS_FILE, true) || {}
); );
//TODO deprecate this (and perhaps use in the clkinfo module)
// Load step goal from health app and pedometer widget as fallback
if (settings.stepGoal == undefined) {
let d = storage.readJSON("health.json", true) || {};
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.stepGoal : undefined;
if (settings.stepGoal == undefined) {
d = storage.readJSON("wpedom.json", true) || {};
settings.stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
}
}
let drawTimeout; let drawTimeout;
const showWidgets = settings.showWidgets || false; const showWidgets = settings.showWidgets || false;
const circleCount = settings.circleCount || 3; const circleCount = settings.circleCount || 3;
const showBigWeather = settings.showBigWeather || false; const showBigWeather = settings.showBigWeather || false;
let hrtValue; //TODO deprecate this
let now = Math.round(new Date().getTime() / 1000); let now = Math.round(new Date().getTime() / 1000);
// layout values: // layout values:
@ -128,8 +116,11 @@ let draw = function() {
queueDraw(); queueDraw();
} }
let getCircleColor = function(index) { let getCircleColor = function(item, clkmenu) {
let color = settings["circle" + index + "color"]; let colorKey = clkmenu.name;
if(!clkmenu.dynamic) colorKey += "/"+item.name;
colorKey += "_color";
let color = settings[colorKey];
if (color && color != "") return color; if (color && color != "") return color;
return g.theme.fg; return g.theme.fg;
} }
@ -138,7 +129,7 @@ let getGradientColor = function(color, percent) {
if (isNaN(percent)) percent = 0; if (isNaN(percent)) percent = 0;
if (percent > 1) percent = 1; if (percent > 1) percent = 1;
let colorList = [ let colorList = [
'#00FF00', '#80FF00', '#FFFF00', '#FF8000', '#FF0000' '#00ff00', '#80ff00', '#ffff00', '#ff8000', '#ff0000'
]; ];
if (color == "fg") { if (color == "fg") {
color = colorFg; color = colorFg;
@ -151,6 +142,17 @@ let getGradientColor = function(color, percent) {
let colorIndex = colorList.length - Math.round(colorList.length * percent); let colorIndex = colorList.length - Math.round(colorList.length * percent);
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000"; return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
} }
colorList = [
'#0000ff', '#8800ff', '#ff00ff', '#ff0088', '#ff0000'
];
if (color == "blue-red") {
let colorIndex = Math.round(colorList.length * percent);
return colorList[Math.min(colorIndex, colorList.length) - 1] || "#0000ff";
}
if (color == "red-blue") {
let colorIndex = colorList.length - Math.round(colorList.length * percent);
return colorList[Math.min(colorIndex, colorList.length)] || "#ff0000";
}
return color; return color;
} }
@ -172,10 +174,10 @@ let drawEmpty = function(img, w, color) {
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24}); .drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
} }
let drawCircle = function(index, item, data) { let drawCircle = function(index, item, data, clkmenu) {
var w = circlePosX[index-1]; var w = circlePosX[index-1];
drawCircleBackground(w); drawCircleBackground(w);
const color = getCircleColor(index); const color = getCircleColor(item, clkmenu);
//drawEmpty(info? info.img : null, w, color); //drawEmpty(info? info.img : null, w, color);
var img = data.img; var img = data.img;
var percent = 1; //fill up if no range var percent = 1; //fill up if no range
@ -338,7 +340,8 @@ Bangle.setUI({
let clockInfoDraw = (itm, info, options) => { let clockInfoDraw = (itm, info, options) => {
//print("Draw",itm.name,options); //print("Draw",itm.name,options);
drawCircle(options.circlePosition, itm, info); let clkmenu = clockInfoItems[options.menuA];
drawCircle(options.circlePosition, itm, info, clkmenu);
if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1) if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1)
}; };
let clockInfoItems = require("clock_info").load(); let clockInfoItems = require("clock_info").load();

View File

@ -3,23 +3,21 @@
"showWidgets": false, "showWidgets": false,
"weatherCircleData": "humidity", "weatherCircleData": "humidity",
"circleCount": 3, "circleCount": 3,
"circle1color": "green-red", "Bangle/Battery_color":"red-green",
"circle2color": "#0000ff", "Bangle/Steps_color":"#0000ff",
"circle3color": "red-green", "Bangle/HRM_color":"green-red",
"circle4color": "#ffff00", "Bangle/Altitude_color":"#00ff00",
"Weather/conditionWithTemperature_color":"#ffff00",
"Weather/condition_color":"#00ffff",
"Weather/humidity_color":"#00ffff",
"Weather/wind_color":"fg",
"Weather/temperature_color":"blue-red",
"Alarms_color":"#00ff00",
"Agenda_color":"#ff0000",
"circle1colorizeIcon": true, "circle1colorizeIcon": true,
"circle2colorizeIcon": true, "circle2colorizeIcon": true,
"circle3colorizeIcon": true, "circle3colorizeIcon": true,
"circle4colorizeIcon": false, "circle4colorizeIcon": false,
"updateInterval": 60, "updateInterval": 60,
"showBigWeather": false, "showBigWeather": false
"minHR": 40,
"maxHR": 200,
"confidence": 0,
"stepGoal": 10000,
"stepDistanceGoal": 8000,
"stepLength": 0.8,
"hrmValidity": 60
} }

View File

@ -1,7 +1,7 @@
{ "id": "circlesclock", { "id": "circlesclock",
"name": "Circles clock", "name": "Circles clock",
"shortName":"Circles clock", "shortName":"Circles clock",
"version":"0.22", "version":"0.23",
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style", "description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}], "screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],

View File

@ -12,12 +12,10 @@
storage.write(SETTINGS_FILE, settings); storage.write(SETTINGS_FILE, settings);
} }
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"]; "#00ffff", "#fff", "#000", "green-red", "red-green", "blue-red", "red-blue", "fg"];
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta", const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
"cyan", "white", "black", "green->red", "red->green", "foreground"]; "cyan", "white", "black", "green->red", "red->green", "blue->red", "red->blue", "foreground"];
const weatherData = ["empty", "humidity", "wind"];
function showMainMenu() { function showMainMenu() {
let menu ={ let menu ={
@ -30,31 +28,11 @@
step: 1, step: 1,
onchange: x => save('circleCount', x), onchange: x => save('circleCount', x),
}, },
/*LANG*/'circle 1': ()=>showCircleMenu(1),
/*LANG*/'circle 2': ()=>showCircleMenu(2),
/*LANG*/'circle 3': ()=>showCircleMenu(3),
/*LANG*/'circle 4': ()=>showCircleMenu(4),
/*LANG*/'battery warn': {
value: settings.batteryWarn,
min: 10,
max : 100,
step: 10,
format: x => {
return x + '%';
},
onchange: x => save('batteryWarn', x),
},
/*LANG*/'show widgets': { /*LANG*/'show widgets': {
value: !!settings.showWidgets, value: !!settings.showWidgets,
format: () => (settings.showWidgets ? 'Yes' : 'No'), format: () => (settings.showWidgets ? 'Yes' : 'No'),
onchange: x => save('showWidgets', x), onchange: x => save('showWidgets', x),
}, },
/*LANG*/'weather data': {
value: weatherData.indexOf(settings.weatherCircleData),
min: 0, max: 2,
format: v => weatherData[v],
onchange: x => save('weatherCircleData', weatherData[x]),
},
/*LANG*/'update interval': { /*LANG*/'update interval': {
value: settings.updateInterval, value: settings.updateInterval,
min: 0, min: 0,
@ -65,41 +43,54 @@
}, },
onchange: x => save('updateInterval', x), onchange: x => save('updateInterval', x),
}, },
//TODO deprecated local icons, may disappear in future
/*LANG*/'legacy weather icons': {
value: !!settings.legacyWeatherIcons,
format: () => (settings.legacyWeatherIcons ? 'Yes' : 'No'),
onchange: x => save('legacyWeatherIcons', x),
},
/*LANG*/'show big weather': { /*LANG*/'show big weather': {
value: !!settings.showBigWeather, value: !!settings.showBigWeather,
format: () => (settings.showBigWeather ? 'Yes' : 'No'), format: () => (settings.showBigWeather ? 'Yes' : 'No'),
onchange: x => save('showBigWeather', x), onchange: x => save('showBigWeather', x),
} },
/*LANG*/'colorize icons': ()=>showCircleMenus()
}; };
clock_info.load().forEach(e=>{
if(e.dynamic) {
const colorKey = e.name + "_color";
menu[e.name+/*LANG*/' color'] = {
value: valuesColors.indexOf(settings[colorKey]) || 0,
min: 0, max: valuesColors.length - 1,
format: v => namesColors[v],
onchange: x => save(colorKey, valuesColors[x]),
};
} else {
let values = e.items.map(i=>e.name+"/"+i.name);
let names = e.name=="Bangle" ? e.items.map(i=>i.name) : values;
values.forEach((v,i)=>{
const colorKey = v + "_color";
menu[names[i]+/*LANG*/' color'] = {
value: valuesColors.indexOf(settings[colorKey]) || 0,
min: 0, max: valuesColors.length - 1,
format: v => namesColors[v],
onchange: x => save(colorKey, valuesColors[x]),
};
});
}
})
E.showMenu(menu); E.showMenu(menu);
} }
function showCircleMenu(circleId) { function showCircleMenus() {
const circleName = "circle" + circleId; const menu = {
const colorKey = circleName + "color"; '': { 'title': /*LANG*/'Colorize icons'},
const colorizeIconKey = circleName + "colorizeIcon"; /*LANG*/'< Back': ()=>showMainMenu(),
};
const menu = { for(var circleId=1; circleId<=4; ++circleId) {
'': { 'title': /*LANG*/'Circle ' + circleId }, const circleName = "circle" + circleId;
/*LANG*/'< Back': ()=>showMainMenu(), const colorKey = circleName + "color";
/*LANG*/'color': { const colorizeIconKey = circleName + "colorizeIcon";
value: valuesColors.indexOf(settings[colorKey]) || 0, menu[/*LANG*/'circle ' + circleId] = {
min: 0, max: valuesColors.length - 1,
format: v => namesColors[v],
onchange: x => save(colorKey, valuesColors[x]),
},
/*LANG*/'colorize icon': {
value: settings[colorizeIconKey] || false, value: settings[colorizeIconKey] || false,
format: () => (settings[colorizeIconKey] ? 'Yes' : 'No'), format: () => (settings[colorizeIconKey]? /*LANG*/'Yes': /*LANG*/'No'),
onchange: x => save(colorizeIconKey, x), onchange: x => save(colorizeIconKey, x),
}, };
}; }
E.showMenu(menu); E.showMenu(menu);
} }

View File

@ -4,3 +4,4 @@
0.04: Now displays the opened text string at launch. 0.04: Now displays the opened text string at launch.
0.05: Now scrolls text when string gets longer than screen width. 0.05: Now scrolls text when string gets longer than screen width.
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present. 0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
0.07: Settings for display colors

View File

@ -12,5 +12,8 @@ Known bugs:
- Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes. - Initially developed for use with dark theme set on Bangle.js 2 - that is still the preferred way to view it although it now works with other themes.
- When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case. - When repeatedly doing 'del' on an empty text-string, the letter case is changed back and forth between upper and lower case.
To do: Settings:
- Possibly provide a dragboard.settings.js file - CAPS LOCK: all characters are displayed and typed in uppercase
- ABC Color: color of the characters row
- Num Color: color of the digits and symbols row
- Highlight Color: color of the currently highlighted character

View File

@ -2,12 +2,14 @@ exports.input = function(options) {
options = options||{}; options = options||{};
var text = options.text; var text = options.text;
if ("string"!=typeof text) text=""; if ("string"!=typeof text) text="";
let settings = require('Storage').readJSON('dragboard.json',1)||{}
var R = Bangle.appRect; var R = Bangle.appRect;
const paramToColor = (param) => g.toColor(`#${settings[param].toString(16).padStart(3,0)}`);
var BGCOLOR = g.theme.bg; var BGCOLOR = g.theme.bg;
var HLCOLOR = g.theme.fg; var HLCOLOR = settings.Highlight ? paramToColor("Highlight") : g.theme.fg;
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000'; var ABCCOLOR = settings.ABC ? paramToColor("ABC") : g.toColor(1,0,0);//'#FF0000';
var NUMCOLOR = g.toColor(0,1,0);//'#00FF00'; var NUMCOLOR = settings.Num ? paramToColor("Num") : g.toColor(0,1,0);//'#00FF00';
var BIGFONT = '6x8:3'; var BIGFONT = '6x8:3';
var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1))); var BIGFONTWIDTH = parseInt(BIGFONT.charAt(0)*parseInt(BIGFONT.charAt(-1)));
var SMALLFONT = '6x8:1'; var SMALLFONT = '6x8:1';
@ -102,6 +104,7 @@ exports.input = function(options) {
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise. //setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
function changeCase(abcHL) { function changeCase(abcHL) {
if (settings.uppercase) return;
g.setColor(BGCOLOR); g.setColor(BGCOLOR);
g.setFontAlign(-1, -1, 0); g.setFontAlign(-1, -1, 0);
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2); g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);

View File

@ -1,6 +1,6 @@
{ "id": "dragboard", { "id": "dragboard",
"name": "Dragboard", "name": "Dragboard",
"version":"0.06", "version":"0.07",
"description": "A library for text input via swiping keyboard", "description": "A library for text input via swiping keyboard",
"icon": "app.png", "icon": "app.png",
"type":"textinput", "type":"textinput",
@ -9,6 +9,7 @@
"screenshots": [{"url":"screenshot.png"}], "screenshots": [{"url":"screenshot.png"}],
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"textinput","url":"lib.js"} {"name":"textinput","url":"lib.js"},
{"name":"dragboard.settings.js","url":"settings.js"}
] ]
} }

View File

@ -0,0 +1,48 @@
(function(back) {
let settings = require('Storage').readJSON('dragboard.json',1)||{};
const colors = {
4095: /*LANG*/"White",
4080: /*LANG*/"Yellow",
3840: /*LANG*/"Red",
3855: /*LANG*/"Magenta",
255: /*LANG*/"Cyan",
240: /*LANG*/"Green",
15: /*LANG*/"Blue",
0: /*LANG*/"Black",
'-1': /*LANG*/"Default"
};
const save = () => require('Storage').write('dragboard.json', settings);
function colorMenu(key) {
let menu = {'': {title: key}, '< Back': () => E.showMenu(appMenu)};
Object.keys(colors).forEach(color => {
var label = colors[color];
menu[label] = {
value: settings[key] == color,
onchange: () => {
if (color >= 0) {
settings[key] = color;
} else {
delete settings[key];
}
save();
setTimeout(E.showMenu, 10, appMenu);
}
};
});
return menu;
}
const appMenu = {
'': {title: 'Dragboard'}, '< Back': back,
/*LANG*/'CAPS LOCK': {
value: !!settings.uppercase,
onchange: v => {settings.uppercase = v; save();}
},
/*LANG*/'ABC Color': () => E.showMenu(colorMenu("ABC")),
/*LANG*/'Num Color': () => E.showMenu(colorMenu("Num")),
/*LANG*/'Highlight Color': () => E.showMenu(colorMenu("Highlight"))
};
E.showMenu(appMenu);
});

View File

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

View File

@ -0,0 +1,20 @@
# Gemini clock
A simple clock face using the Buro Destruct Geminis font, inspired by their Pebble Watch designs: https://burodestruct.net/work/pebble-watchfaces
![image](watch-in-use.jpg)
It is designed for maximum legibility and utility whilst still showing widgets.
If editing or remixing this code, please retain leading zeroes on the hours, they are an integral part of the design.
The minutes are not right-aligned deliberately so that the numbers don't jump around too much when they change.
## Creator
Created by Giles Booth:
- http://www.suppertime.co.uk/blogmywiki/
- https://mastodon.social/@blogmywiki
- https://github.com/blogmywiki

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4f/AoP//+iiE00u++/nnMooWSyhT/AA8C9u27dtAQNkgEKAwdQCIUD5MkyVJAQNOwG9DIf+oARBgIPDAQOZoHSA4cj0ARIyeA6AIDpnAI4X2FgXcwgRBp0SDQYRCgErKAVt+ARBi4HC3AREAAnMCIIFCgN4CJOuiYRDgFmCKFXhIR/CMSPFCI0reYazCCJEDuzXGUIvoCIXCfY0Brdt284pOfqjLBBwQCCzNArf///9DoMx3gaBEAYTBnmA5wZCiQDB4+QCIeY+3bFgPQFg3QCIeXKwdMCJfOCIcbI4gRLhIhCvARMR4oRPWYIR/CNNnCI+Z9u20AREtCPHzMbtv8wEXv7GB3ARHAQXJoGuA4VCCIjdCCIWTwHQfY8DnIHDAQNACI+AgNvHwIACI4Nd23btoCC3x3BEQsggEKCAm26iIGAH4ATA=="))

BIN
apps/geminiclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,17 @@
{ "id": "geminiclock",
"name": "Gemini clock",
"shortName":"Gemini Clock",
"icon": "app.png",
"version":"0.01",
"description": "Watch face using retro Gemini font",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"geminiclock.app.js","url":"gemini-watch-app.js"},
{"name":"geminiclock.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@ -7,3 +7,4 @@
0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway) 0.08: Leave GPS power switched on on exit (will switch off after 0.5 seconds anyway)
0.09: Fix FIFO_FULL error 0.09: Fix FIFO_FULL error
0.10: Show satellites "in view" separated by GNS-system 0.10: Show satellites "in view" separated by GNS-system
0.11: Show number of packets received

View File

@ -5,7 +5,7 @@ function satelliteImage() {
var Layout = require("Layout"); var Layout = require("Layout");
var layout; var layout;
//Bangle.setGPSPower(1, "app"); //Bangle.setGPSPower(1, "app");
E.showMessage(/*LANG*/"Loading..."); // avoid showing rubbish on screen E.showMessage(/*LANG*/"Waiting for GNS data..."); // avoid showing rubbish on screen
var lastFix = { var lastFix = {
fix: -1, fix: -1,
@ -19,6 +19,7 @@ var lastFix = {
var SATinView = 0, lastSATinView = -1, nofGP = 0, nofBD = 0, nofGL = 0; var SATinView = 0, lastSATinView = -1, nofGP = 0, nofBD = 0, nofGL = 0;
const leaveNofixLayout = 1; // 0 = stay on initial screen for debugging (default = 1) const leaveNofixLayout = 1; // 0 = stay on initial screen for debugging (default = 1)
var listenerGPSraw = 0; var listenerGPSraw = 0;
var dataCounter = 0;
function formatTime(now) { function formatTime(now) {
if (now == undefined) { if (now == undefined) {
@ -80,11 +81,15 @@ function onGPS(fix) {
type:"v", c: [ type:"v", c: [
{type:"txt", font:"6x8:2", label:"GPS Info" }, {type:"txt", font:"6x8:2", label:"GPS Info" },
{type:"img", src:satelliteImage, pad:4 }, {type:"img", src:satelliteImage, pad:4 },
{type:"txt", font:"6x8", label:"Waiting for GPS" }, {type:"txt", font:"6x8", label:"Waiting for GPS fix" },
{type:"h", c: [ {type:"h", c: [
{type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" }, {type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" },
{type:"txt", font:"6x8", pad:3, label:"Satellites used" } {type:"txt", font:"6x8", pad:3, label:"Satellites used" }
]}, ]},
{type:"h", c: [
{type:"txt", font:"10%", label:dataCounter, pad:2, id:"dataCounter" },
{type:"txt", font:"6x8", pad:3, label:"packets received" }
]},
{type:"txt", font:"6x8", label:"", fillx:true, id:"progress" } {type:"txt", font:"6x8", label:"", fillx:true, id:"progress" }
]},{lazy:false}); ]},{lazy:false});
} }
@ -122,6 +127,9 @@ function onGPS(fix) {
layout.progress.label = "in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL; layout.progress.label = "in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL;
// console.log("in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL); // console.log("in view GP/BD/GL: " + nofGP + " " + nofBD + " " + nofGL);
layout.render(layout.progress); layout.render(layout.progress);
layout.clear(layout.dataCounter);
layout.dataCounter.label = ++dataCounter;
layout.render(layout.dataCounter);
} }
} }

View File

@ -1,11 +1,11 @@
{ {
"id": "gpsinfo", "id": "gpsinfo",
"name": "GPS Info", "name": "GPS Info",
"version": "0.10", "version": "0.11",
"description": "An application that displays information about altitude, lat/lon, satellites and time", "description": "An application that displays information about latitude, longitude, altitude, speed, satellites and time",
"icon": "gps-info.png", "icon": "gps-info.png",
"type": "app", "type": "app",
"tags": "gps,outdoors", "tags": "gps,outdoors,tools",
"supports": ["BANGLEJS","BANGLEJS2"], "supports": ["BANGLEJS","BANGLEJS2"],
"storage": [ "storage": [
{"name":"gpsinfo.app.js","url":"gps-info.js"}, {"name":"gpsinfo.app.js","url":"gps-info.js"},

View File

@ -16,3 +16,4 @@
0.15: Fix charts (fix #1366) 0.15: Fix charts (fix #1366)
0.16: Code tidyup, add back button in top left of health app graphs 0.16: Code tidyup, add back button in top left of health app graphs
0.17: Add automatic translation of bar chart labels 0.17: Add automatic translation of bar chart labels
0.18: Show step goal in daily step chart

View File

@ -1,6 +1,6 @@
# Health Tracking # Health Tracking
Logs health data to a file every 10 minutes, and provides an app to view it Logs health data to a file in a defined interval, and provides an app to view it
**BETA - requires firmware 2v11 or later** **BETA - requires firmware 2v11 or later**
@ -22,6 +22,7 @@ Stores:
* **Heart Rt** - Whether to monitor heart rate or not * **Heart Rt** - Whether to monitor heart rate or not
* **Off** - Don't turn HRM on, but record heart rate if the HRM was turned on by another app/widget * **Off** - Don't turn HRM on, but record heart rate if the HRM was turned on by another app/widget
* **3 Min** - Turn HRM on every 3 minutes (for each heath entry) and turn it off after 1 minute, or when a good reading is found
* **10 Min** - Turn HRM on every 10 minutes (for each heath entry) and turn it off after 2 minutes, or when a good reading is found * **10 Min** - Turn HRM on every 10 minutes (for each heath entry) and turn it off after 2 minutes, or when a good reading is found
* **Always** - Keep HRM on all the time (more accurate recording, but reduces battery life to ~36 hours) * **Always** - Keep HRM on all the time (more accurate recording, but reduces battery life to ~36 hours)
* **Daily Step Goal** - Default 10000, daily step goal for pedometer apps to use * **Daily Step Goal** - Default 10000, daily step goal for pedometer apps to use
@ -49,3 +50,7 @@ and run `EspruinoDocs/bin/minify.js lib.js lib.min.js`
* Yearly view * Yearly view
* Heart rate 'zone' graph * Heart rate 'zone' graph
* .. other * .. other
## License
The graphs on the web interface use Chart.js, licensed under MIT License.

View File

@ -38,6 +38,7 @@ function menuHRM() {
function stepsPerHour() { function stepsPerHour() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
current_selection = "stepsPerHour";
var 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);
setButton(menuStepCount); setButton(menuStepCount);
@ -46,14 +47,17 @@ function stepsPerHour() {
function stepsPerDay() { function stepsPerDay() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
current_selection = "stepsPerDay";
var 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);
setButton(menuStepCount); setButton(menuStepCount);
barChart(/*LANG*/"DAY", data); barChart(/*LANG*/"DAY", data);
drawHorizontalLine(settings.stepGoal);
} }
function hrmPerHour() { function hrmPerHour() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
current_selection = "hrmPerHour";
var data = new Uint16Array(24); var data = new Uint16Array(24);
var cnt = new Uint8Array(23); var cnt = new Uint8Array(23);
require("health").readDay(new Date(), h=>{ require("health").readDay(new Date(), h=>{
@ -67,6 +71,7 @@ function hrmPerHour() {
function hrmPerDay() { function hrmPerDay() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
current_selection = "hrmPerDay";
var data = new Uint16Array(31); var data = new Uint16Array(31);
var cnt = new Uint8Array(31); var cnt = new Uint8Array(31);
require("health").readDailySummaries(new Date(), h=>{ require("health").readDailySummaries(new Date(), h=>{
@ -80,6 +85,7 @@ function hrmPerDay() {
function movementPerHour() { function movementPerHour() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
current_selection = "movementPerHour";
var 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);
setButton(menuMovement); setButton(menuMovement);
@ -88,6 +94,7 @@ function movementPerHour() {
function movementPerDay() { function movementPerDay() {
E.showMessage(/*LANG*/"Loading..."); E.showMessage(/*LANG*/"Loading...");
current_selection = "movementPerDay";
var 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);
setButton(menuMovement); setButton(menuMovement);
@ -97,12 +104,14 @@ function movementPerDay() {
// Bar Chart Code // Bar Chart Code
const w = g.getWidth(); const w = g.getWidth();
const h = g.getHeight(); const h = g.getHeight();
const bar_bot = 140;
var data_len; var data_len;
var chart_index; var chart_index;
var chart_max_datum; var chart_max_datum;
var chart_label; var chart_label;
var chart_data; var chart_data;
var current_selection;
// find the max value in the array, using a loop due to array size // find the max value in the array, using a loop due to array size
function max(arr) { function max(arr) {
@ -131,7 +140,6 @@ function barChart(label, dt) {
} }
function drawBarChart() { function drawBarChart() {
const bar_bot = 140;
const bar_width = (w - 2) / 9; // we want 9 bars, bar 5 in the centre const bar_width = (w - 2) / 9; // we want 9 bars, bar 5 in the centre
var bar_top; var bar_top;
var bar; var bar;
@ -157,6 +165,11 @@ function drawBarChart() {
} }
} }
function drawHorizontalLine(value) {
const top = bar_bot - 100 * value / chart_max_datum;
g.setColor(g.theme.fg).drawLine(0, top ,g.getWidth(), top);
}
function setButton(fn) { function setButton(fn) {
Bangle.setUI({mode:"custom", Bangle.setUI({mode:"custom",
back:fn, back:fn,
@ -170,9 +183,13 @@ function setButton(fn) {
return fn(); return fn();
} }
drawBarChart(); drawBarChart();
if (current_selection == "stepsPerDay") {
drawHorizontalLine(settings.stepGoal);
}
}}); }});
} }
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
var settings = require("Storage").readJSON("health.json",1)||{};
menuMain(); menuMain();

14
apps/health/chart.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -3,9 +3,10 @@
<link rel="stylesheet" href="../../css/spectre.min.css"> <link rel="stylesheet" href="../../css/spectre.min.css">
</head> </head>
<body> <body>
<div id="table"></div> <div id="content"></div>
<script src="../../core/lib/interface.js"></script> <script src="../../core/lib/interface.js"></script>
<script type="module" src="chart.min.js"></script>
<script> <script>
const DB_RECORD_LEN = 4; const DB_RECORD_LEN = 4;
const DB_RECORDS_PER_HR = 6; const DB_RECORDS_PER_HR = 6;
@ -14,7 +15,7 @@ const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31;
const DB_HEADER_LEN = 8; const DB_HEADER_LEN = 8;
const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN; const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN;
var domTable = document.getElementById("table"); var domContent = document.getElementById("content");
function saveCSV(data, date, title) { function saveCSV(data, date, title) {
// date = "2021-9"/ etc // date = "2021-9"/ etc
@ -59,7 +60,7 @@ function downloadHealth(filename, callback) {
} }
function getMonthList() { function getMonthList() {
Util.showModal("Loading..."); Util.showModal("Loading...");
domTable.innerHTML = ""; domContent.innerHTML = "";
Puck.eval(`require("Storage").list(/^health-.*\\.raw$/)`,files=>{ Puck.eval(`require("Storage").list(/^health-.*\\.raw$/)`,files=>{
files = files.map(f => { files = files.map(f => {
var m = f.match(/^health-([^\.]+)\.raw$/); var m = f.match(/^health-([^\.]+)\.raw$/);
@ -69,7 +70,7 @@ function getMonthList() {
str : new Date(m[1]).toLocaleString(undefined, {month:'long',year:'numeric'}) str : new Date(m[1]).toLocaleString(undefined, {month:'long',year:'numeric'})
} }
}) })
var html = `<table class="table table-striped table-hover"> var htmlOverview = `<table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Month</th> <th>Month</th>
@ -78,36 +79,39 @@ function getMonthList() {
</thead> </thead>
<tbody>\n`; <tbody>\n`;
files.forEach(f => { files.forEach(f => {
html += ` htmlOverview += `
<tr> <tr>
<td>${f.str}</td> <td>${f.str}</td>
<td> <td>
<button class="btn btn-primary" filename="${f.filename}" date="${f.date}" task="downloadcsv">Download CSV</button> <button class="btn btn-primary" filename="${f.filename}" date="${f.date}" task="downloadcsv">Download CSV</button>
<button class="btn btn-default" filename="${f.filename}" date="${f.date}" task="delete">Delete</button> <button class="btn btn-primary" filename="${f.filename}" date="${f.date}" monthstr="${f.str}" task="monthtable">Table</button>
<button class="btn btn-primary" filename="${f.filename}" date="${f.date}" monthstr="${f.str}" task="monthgraph">Graph</button>
<button class="btn btn-error" filename="${f.filename}" date="${f.date}" task="delete" style="float: right;margin-right: 5px;">Delete</button>
</td> </td>
</tr> </tr>
`; `;
}); });
if (files.length==0) { if (files.length==0) {
html += ` htmlOverview += `
<tr> <tr>
<td>No data recorded</td> <td>No data recorded</td>
<td></td> <td></td>
</tr> </tr>
`; `;
} }
html += ` htmlOverview += `
</tbody> </tbody>
</table>`; </table>`;
domTable.innerHTML = html; domContent.innerHTML = htmlOverview;
Util.hideModal(); Util.hideModal();
var buttons = domTable.querySelectorAll("button"); var buttons = domContent.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) { for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => { buttons[i].addEventListener("click",event => {
var button = event.currentTarget; var button = event.currentTarget;
var filename = button.getAttribute("filename"); var filename = button.getAttribute("filename");
var date = button.getAttribute("date"); var date = button.getAttribute("date");
if (!filename || !date) return; if (!filename || !date) return;
var monthstr = button.getAttribute("monthstr");
var task = button.getAttribute("task"); var task = button.getAttribute("task");
if (task=="delete") { if (task=="delete") {
Util.showModal("Deleting..."); Util.showModal("Deleting...");
@ -119,11 +123,179 @@ function getMonthList() {
if (task=="downloadcsv") { if (task=="downloadcsv") {
downloadHealth(filename, data => saveCSV(data, date, `Bangle.js Health ${date}`)); downloadHealth(filename, data => saveCSV(data, date, `Bangle.js Health ${date}`));
} }
if (task=="monthtable") {
viewMonthDataAsTable(filename, date, monthstr);
}
if (task=="monthgraph") {
viewMonthDataAsGraph(filename, date, monthstr);
}
}); });
} }
}) })
} }
function getDailyData(data) {
var dailyData = [];
var idx = DB_HEADER_LEN;
for (var day = 0; day < 31; day++) {
var dayData = {steps: 0, bpm: 0, movement: 0};
for (var hr = 0; hr < 24; hr++) { // actually 25, see below
for (var m = 0; m < DB_RECORDS_PER_HR; m++) {
var h = data.substr(idx, DB_RECORD_LEN);
if (h != "\xFF\xFF\xFF\xFF") {
var h = {
day : day + 1,
hr : hr,
min : m * 10,
steps : (h.charCodeAt(0) << 8) | h.charCodeAt(1),
bpm : h.charCodeAt(2),
movement : h.charCodeAt(3)
};
dayData.steps += h.steps; // sum
dayData.bpm = (dayData.bpm + h.bpm) / 2; // average
dayData.movement += h.movement; // sum
}
idx += DB_RECORD_LEN;
}
}
idx += DB_RECORD_LEN; // +1 because we have an extra record with totals
// for the end of the day
dailyData[day + 1] = dayData;
}
return dailyData;
}
function viewMonthDataAsTable(filename, date, monthstr) {
Util.showModal("Reading Health info...");
Util.readStorage(
filename, data => {
Util.hideModal();
var htmlOverview = `<h1>` + monthstr + `</ h1>
<button class="btn btn-primary" id="backtomonth" style="float: right;margin-right: 5px;">Back</button>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Day</th>
<th>Steps</th>
<th>BPM</th>
<th>Movement</th>
</tr>
</thead>
<tbody>\n`;
var dailyData = getDailyData(data);
for (var i = 1; i < dailyData.length + 1; i++) {
var dayData = dailyData[i];
if (dayData) {
htmlOverview += `<tr>
<td>${i}</td>
<td>${dayData.steps}</td>
<td>${Math.round(dayData.bpm)}</td>
<td>${dayData.movement}</td></tr>`
}
}
htmlOverview += `</tbody></table>`;
domContent.innerHTML = htmlOverview;
domContent.querySelector("#backtomonth").addEventListener("click",event => {
getMonthList();
});
});
}
function viewMonthDataAsGraph(filename, date, monthstr) {
Util.showModal("Reading Health info...");
Util.readStorage(
filename, data => {
Util.hideModal();
var html = `<h1>` + monthstr + `</ h1>
<button class="btn btn-primary" id="backtomonth" style="float: right;margin-right: 5px;">Back</button>
<h2>Steps</h2>
<canvas id="chartSteps"></canvas>
<h2>BPM</h2>
<canvas id="chartBPM"></canvas>
<h2>Movement</h2>
<canvas id="chartMovement"></canvas>`
domContent.innerHTML = html;
domContent.querySelector("#backtomonth").addEventListener("click",event => {
getMonthList();
});
var labels = [];
var dataSteps = [], dataBPM = [], dataMovement = [];
var dailyData = getDailyData(data);
for (var i = 1; i < dailyData.length + 1; i++) {
var dayData = dailyData[i];
if (dayData) {
labels.push(i);
dataSteps.push(dayData.steps);
dataBPM.push(dayData.bpm);
dataMovement.push(dayData.movement);
}
}
new Chart(document.getElementById("chartSteps"), {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '# of steps',
data: dataSteps,
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
new Chart(document.getElementById("chartBPM"), {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Beats per minute',
data: dataBPM,
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
new Chart(document.getElementById("chartMovement"), {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Movement',
data: dataMovement,
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
});
}
function onInit() { function onInit() {
getMonthList(); getMonthList();
} }

View File

@ -60,7 +60,7 @@ exports.readDailySummaries = function(d, cb) {
} }
} }
// Read all records from the given month // Read all records from the given day
exports.readDay = function(d, cb) { exports.readDay = function(d, cb) {
var rec = getRecordIdx(d); var rec = getRecordIdx(d);
var fn = getRecordFN(d); var fn = getRecordFN(d);

View File

@ -2,7 +2,7 @@
"id": "health", "id": "health",
"name": "Health Tracking", "name": "Health Tracking",
"shortName": "Health", "shortName": "Health",
"version": "0.17", "version": "0.18",
"description": "Logs health data and provides an app to view it", "description": "Logs health data and provides an app to view it",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,health", "tags": "tool,system,health",

View File

@ -2,6 +2,7 @@
"id": "infoclk", "id": "infoclk",
"name": "Informational clock", "name": "Informational clock",
"version": "0.08", "version": "0.08",
"dependencies": {"weather":"app"},
"description": "A configurable clock with extra info and shortcuts when unlocked, but large time when locked", "description": "A configurable clock with extra info and shortcuts when unlocked, but large time when locked",
"readme": "README.md", "readme": "README.md",
"icon": "icon.png", "icon": "icon.png",
@ -10,7 +11,6 @@
"supports": [ "supports": [
"BANGLEJS2" "BANGLEJS2"
], ],
"allow_emulator": true,
"storage": [ "storage": [
{ {
"name": "infoclk.app.js", "name": "infoclk.app.js",
@ -35,4 +35,4 @@
"name": "infoclk.json" "name": "infoclk.json"
} }
] ]
} }

View File

@ -20,5 +20,6 @@
0.20: Use alarm for alarm functionality instead of own implementation. 0.20: Use alarm for alarm functionality instead of own implementation.
0.21: Add custom theming. 0.21: Add custom theming.
0.22: Fix alarm and add build in function for step counting. 0.22: Fix alarm and add build in function for step counting.
0.23: Add warning for low flash memory 0.23: Add warning for low flash memory.
0.24: Add ability to disable alarm functionality 0.24: Add ability to disable alarm functionality.
0.25: Add more colors to the settings and add the ability to disable the data charts+Markup.

View File

@ -19,6 +19,8 @@ the "sched" app must be installed on your device.
* The lower orange line indicates the battery level. * The lower orange line indicates the battery level.
* Display graphs (day or month) for steps + hrm on the second screen. * Display graphs (day or month) for steps + hrm on the second screen.
* Customizable theming colors in the settings menu of the app. * Customizable theming colors in the settings menu of the app.
* Enable or disable the alarm feature.
* Enable or disbale the graphs for steps + hrm.
## Data that can be configured ## Data that can be configured
* Steps - Steps loaded via the wpedom app. * Steps - Steps loaded via the wpedom app.

View File

@ -13,6 +13,7 @@ let settings = {
themeColor2BG: "#FF00DC", themeColor2BG: "#FF00DC",
themeColor3BG: "#0094FF", themeColor3BG: "#0094FF",
disableAlarms: false, disableAlarms: false,
disableData: 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) {
@ -55,7 +56,7 @@ function convert24to16(input)
return "0x"+RGB565.toString(16); return "0x"+RGB565.toString(16);
} }
var color1C = convert24to16(color1); var color1C = convert24to16(color1);//Converting colors to the correct format.
var color2C = convert24to16(color2); var color2C = convert24to16(color2);
var color3C = convert24to16(color3); var color3C = convert24to16(color3);
@ -63,7 +64,7 @@ var color3C = convert24to16(color3);
* Requirements and globals * Requirements and globals
*/ */
var colorPalette = new Uint16Array([ var colorPalette = new Uint16Array([//Used to change the color of the image if the user selects a color that is diffrent than the default.
0x0000, // not used 0x0000, // not used
color2C, // second color2C, // second
color3C, // third color3C, // third
@ -708,18 +709,20 @@ Bangle.on('touch', function(btn, e){
var is_right = e.x > right; var is_right = e.x > right;
var is_upper = e.y < upper; var is_upper = e.y < upper;
var is_lower = e.y > lower; var is_lower = e.y > lower;
if(!settings.disableData){
if(is_left && lcarsViewPos == 1){
feedback();
lcarsViewPos = 0;
draw();
return;
if(is_left && lcarsViewPos == 1){ } else if(is_right && lcarsViewPos == 0){
feedback(); feedback();
lcarsViewPos = 0; lcarsViewPos = 1;
draw(); draw();
return; return;
}
} else if(is_right && lcarsViewPos == 0){
feedback();
lcarsViewPos = 1;
draw();
return;
} }
if(lcarsViewPos == 0){ if(lcarsViewPos == 0){

View File

@ -14,6 +14,7 @@
themeColor2BG: "#FF00DC", themeColor2BG: "#FF00DC",
themeColor3BG: "#0094FF", themeColor3BG: "#0094FF",
disableAlarms: false, disableAlarms: false,
disableData: 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) {
@ -27,8 +28,8 @@
var dataOptions = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"]; var dataOptions = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"];
var speedOptions = ["kph", "mph"]; var speedOptions = ["kph", "mph"];
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White']; var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green'];
var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF']; var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F'];
E.showMenu({ E.showMenu({
'': { 'title': 'LCARS Clock' }, '': { 'title': 'LCARS Clock' },
@ -79,7 +80,7 @@
}, },
'Theme Color 1': { 'Theme Color 1': {
value: 0 | bg_code.indexOf(settings.themeColor1BG), value: 0 | bg_code.indexOf(settings.themeColor1BG),
min: 0, max: 7, min: 0, max: 11,
format: v => color_options[v], format: v => color_options[v],
onchange: v => { onchange: v => {
settings.themeColor1BG = bg_code[v]; settings.themeColor1BG = bg_code[v];
@ -88,7 +89,7 @@
}, },
'Theme Color 2': { 'Theme Color 2': {
value: 0 | bg_code.indexOf(settings.themeColor2BG), value: 0 | bg_code.indexOf(settings.themeColor2BG),
min: 0, max: 7, min: 0, max: 11,
format: v => color_options[v], format: v => color_options[v],
onchange: v => { onchange: v => {
settings.themeColor2BG = bg_code[v]; settings.themeColor2BG = bg_code[v];
@ -97,7 +98,7 @@
}, },
'Theme Color 3': { 'Theme Color 3': {
value: 0 | bg_code.indexOf(settings.themeColor3BG), value: 0 | bg_code.indexOf(settings.themeColor3BG),
min: 0, max: 7, min: 0, max: 11,
format: v => color_options[v], format: v => color_options[v],
onchange: v => { onchange: v => {
settings.themeColor3BG = bg_code[v]; settings.themeColor3BG = bg_code[v];
@ -112,5 +113,13 @@
save(); save();
}, },
}, },
'Disable data pages functionality': {
value: settings.disableData,
format: () => (settings.disableData ? 'Yes' : 'No'),
onchange: () => {
settings.disableData = !settings.disableData;
save();
},
},
}); });
}) })

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock", "name": "LCARS Clock",
"shortName":"LCARS", "shortName":"LCARS",
"icon": "lcars.png", "icon": "lcars.png",
"version":"0.24", "version":"0.25",
"readme": "README.md", "readme": "README.md",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.", "description": "Library Computer Access Retrieval System (LCARS) clock.",

View File

@ -1,3 +1,4 @@
0.01: New App. 0.01: New App.
0.02: Performance improvements. 0.02: Performance improvements.
0.03: Update clock_info to avoid a redraw 0.03: Update clock_info to avoid a redraw
0.04: Fix clkinfo -- use .get instead of .show

View File

@ -76,21 +76,6 @@ var H = g.getHeight();
var menu = clock_info.load(); var menu = clock_info.load();
menu = menu.concat(dateMenu); menu = menu.concat(dateMenu);
// Set draw functions for each item
menu.forEach((menuItm, x) => {
menuItm.items.forEach((item, y) => {
function drawItem() {
item.hide();
var info = item.get();
drawText(item.name, info.text, (y%4)+1);
}
item.on('redraw', drawItem);
})
});
// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it. // Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){ if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
settings.menuPosX = 0; settings.menuPosX = 0;
@ -184,8 +169,9 @@ function drawMenuItems(menuItem) {
if (i >= menuItem.items.length) { if (i >= menuItem.items.length) {
continue; continue;
} }
lock_input++;
menuItem.items[i].show(); var item = menuItem.items[i];
drawText(item.name, item.get().text, (i%4)+1);
} }
} }
@ -217,7 +203,6 @@ function drawText(key, value, line){
value = String(value).replace("\n", " "); value = String(value).replace("\n", " ");
g.drawString(key + value, x, y); g.drawString(key + value, x, y);
lock_input -= 1;
} }
@ -289,14 +274,8 @@ Bangle.on('charging',function(charging) {
draw(); draw();
}); });
var lock_input = 0;
Bangle.on('touch', function(btn, e){ Bangle.on('touch', function(btn, e){
if(lock_input > 0){
return;
}
lock_input = 0;
var left = parseInt(g.getWidth() * 0.22); var left = parseInt(g.getWidth() * 0.22);
var right = g.getWidth() - left; var right = g.getWidth() - left;
var upper = parseInt(g.getHeight() * 0.22) + 20; var upper = parseInt(g.getHeight() * 0.22) + 20;

View File

@ -1,7 +1,7 @@
{ {
"id": "linuxclock", "id": "linuxclock",
"name": "Linux Clock", "name": "Linux Clock",
"version": "0.03", "version": "0.04",
"description": "A Linux inspired clock.", "description": "A Linux inspired clock.",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",

View File

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

BIN
apps/messagesdebug/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,3 @@
Bangle.on("message", (t, m) => {
require("Storage").open("messagesdebug.log", "a").write(`${t}: ${JSON.stringify(m)}\n`);
});

View File

@ -0,0 +1,71 @@
<html lang="en">
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<title>Messages Debug</title>
</head>
<body>
<div id="data"></div>
<button class="btn btn-default" id="btnSave">Save</button>
<button class="btn btn-default" id="btnDelete">Clear</button>
<button class="btn btn-default" id="btnReload" style="float:right">Reload</button>
<script src="../../core/lib/interface.js"></script>
<script>
const dataElement = document.getElementById("data");
let messages = "";
function getData() {
// show loading window
Util.showModal("Loading...");
// get the data
dataElement.innerHTML = "";
Util.readStorageFile(`messagesdebug.log`, data => {
messages = data.trim();
// remove window
Util.hideModal();
let disable = false;
if (data.length) {
dataElement.innerHTML = `<pre style="overflow:auto;border:1px solid black;">${data}</pre>`;
} else {
dataElement.innerHTML = "<b>No messages found</b>";
disable = true;
}
['btnSave','btnDelete','btnCopy'].forEach(id=>{
document.getElementById(id).disabled = disable;
});
});
}
const button = (id, fn) => document.getElementById(id).addEventListener("click", fn);
// Save messages to file
button("btnSave", function() {
let a = document.createElement("a");
let url = URL.createObjectURL(new Blob([messages], {type: "text/plain"}));
a.href = url;
a.download = "messagesdebug.log";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
});
// Delete the file from the watch
button("btnDelete", function() {
Util.showModal("Deleting...");
Util.eraseStorageFile("messagesdebug.log", function() {
Util.hideModal();
getData();
});
});
// Reload, in case we're e.g. using the IDE to send test messages
button("btnReload", getData);
// Called when app starts
function onInit() {
getData();
}
</script>
</body>
</html>

View File

@ -0,0 +1,15 @@
{
"id": "messagesdebug",
"name": "Messages Debug",
"version": "0.01",
"description": "Write all messages to a file, for debugging purposes",
"icon": "app.png",
"type": "bootloader",
"tags": "tool,system",
"supports": ["BANGLEJS","BANGLEJS2"],
"interface": "interface.html",
"storage": [
{"name":"messagesdebug.boot.js","url":"boot.js"}
],
"data": [{"name":"messagesdebug.log"}]
}

View File

@ -1 +1,2 @@
0.01: First release 0.01: First release
0.02: Use locale time

View File

@ -2,7 +2,7 @@
"id":"mosaic", "id":"mosaic",
"name":"Mosaic Clock", "name":"Mosaic Clock",
"shortName": "Mosaic Clock", "shortName": "Mosaic Clock",
"version": "0.01", "version": "0.02",
"description": "A fabulously colourful clock", "description": "A fabulously colourful clock",
"readme": "README.md", "readme": "README.md",
"icon":"mosaic.png", "icon":"mosaic.png",

View File

@ -58,13 +58,15 @@ function draw() {
); );
} }
} }
let t = new Date(); let t = require("locale").time(new Date(), 1);
let hour = parseInt(t.split(":")[0]);
let minute = parseInt(t.split(":")[1]);
g.setBgColor(theme.fg); g.setBgColor(theme.fg);
g.setColor(theme.bg); g.setColor(theme.bg);
g.drawImage(digits[Math.floor(t.getHours()/10)], (mid_x-5)*s+o_w, (mid_y-7)*s+o_h, {scale:s}); g.drawImage(digits[Math.floor(hour/10)], (mid_x-5)*s+o_w, (mid_y-7)*s+o_h, {scale:s});
g.drawImage(digits[t.getHours() % 10], (mid_x+1)*s+o_w, (mid_y-7)*s+o_h, {scale:s}); g.drawImage(digits[hour % 10], (mid_x+1)*s+o_w, (mid_y-7)*s+o_h, {scale:s});
g.drawImage(digits[Math.floor(t.getMinutes()/10)], (mid_x-5)*s+o_w, (mid_y+1)*s+o_h, {scale:s}); g.drawImage(digits[Math.floor(minute/10)], (mid_x-5)*s+o_w, (mid_y+1)*s+o_h, {scale:s});
g.drawImage(digits[t.getMinutes() % 10], (mid_x+1)*s+o_w, (mid_y+1)*s+o_h, {scale:s}); g.drawImage(digits[minute % 10], (mid_x+1)*s+o_w, (mid_y+1)*s+o_h, {scale:s});
queueDraw(timeout); queueDraw(timeout);
} }

View File

@ -10,7 +10,6 @@
"BANGLEJS", "BANGLEJS",
"BANGLEJS2" "BANGLEJS2"
], ],
"allow_emulator": true,
"storage": [ "storage": [
{ {
"name": "pomoplus.app.js", "name": "pomoplus.app.js",
@ -34,4 +33,4 @@
"url": "settings.js" "url": "settings.js"
} }
] ]
} }

View File

@ -60,3 +60,5 @@
0.53: Ensure that when clock is set, clockHasWidgets is set correctly too 0.53: Ensure that when clock is set, clockHasWidgets is set correctly too
0.54: If setting.json is corrupt, ensure it gets re-written 0.54: If setting.json is corrupt, ensure it gets re-written
0.55: More strings tagged for automatic translation. 0.55: More strings tagged for automatic translation.
0.56: make System menu items shorter and more consistant, Eg 'Clock', intead
of 'Select Clock'

View File

@ -1,7 +1,7 @@
{ {
"id": "setting", "id": "setting",
"name": "Settings", "name": "Settings",
"version": "0.55", "version": "0.56",
"description": "A menu for setting up Bangle.js", "description": "A menu for setting up Bangle.js",
"icon": "settings.png", "icon": "settings.png",
"tags": "tool,system", "tags": "tool,system",

View File

@ -89,8 +89,8 @@ function showSystemMenu() {
/*LANG*/'Theme': ()=>showThemeMenu(), /*LANG*/'Theme': ()=>showThemeMenu(),
/*LANG*/'LCD': ()=>showLCDMenu(), /*LANG*/'LCD': ()=>showLCDMenu(),
/*LANG*/'Locale': ()=>showLocaleMenu(), /*LANG*/'Locale': ()=>showLocaleMenu(),
/*LANG*/'Select Clock': ()=>showClockMenu(), /*LANG*/'Clock': ()=>showClockMenu(),
/*LANG*/'Select Launcher': ()=>showLauncherMenu(), /*LANG*/'Launcher': ()=>showLauncherMenu(),
/*LANG*/'Date & Time': ()=>showSetTimeMenu() /*LANG*/'Date & Time': ()=>showSetTimeMenu()
}; };

View File

@ -0,0 +1,2 @@
0.01: first release
0.02: removed fast load, minimalism is useful for narrowing down on issues

View File

@ -1,6 +1,6 @@
# Simplest++ Clock # Simplest++ Clock
The simplest working clock, with fast load and clock_info The simplest working clock, with clock_info
![](screenshot1.png) ![](screenshot1.png)
![](screenshot2.png) ![](screenshot2.png)
@ -36,8 +36,6 @@ This provides a working demo of how to use the clock_info modules.
## References ## References
* [What is Fast Load and how does it work](http://www.espruino.com/Bangle.js+Fast+Load)
* [Clock Info Tutorial](http://www.espruino.com/Bangle.js+Clock+Info) * [Clock Info Tutorial](http://www.espruino.com/Bangle.js+Clock+Info)
* [How to load modules through the IDE](https://github.com/espruino/BangleApps/blob/master/modules/README.md) * [How to load modules through the IDE](https://github.com/espruino/BangleApps/blob/master/modules/README.md)

View File

@ -1,108 +1,95 @@
// Simplestpp Clock, see comments 'clock_info_support'
function draw() {
var date = new Date();
var timeStr = require("locale").time(date,1);
var h = g.getHeight();
var w = g.getWidth();
g.reset();
g.setColor(g.theme.bg);
g.fillRect(Bangle.appRect);
g.setFont('Vector', w/3);
g.setFontAlign(0, 0);
g.setColor(g.theme.fg);
g.drawString(timeStr, w/2, h/2);
clockInfoMenu.redraw(); // clock_info_support
queueDraw(); // queue draw in one minute
}
// 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));
}
/** /**
* * clock_info_support
* Simplestpp Clock * this is the callback function that get invoked by clockInfoMenu.redraw();
* *
* The entire clock code is contained within the block below this * We will display the image and text on the same line and centre the combined
* supports 'fast load' * length of the image+text
*
* To add support for clock_info_supprt we add the code marked at [1] and [2]
* *
*/ */
function clockInfoDraw(itm, info, options) {
{ g.reset().setFont('Vector',24).setBgColor(options.bg).setColor(options.fg);
// must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
let draw = function() { //use info.text.toString(), steps does not have length defined
var date = new Date(); var text_w = g.stringWidth(info.text.toString());
var timeStr = require("locale").time(date,1); // gap between image and text
var h = g.getHeight(); var gap = 10;
var w = g.getWidth(); // width of the image and text combined
var w = gap + (info.img ? 24 :0) + text_w;
g.reset(); // different fg color if we tapped on the menu
g.setColor(g.theme.bg); if (options.focus) g.setColor(options.hl);
g.fillRect(Bangle.appRect);
g.setFont('Vector', w/3); // clear the whole info line
g.setFontAlign(0, 0); g.clearRect(0, options.y -1, g.getWidth(), options.y+24);
g.setColor(g.theme.fg);
g.drawString(timeStr, w/2, h/2);
clockInfoMenu.redraw(); // clock_info_support
// schedule a draw for the next minute
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
};
/**
* clock_info_support
* this is the callback function that get invoked by clockInfoMenu.redraw();
*
* We will display the image and text on the same line and centre the combined
* length of the image+text
*
*
*/
let clockInfoDraw = (itm, info, options) => {
g.reset().setFont('Vector',24).setBgColor(options.bg).setColor(options.fg);
//use info.text.toString(), steps does not have length defined
var text_w = g.stringWidth(info.text.toString());
// gap between image and text
var gap = 10;
// width of the image and text combined
var w = gap + (info.img ? 24 :0) + text_w;
// different fg color if we tapped on the menu
if (options.focus) g.setColor(options.hl);
// clear the whole info line
g.clearRect(0, options.y -1, g.getWidth(), options.y+24);
// draw the image if we have one
if (info.img) {
// image start
var x = (g.getWidth() / 2) - (w/2);
g.drawImage(info.img, x, options.y);
// draw the text to the side of the image (left/centre alignment)
g.setFontAlign(-1,0).drawString(info.text, x + 23 + gap, options.y+12);
} else {
// text only option, not tested yet
g.setFontAlign(0,0).drawString(info.text, g.getWidth() / 2, options.y+12);
}
};
// clock_info_support
// retrieve all the clock_info modules that are installed
let clockInfoItems = require("clock_info").load();
// clock_info_support
// setup the way we wish to interact with the menu
// the hl property defines the color the of the info when the menu is selected after tapping on it
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:64, y:132, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#0ff"} );
// timeout used to update every minute // draw the image if we have one
var drawTimeout; if (info.img) {
g.clear(); // image start
var x = (g.getWidth() / 2) - (w/2);
g.drawImage(info.img, x, options.y);
// draw the text to the side of the image (left/centre alignment)
g.setFontAlign(-1,0).drawString(info.text, x + 23 + gap, options.y+12);
} else {
// text only option, not tested yet
g.setFontAlign(0,0).drawString(info.text, g.getWidth() / 2, options.y+12);
}
// Show launcher when middle button pressed, add updown button handlers }
Bangle.setUI({
mode : "clock",
remove : function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
// remove info menu
clockInfoMenu.remove();
delete clockInfoMenu;
}
});
// Load widgets /**
Bangle.loadWidgets(); * clock_info_support: retrieve all the clock_info modules that are
draw(); * installed
setTimeout(Bangle.drawWidgets,0); *
} // end of clock */
let clockInfoItems = require("clock_info").load();
/**
* clock_info_support: setup the way we wish to interact with the menu
* the hl property defines the color the of the info when the menu is
* selected after tapping on it
*
*/
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:64, y:132, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#0ff"} );
// Clear the screen once, at startup
g.clear();
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
draw();

View File

@ -2,8 +2,8 @@
"id": "simplestpp", "id": "simplestpp",
"name": "Simplest++ Clock", "name": "Simplest++ Clock",
"shortName": "Simplest++", "shortName": "Simplest++",
"version": "0.01", "version": "0.02",
"description": "The simplest working clock, with fast load and clock_info, acts as a tutorial piece", "description": "The simplest working clock, with clock_info, acts as a tutorial piece",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot3.png"}], "screenshots": [{"url":"screenshot3.png"}],

View File

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

16
apps/spaceclock/README.md Normal file
View File

@ -0,0 +1,16 @@
# Space Clock (Casio)
![Light Theme version](spaceclock_light_big.png)
## Description
This watch face is inspired by the Casio Prototype, which was made by Khryzteen Nakamura from Clockology Fans on Facebook.
## Features
- Time and Date
- Weekday
- Temperature
- HeartRate
- Battery
- Step Count
- Support of light and dark theme
## Tips
Click on the heart icon to deactivate the heart rate monitor.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEewgbYhGIxGAC6wABBAcM5gACC5wYCCwgYLIwYcBhoWFDBQTBAofcC4/AGBIEDCw4wLJARdGAAfQGBYWJJBQwCC5SSLC6wwBC6owBwhgUDASQKC5UMpGIpgXU5koMRIXM4hiJC5gNBMRAXN5hiBDA0IC5oPBPYz+CABAQElAYFWgIYJC4h7BDAZeBHAJINPYQYC6DlCGBENRQwRBhgNCGBSjG4QxBBoYwQGIIrEPJS8GIgYADYZwIDDAgXJABQXXAAY"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

237
apps/spaceclock/app.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
{ "id": "spaceclock",
"name": "Space Clock (Casio Style)",
"shortName":"Space Clock",
"version":"0.01",
"description": "Watch face in the style of Casio Prototype Space Resist",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock, casio, retro",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"spaceclock.app.js","url":"app.js"},
{"name":"spaceclock.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

1
apps/tictactoe/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Stable Launch

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwIJGv//AAX+oEAwEBgECApuD4IFBocww1hAoXwxkxAoNB8GIiIFC4AFDofgCIYdFoEQFIZBRLLoFBHYkAI4YFBKYYFHCIodFLO8M4RHDh4FCKYMHwQFELIkPBYQdFFIVCLK8AAAg="))

210
apps/tictactoe/app.js Normal file
View File

@ -0,0 +1,210 @@
//////////////////////////////
// Tic - Tac - Toe
// Stable Version 1.0 - 12/31/2022
// MissionMake
//////////////////////////////
////////////////////////////
// TODO:
// Implement Computer Player
// Beginning Screen (pick player to go first, pick one or two player)
////////////////////////////
//create 3x3 array to log plays Xs defined as 1, Os defined as -1, blank is undefined, array is initialized undefined, player is which players turn is active (using 1,-1 definition to match matrix), active is if a game is being played
var arr1 = new Array(3);
var arr2 = new Array(3);
var arr3 = new Array(3);
var arr = new Array(arr1,arr2,arr3);
var val = 0;
var player;
var active = false;
var select = false;
var next = 0;
var winval =0;
var ex = require("heatshrink").decompress(atob("mEwwI63jACEngCEvwCEv4CB/wCBn+AgP8AoMf4ED/AFBh/gg/wAoIDBA4IFBB4ITBAoIbBD4I8C/wrCGAQuCGAQuCGAQuCGAQuCAo4RFDoopFGohBFJopZFMopxFPoqJFSoqhFVooA0A"));
var oh = require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCB/wCBBAU/AQIUCj8AgIzCh+AgYmCg/AgYyCAYIHBAoXgg+AAoMBApkPLgZKBAtBBRLIprDMoJxFPoqJFSoyhCAQStFXIrFFaIrdFdIwAVA"));
//calculates sum of rows, colums, and diagonals for a win condition. passes winner to win() and breaks out of calcs
function calcWin(){
winval = 0;
//sum of row
for(let i = 0; i<3; i++){
val=0;
for(let j = 0; j<3; j++){
val = arr[i][j]+val;
}
if (Math.abs(val)==3) {
winval = val;
}
}
//sum of columns
for(let j = 0; j<3; j++){
val=0;
for (let i = 0; i<3; i++){
val = arr[i][j]+val;
}
//if win set winval to val
if (Math.abs(val)==3) {
winval = val;
}
}
//Sum of ul to lr
val=0;
val = arr[0][0]+arr[1][1]+arr[2][2];
//if win set winval to val
if (Math.abs(val)==3) {
winval = val;
}
//sum of ur to ll
val=0;
val = arr[0][2]+arr[1][1]+arr[2][0];
//if win set winval to val
if (Math.abs(val)==3){
winval = val;
}
//draw check
// drawChk is sum absolute value of array, if drawChk = 9 then there is a draw
drawChk = 0;
for(let i = 0; i<3; i++){
for(let j = 0; j<3; j++){
drawChk = drawChk + Math.abs(arr[i][j]);
}
}
//checks for win cases and posts correct message, otherwise play
if (winval == 3){
active = false;
E.showAlert("Player X Wins").then(start);
} else if (winval == -3){
active = false;
E.showAlert("Player O Wins").then(start);
} else if (drawChk == 9) {
active = false;
E.showAlert("Draw").then(start);
}else{
//If no win then play
draw();
}
}
function draw(){
g.clear();
if (player ==1){
playerIcon = "X";
} else if(player == -1){
playerIcon = "O";
}
//Banner Displays player turn
E.showMessage("","Player "+ playerIcon);
//drawboard
g.drawLine(62,24,62,176);
g.drawLine(112,24,112,176);
g.drawLine(12,74,164,74);
g.drawLine(12,124,164,124);
//loop through array and draw markers
for(let i = 0; i<3; i++){
for(let j = 0; j<3; j++){
if(arr[j][i] == -1){
g.drawImage(oh,i*50+12,j*50+24);//, {scale:1.05});
} else if (arr[j][i] == 1){
g.drawImage(ex,i*50+12,j*50+24);//, {scale:1.05});
} else {
//blank spot
}
}
}
select=false;
wait();
}
// Square locations
//12,24;62,24,112,24
//12,74;62,74,112,74
//12,124;62,124,112,124
function placeMarker(){
///Determine marker square
if (x <= 62) {
b = 0;
} else if (x <= 112){
b = 1;
} else {
b = 2;
}
if (y <= 74) {
a = 0;
} else if (y <= 124){
a = 1;
} else {
a = 2;
}
//if empty
if( arr[a][b] == undefined){
//record in array
arr[a][b] = player;
player=player*-1;
select = false;
calcWin();
} else{ //if filled
// This could just do nothing
E.showAlert("SpaceFilled Try again").then(draw);
}
}
// Wait loop which is run until a tap is selected
function wait(){
//Terminal.println("wait");
if(select == true){
placeMarker();
} else {
setTimeout(wait,300);
}
}
// Starts new game
// Draws the start pattern, sets first player to x and goes to play
function start(){
//reset array to undefined
arr1.fill(undefined);
arr2.fill(undefined);
arr3.fill(undefined);
g.clear();
active =true;
player=1;
draw();
}
//Looks for touch
Bangle.on('touch', function(zone,e) {
x = Object.values(e)[0];
y = Object.values(e)[1];
//if game is active
if(active == true){
g.fillCircle(x, y, 10);
select = true;
}
if(active == false){
start();
}
});
start();

BIN
apps/tictactoe/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

View File

@ -0,0 +1,17 @@
{ "id": "tictactoe",
"name": "TicTacToe",
"shortName":"TicTacToe",
"icon": "app.png",
"version":"0.01",
"description": "Tic Tac Toe for two players!",
"tags": "game",
"storage": [
{"name":"tictactoe.app.js","url":"app.js"},
{"name":"tictactoe.img","url":"app-icon.js","evaluate":true}
],
"screenshots" : [
{ "url":"tttscreenshot.png" },
{ "url":"tttscreenshot2.png" }
],
"supports": ["BANGLEJS2"]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -136,7 +136,8 @@
<button class="btn" id="removeall" data-tooltip="Delete everything from your Bangle, leaving it blank">Remove all Apps</button> <button class="btn" id="removeall" data-tooltip="Delete everything from your Bangle, leaving it blank">Remove all Apps</button>
<button class="btn" id="reinstallall" data-tooltip="Remove and re-install every app, leaving all other data intact">Reinstall apps</button> <button class="btn" id="reinstallall" data-tooltip="Remove and re-install every app, leaving all other data intact">Reinstall apps</button>
<button class="btn" id="installdefault">Install default apps</button> <button class="btn" id="installdefault">Install default apps</button>
<button class="btn" id="installfavourite" data-tooltip="Delete everything, install apps you've marked as favourites">Install favourite apps</button></p> <button class="btn" id="installfavourite" data-tooltip="Delete everything, install apps you've marked as favourites">Install favourite apps</button>
<button class="btn" id="newGithubIssue" data-tooltip="Create a new issue on github">New issue on github</button></p>
<p><button class="btn tooltip tooltip-right" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button> <p><button class="btn tooltip tooltip-right" id="downloadallapps" data-tooltip="Download all Bangle.js files to a ZIP file">Backup</button>
<button class="btn tooltip tooltip-right" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button></p> <button class="btn tooltip tooltip-right" id="uploadallapps" data-tooltip="Restore Bangle.js from a ZIP file">Restore</button></p>
<h3>Settings</h3> <h3>Settings</h3>

View File

@ -71,11 +71,15 @@ exports.load = function() {
bangleItems[2].emit("redraw"); bangleItems[2].emit("redraw");
} }
function altUpdateHandler() { function altUpdateHandler() {
Bangle.getPressure().then(data=>{ try {
if (!data) return; Bangle.getPressure().then(data=>{
alt = Math.round(data.altitude) + "m"; if (!data) return;
bangleItems[3].emit("redraw"); alt = Math.round(data.altitude) + "m";
}); bangleItems[3].emit("redraw");
});
} catch (e) {
print("Caught "+e+"\n in function altUpdateHandler in module clock_info");
bangleItems[3].emit('redraw');}
} }
// actual menu // actual menu
var menu = [{ var menu = [{