diff --git a/.gitignore b/.gitignore
index fce2efb1a..231851dd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ tests/Layout/bin/tmp.*
tests/Layout/testresult.bmp
apps.local.json
_site
+.jekyll-cache
diff --git a/README.md b/README.md
index 78dd1b492..b3da9f685 100644
--- a/README.md
+++ b/README.md
@@ -186,7 +186,7 @@ The widget example is available in [`apps/_example_widget`](apps/_example_widget
Widgets are just small bits of code that run whenever an app that supports them
calls `Bangle.loadWidgets()`. If they want to display something in the 24px high
-widget bar at the top of the screen they can add themselves to the global
+widget bar at the top of the screen they can add themselves to the global
`WIDGETS` array with:
```
@@ -226,10 +226,8 @@ and which gives information about the app for the Launcher.
"name":"Short Name", // for Bangle.js menu
"icon":"*myappid", // for Bangle.js menu
"src":"-myappid", // source file
- "type":"widget/clock/app/bootloader", // optional, default "app"
- // if this is 'widget' then it's not displayed in the menu
- // if it's 'clock' then it'll be loaded by default at boot time
- // if this is 'bootloader' then it's code that is run at boot time, but is not in a menu
+ "type":"widget/clock/app/bootloader/...", // optional, default "app"
+ // see 'type' in 'metadata.json format' below for more options/info
"version":"1.23",
// added by BangleApps loader on upload based on metadata.json
"files:"file1,file2,file3",
@@ -252,17 +250,24 @@ and which gives information about the app for the Launcher.
"version": "0v01", // the version of this app
"description": "...", // long description (can contain markdown)
"icon": "icon.png", // icon in apps/
- "screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app
+ "screenshots" : [ { "url":"screenshot.png" } ], // optional screenshot for app
"type":"...", // optional(if app) -
// 'app' - an application
// 'clock' - a clock - required for clocks to automatically start
// 'widget' - a widget
- // 'launch' - replacement launcher app
- // 'bootloader' - code that runs at startup only
+ // 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
+ // 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js'
// 'RAM' - code that runs and doesn't upload anything to storage
+ // 'launch' - replacement 'Launcher'
+ // 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
+ // 'scheduler' - provides 'sched' library and boot code for scheduling alarms/timers
+ // (currently only 'sched' app)
+ // 'notify' - provides 'notify' library for showing notifications
+ // 'locale' - provides 'locale' library for language-specific date/distance/etc
+ // (a version of 'locale' is included in the firmware)
"tags": "", // comma separated tag list for searching
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
- "dependencies" : { "notify":"type" } // optional, app 'types' we depend on
+ "dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
"readme": "README.md", // if supplied, a link to a markdown-style text file
@@ -415,7 +420,7 @@ Example `settings.js`
// make sure to enclose the function in parentheses
(function(back) {
let settings = require('Storage').readJSON('myappid.json',1)||{};
- if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
+ if (typeof settings.monkeys !== "number") settings.monkeys = 12; // default value
function save(key, value) {
settings[key] = value;
require('Storage').write('myappid.json', settings);
diff --git a/_config.yml b/_config.yml
index 2f7efbeab..c74188174 100644
--- a/_config.yml
+++ b/_config.yml
@@ -1 +1 @@
-theme: jekyll-theme-minimal
\ No newline at end of file
+theme: jekyll-theme-slate
\ No newline at end of file
diff --git a/android.html b/android.html
new file mode 100644
index 000000000..93999008f
--- /dev/null
+++ b/android.html
@@ -0,0 +1,352 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Bangle.js App Loader
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Default
+ Clocks
+ Games
+ Tools
+ Widgets
+ Bluetooth
+ Outdoors
+ Favourites
+
+
+ Sort by:
+ None
+ New
+ Updated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/activityreminder/ChangeLog b/apps/activityreminder/ChangeLog
index 53e29a66d..d4b5100a2 100644
--- a/apps/activityreminder/ChangeLog
+++ b/apps/activityreminder/ChangeLog
@@ -1,2 +1,5 @@
0.01: New App!
0.02: Fix the settings bug and some tweaking
+0.03: Do not alarm while charging
+0.04: Obey system quiet mode
+0.05: Battery optimisation, add the pause option, bug fixes
diff --git a/apps/activityreminder/README.md b/apps/activityreminder/README.md
index 1e643fb54..25e2c8d35 100644
--- a/apps/activityreminder/README.md
+++ b/apps/activityreminder/README.md
@@ -1,13 +1,14 @@
# Activity reminder
A reminder to take short walks for the ones with a sedentary lifestyle.
-The alert will popup only if you didn't take your short walk yet
+The alert will popup only if you didn't take your short walk yet.
Different settings can be personalized:
- Enable : Enable/Disable the app
- Start hour: Hour to start the reminder
- End hour: Hour to end the reminder
-- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 60 min
-- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 15 min
+- Max inactivity: Maximum inactivity time to allow before the alert. From 15 to 120 min
+- Dismiss delay: Delay added before the next alert if the alert is dismissed. From 5 to 60 min
+- Pause delay: Same as Dismiss delay but longer (usefull for meetings and such). From 30 to 240 min
- Min steps: Minimal amount of steps to count as an activity
diff --git a/apps/activityreminder/app.js b/apps/activityreminder/app.js
index 310dc10b0..f3d72976e 100644
--- a/apps/activityreminder/app.js
+++ b/apps/activityreminder/app.js
@@ -1,37 +1,42 @@
-function drawAlert(){
- E.showPrompt("Inactivity detected",{
- title:"Activity reminder",
- buttons : {"Ok": true,"Dismiss": false}
- }).then(function(v) {
- if(v == true){
- stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - 3);
- require("activityreminder").saveStepsArray(stepsArray);
- }
- if(v == false){
- stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin - activityreminder.dismissDelayMin);
- require("activityreminder").saveStepsArray(stepsArray);
- }
+function drawAlert() {
+ E.showPrompt("Inactivity detected", {
+ title: "Activity reminder",
+ buttons: { "Ok": 1, "Dismiss": 2, "Pause": 3 }
+ }).then(function (v) {
+ if (v == 1) {
+ activityreminder_data.okDate = new Date();
+ }
+ if (v == 2) {
+ activityreminder_data.dismissDate = new Date();
+ }
+ if (v == 3) {
+ activityreminder_data.pauseDate = new Date();
+ }
+ activityreminder.saveData(activityreminder_data);
load();
});
-
- Bangle.buzz(400);
+
+ // Obey system quiet mode:
+ if (!(storage.readJSON('setting.json', 1) || {}).quiet) {
+ Bangle.buzz(400);
+ }
setTimeout(load, 20000);
}
-function run(){
- if(stepsArray.length == activityreminder.maxInnactivityMin){
- if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){
- drawAlert();
- }
- }else{
- eval(require("Storage").read("activityreminder.settings.js"))(()=>load());
- }
+function run() {
+ if (activityreminder.mustAlert(activityreminder_data, activityreminder_settings)) {
+ drawAlert();
+ } else {
+ eval(storage.read("activityreminder.settings.js"))(() => load());
+ }
}
+const activityreminder = require("activityreminder");
+const storage = require("Storage");
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
-activityreminder = require("activityreminder").loadSettings();
-stepsArray = require("activityreminder").loadStepsArray();
+const activityreminder_settings = activityreminder.loadSettings();
+const activityreminder_data = activityreminder.loadData();
run();
diff --git a/apps/activityreminder/boot.js b/apps/activityreminder/boot.js
index 0f89bf543..7c094f521 100644
--- a/apps/activityreminder/boot.js
+++ b/apps/activityreminder/boot.js
@@ -1,29 +1,45 @@
-function run(){
- var now = new Date();
- var h = now.getHours();
- if(h >= activityreminder.startHour && h < activityreminder.endHour){
- var health = Bangle.getHealthStatus("day");
- stepsArray.unshift(health.steps);
- stepsArray = stepsArray.slice(0, activityreminder.maxInnactivityMin);
- require("activityreminder").saveStepsArray(stepsArray);
- }
- else{
- if(stepsArray != []){
- stepsArray = [];
- require("activityreminder").saveStepsArray(stepsArray);
+function run() {
+ if (isNotWorn()) return;
+ let now = new Date();
+ let h = now.getHours();
+ let health = Bangle.getHealthStatus("day");
+
+ if (h >= activityreminder_settings.startHour && h < activityreminder_settings.endHour) {
+ if (health.steps - activityreminder_data.stepsOnDate >= activityreminder_settings.minSteps // more steps made than needed
+ || health.steps < activityreminder_data.stepsOnDate) { // new day or reboot of the watch
+ activityreminder_data.stepsOnDate = health.steps;
+ activityreminder_data.stepsDate = now;
+ activityreminder.saveData(activityreminder_data);
+ /* todo in a futur release
+ add settimer to trigger like 10 secs after the stepsDate + minSteps
+ cancel all other timers of this app
+ */
}
- }
- if(stepsArray.length >= activityreminder.maxInnactivityMin){
- if (stepsArray[0] - stepsArray[stepsArray.length-1] < activityreminder.minSteps){
+
+ if(activityreminder.mustAlert(activityreminder_data, activityreminder_settings)){
load('activityreminder.app.js');
}
}
+
}
+function isNotWorn() {
+ // todo in a futur release check temperature and mouvement in a futur release
+ return Bangle.isCharging();
+}
-activityreminder = require("activityreminder").loadSettings();
-if(activityreminder.enabled) {
- stepsArray = require("activityreminder").loadStepsArray();
+const activityreminder = require("activityreminder");
+const activityreminder_settings = activityreminder.loadSettings();
+if (activityreminder_settings.enabled) {
+ const activityreminder_data = activityreminder.loadData();
+ if(activityreminder_data.firstLoad){
+ activityreminder_data.firstLoad =false;
+ activityreminder.saveData(activityreminder_data);
+ }
setInterval(run, 60000);
+ /* todo in a futur release
+ increase setInterval time to something that is still sensible (5 mins ?)
+ add settimer to trigger like 10 secs after the stepsDate + minSteps
+ cancel all other timers of this app
+ */
}
-
diff --git a/apps/activityreminder/lib.js b/apps/activityreminder/lib.js
index 712842fba..5b7959827 100644
--- a/apps/activityreminder/lib.js
+++ b/apps/activityreminder/lib.js
@@ -1,22 +1,57 @@
-exports.loadSettings = function() {
+const storage = require("Storage");
+
+exports.loadSettings = function () {
return Object.assign({
enabled: true,
startHour: 9,
endHour: 20,
maxInnactivityMin: 30,
dismissDelayMin: 15,
+ pauseDelayMin: 120,
minSteps: 50
- }, require("Storage").readJSON("activityreminder.s.json", true) || {});
+ }, storage.readJSON("activityreminder.s.json", true) || {});
};
-exports.writeSettings = function(settings){
- require("Storage").writeJSON("activityreminder.s.json", settings);
+exports.writeSettings = function (settings) {
+ storage.writeJSON("activityreminder.s.json", settings);
};
-exports.saveStepsArray = function(stepsArray) {
- require("Storage").writeJSON("activityreminder.sa.json", stepsArray);
+exports.saveData = function (data) {
+ storage.writeJSON("activityreminder.data.json", data);
};
-exports.loadStepsArray = function(){
- return require("Storage").readJSON("activityreminder.sa.json") || [];
-};
\ No newline at end of file
+exports.loadData = function () {
+ let health = Bangle.getHealthStatus("day");
+ const data = Object.assign({
+ firstLoad: true,
+ stepsDate: new Date(),
+ stepsOnDate: health.steps,
+ okDate: new Date(1970),
+ dismissDate: new Date(1970),
+ pauseDate: new Date(1970),
+ },
+ storage.readJSON("activityreminder.data.json") || {});
+
+ if(typeof(data.stepsDate) == "string")
+ data.stepsDate = new Date(data.stepsDate);
+ if(typeof(data.okDate) == "string")
+ data.okDate = new Date(data.okDate);
+ if(typeof(data.dismissDate) == "string")
+ data.dismissDate = new Date(data.dismissDate);
+ if(typeof(data.pauseDate) == "string")
+ data.pauseDate = new Date(data.pauseDate);
+
+ return data;
+};
+
+exports.mustAlert = function(activityreminder_data, activityreminder_settings) {
+ let now = new Date();
+ if ((now - activityreminder_data.stepsDate) / 60000 > activityreminder_settings.maxInnactivityMin) { // inactivity detected
+ if ((now - activityreminder_data.okDate) / 60000 > 3 && // last alert anwsered with ok was more than 3 min ago
+ (now - activityreminder_data.dismissDate) / 60000 > activityreminder_settings.dismissDelayMin && // last alert was more than dismissDelayMin ago
+ (now - activityreminder_data.pauseDate) / 60000 > activityreminder_settings.pauseDelayMin) { // last alert was more than pauseDelayMin ago
+ return true;
+ }
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/apps/activityreminder/metadata.json b/apps/activityreminder/metadata.json
index eba5de105..15f10f2ed 100644
--- a/apps/activityreminder/metadata.json
+++ b/apps/activityreminder/metadata.json
@@ -3,7 +3,7 @@
"name": "Activity Reminder",
"shortName":"Activity Reminder",
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
- "version":"0.02",
+ "version":"0.05",
"icon": "app.png",
"type": "app",
"tags": "tool,activity",
@@ -18,6 +18,6 @@
],
"data": [
{"name": "activityreminder.s.json"},
- {"name": "activityreminder.sa.json"}
+ {"name": "activityreminder.data.json"}
]
}
diff --git a/apps/activityreminder/settings.js b/apps/activityreminder/settings.js
index 9b9a0ecd8..9dff61f48 100644
--- a/apps/activityreminder/settings.js
+++ b/apps/activityreminder/settings.js
@@ -1,64 +1,76 @@
-(function(back) {
+(function (back) {
// Load settings
- var settings = require("activityreminder").loadSettings();
+ const activityreminder = require("activityreminder");
+ const settings = activityreminder.loadSettings();
// Show the menu
E.showMenu({
- "" : { "title" : "Activity Reminder" },
- "< Back" : () => back(),
- 'Enable': {
- value: settings.enabled,
- format: v => v?"Yes":"No",
- onchange: v => {
- settings.enabled = v;
- require("activityreminder").writeSettings(settings);
- }
+ "": { "title": "Activity Reminder" },
+ "< Back": () => back(),
+ 'Enable': {
+ value: settings.enabled,
+ format: v => v ? "Yes" : "No",
+ onchange: v => {
+ settings.enabled = v;
+ activityreminder.writeSettings(settings);
+ }
+ },
+ 'Start hour': {
+ value: settings.startHour,
+ min: 0, max: 24,
+ onchange: v => {
+ settings.startHour = v;
+ activityreminder.writeSettings(settings);
+ }
+ },
+ 'End hour': {
+ value: settings.endHour,
+ min: 0, max: 24,
+ onchange: v => {
+ settings.endHour = v;
+ activityreminder.writeSettings(settings);
+ }
+ },
+ 'Max inactivity': {
+ value: settings.maxInnactivityMin,
+ min: 15, max: 120,
+ onchange: v => {
+ settings.maxInnactivityMin = v;
+ activityreminder.writeSettings(settings);
},
- 'Start hour': {
- value: settings.startHour,
- min: 0, max: 24,
- onchange: v => {
- settings.startHour = v;
- require("activityreminder").writeSettings(settings);
- }
- },
- 'End hour': {
- value: settings.endHour,
- min: 0, max: 24,
- onchange: v => {
- settings.endHour = v;
- require("activityreminder").writeSettings(settings);
- }
- },
- 'Max inactivity': {
- value: settings.maxInnactivityMin,
- min: 15, max: 120,
- onchange: v => {
- settings.maxInnactivityMin = v;
- require("activityreminder").writeSettings(settings);
- },
- format: x => {
- return x + " min";
- }
- },
- 'Dismiss delay': {
- value: settings.dismissDelayMin,
- min: 5, max: 15,
- onchange: v => {
- settings.dismissDelayMin = v;
- require("activityreminder").writeSettings(settings);
- },
- format: x => {
- return x + " min";
- }
- },
- 'Min steps': {
- value: settings.minSteps,
- min: 10, max: 500,
- onchange: v => {
- settings.minSteps = v;
- require("activityreminder").writeSettings(settings);
- }
- }
+ format: x => {
+ return x + " min";
+ }
+ },
+ 'Dismiss delay': {
+ value: settings.dismissDelayMin,
+ min: 5, max: 60,
+ onchange: v => {
+ settings.dismissDelayMin = v;
+ activityreminder.writeSettings(settings);
+ },
+ format: x => {
+ return x + " min";
+ }
+ },
+ 'Pause delay': {
+ value: settings.pauseDelayMin,
+ min: 30, max: 240,
+ onchange: v => {
+ settings.pauseDelayMin = v;
+ activityreminder.writeSettings(settings);
+ },
+ format: x => {
+ return x + " min";
+ }
+ },
+ 'Min steps': {
+ value: settings.minSteps,
+ min: 10, max: 500,
+ onchange: v => {
+ settings.minSteps = v;
+ activityreminder.writeSettings(settings);
+ }
+ }
});
})
diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog
index 41dd93081..b00055334 100644
--- a/apps/alarm/ChangeLog
+++ b/apps/alarm/ChangeLog
@@ -24,3 +24,8 @@
0.23: Fix regression with Days of Week (#1735)
0.24: Automatically save the alarm/timer when the user returns to the main menu using the back arrow
Add "Enable All", "Disable All" and "Remove All" actions
+0.25: Fix redrawing selected Alarm/Timer entry inside edit submenu
+0.26: Add support for Monday as first day of the week (#1780)
+0.27: New UI!
+0.28: Fix bug with alarms not firing when configured to fire only once
+0.29: Fix wrong 'dow' handling in new timer if first day of week is Monday
diff --git a/apps/alarm/README.md b/apps/alarm/README.md
index e979dbaf1..741946b0c 100644
--- a/apps/alarm/README.md
+++ b/apps/alarm/README.md
@@ -1,7 +1,31 @@
-Alarms & Timers
-===============
+# Alarms & Timers
This app allows you to add/modify any alarms and timers.
-It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched)
-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
+
+- `New...`
+ - `New Alarm` → Configure a new alarm
+ - `Repeat` → 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` → Configure a new timer
+- `Advanced`
+ - `Scheduler settings` → 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` → Enable _all_ disabled alarms & timers
+ - `Disable All` → Disable _all_ enabled alarms & timers
+ - `Delete All` → Delete _all_ alarms & timers
+
+## Creator
+
+- [Gordon Williams](https://github.com/gfwilliams)
+
+## Main Contributors
+
+- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
+- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
+- [storm64](https://github.com/storm64) - Fix redrawing in submenus
+
+## Attributions
+
+All icons used in this app are from [icons8](https://icons8.com).
diff --git a/apps/alarm/app.js b/apps/alarm/app.js
index 3b3421115..fe0f67dbb 100644
--- a/apps/alarm/app.js
+++ b/apps/alarm/app.js
@@ -1,240 +1,358 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
+// 0 = Sunday (default), 1 = Monday
+const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0;
+const WORKDAYS = 62
+const WEEKEND = firstDayOfWeek ? 192 : 65;
+const EVERY_DAY = firstDayOfWeek ? 254 : 127;
+
+const iconAlarmOn = "\0" + atob("GBiBAAAAAAAAAAYAYA4AcBx+ODn/nAP/wAf/4A/n8A/n8B/n+B/n+B/n+B/n+B/h+B/4+A/+8A//8Af/4AP/wAH/gAB+AAAAAAAAAA==");
+const iconAlarmOff = "\0" + (g.theme.dark
+ ? atob("GBjBAP////8AAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg=")
+ : atob("GBjBAP//AAAAAAAAAAAGAGAOAHAcfjg5/5wD/8AH/+AP5/AP5/Af5/gf5/gf5wAf5gAf4Hgf+f4P+bYP8wMH84cD84cB8wMAebYAAf4AAHg="));
+
+const iconTimerOn = "\0" + (g.theme.dark
+ ? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA=")
+ : atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5wAAwwABgYABgYABgYAH/+AH/+AAAAAAAAAAAAA="));
+const iconTimerOff = "\0" + (g.theme.dark
+ ? atob("GBjBAP////8AAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg=")
+ : atob("GBjBAP//AAAAAAAAAAAAAAAH/+AH/+ABgYABgYABgYAA/wAA/wAAfgAAPAAAPAAAfgAA5HgAwf4BgbYBgwMBg4cH84cH8wMAAbYAAf4AAHg="));
+
// An array of alarm objects (see sched/README.md)
-let alarms = require("sched").getAlarms();
+var alarms = require("sched").getAlarms();
-function getCurrentTime() {
- let time = new Date();
- return (
- time.getHours() * 3600000 +
- time.getMinutes() * 60000 +
- time.getSeconds() * 1000
- );
+function handleFirstDayOfWeek(dow) {
+ if (firstDayOfWeek == 1) {
+ if ((dow & 1) == 1) {
+ // In the scheduler API Sunday is 1.
+ // Here the week starts on Monday and Sunday is ON so
+ // when I read the dow I need to move Sunday to 128...
+ dow += 127;
+ } else if ((dow & 128) == 128) {
+ // ... and then when I write the dow I need to move Sunday back to 1.
+ dow -= 127;
+ }
+ }
+ return dow;
}
-function saveAndReload() {
- require("sched").setAlarms(alarms);
- require("sched").reload();
-}
+// Check the first day of week and update the dow field accordingly (alarms only!)
+alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
function showMainMenu() {
- // Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w")
- // Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA")
const menu = {
- '': { 'title': /*LANG*/'Alarms&Timers' },
- /*LANG*/'< Back' : ()=>{load();},
- /*LANG*/'New Alarm': ()=>editAlarm(-1),
- /*LANG*/'New Timer': ()=>editTimer(-1)
+ "": { "title": /*LANG*/"Alarms & Timers" },
+ "< Back": () => load(),
+ /*LANG*/"New...": () => showNewMenu()
};
- alarms.forEach((alarm,idx)=>{
- var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
- if (alarm.timer) {
- type = /*LANG*/"Timer";
- txt = " "+require("sched").formatTime(alarm.timer);
- } else {
- type = /*LANG*/"Alarm";
- txt = " "+require("sched").formatTime(alarm.t);
- }
- if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
- // rename duplicate alarms
- if (menu[type+txt]) {
- var n = 2;
- while (menu[type+" "+n+txt]) n++;
- txt = type+" "+n+txt;
- } else txt = type+txt;
- // add to menu
- menu[txt] = {
- value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
- onchange : function() {
- if (alarm.timer) editTimer(idx, alarm);
- else editAlarm(idx, alarm);
- }
+
+ alarms.forEach((e, index) => {
+ var label = e.timer
+ ? require("time_utils").formatDuration(e.timer)
+ : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : "");
+ menu[label] = {
+ value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
+ onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index)
};
});
- if (alarms.some(e => !e.on)) {
- menu[/*LANG*/"Enable All"] = () => enableAll(true);
- }
- if (alarms.some(e => e.on)) {
- menu[/*LANG*/"Disable All"] = () => enableAll(false);
- }
- if (alarms.length > 0) {
- menu[/*LANG*/"Delete All"] = () => deleteAll();
- }
+ menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
- if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
- return E.showMenu(menu);
-}
-
-function editDOW(dow, onchange) {
- const menu = {
- '': { 'title': /*LANG*/'Days of Week' },
- /*LANG*/'< Back' : () => onchange(dow)
- };
- for (let i = 0; i < 7; i++) (i => {
- let dayOfWeek = require("locale").dow({ getDay: () => i });
- menu[dayOfWeek] = {
- value: !!(dow&(1< v ? /*LANG*/"Yes" : /*LANG*/"No",
- onchange: v => v ? dow |= 1< showMainMenu(),
+ /*LANG*/"Alarm": () => showEditAlarmMenu(undefined, undefined),
+ /*LANG*/"Timer": () => showEditTimerMenu(undefined, undefined)
+ });
+}
+
+function showEditAlarmMenu(selectedAlarm, alarmIndex) {
+ var isNew = alarmIndex === undefined;
+
+ var alarm = require("sched").newDefaultAlarm();
+ alarm.dow = handleFirstDayOfWeek(alarm.dow);
+
+ if (selectedAlarm) {
+ Object.assign(alarm, selectedAlarm);
+ }
+
+ var time = require("time_utils").decodeTime(alarm.t);
const menu = {
- '': { 'title': /*LANG*/'Alarm' },
- /*LANG*/'< Back': () => {
- saveAlarm(newAlarm, alarmIndex, a, t);
+ "": { "title": isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm" },
+ "< Back": () => {
+ saveAlarm(alarm, alarmIndex, time);
showMainMenu();
},
- /*LANG*/'Hours': {
- value: t.hrs, min : 0, max : 23, wrap : true,
- onchange: v => t.hrs=v
+ /*LANG*/"Hour": {
+ value: time.h,
+ format: v => ("0" + v).substr(-2),
+ min: 0,
+ max: 23,
+ wrap: true,
+ onchange: v => time.h = v
},
- /*LANG*/'Minutes': {
- value: t.mins, min : 0, max : 59, wrap : true,
- onchange: v => t.mins=v
+ /*LANG*/"Minute": {
+ value: time.m,
+ format: v => ("0" + v).substr(-2),
+ min: 0,
+ max: 59,
+ wrap: true,
+ onchange: v => time.m = v
},
- /*LANG*/'Enabled': {
- value: a.on,
- format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
- onchange: v=>a.on=v
+ /*LANG*/"Enabled": {
+ value: alarm.on,
+ onchange: v => alarm.on = v
},
- /*LANG*/'Repeat': {
- value: a.rp,
- format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
- onchange: v => a.rp = v
- },
- /*LANG*/'Days': {
- value: "SMTWTFS".split("").map((d,n)=>a.dow&(1< editDOW(a.dow, d => {
- a.dow = d;
- a.t = require("sched").encodeTime(t);
- editAlarm(alarmIndex, a);
+ /*LANG*/"Repeat": {
+ value: decodeDOW(alarm),
+ onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
+ alarm.rp = repeat;
+ alarm.dow = dow;
+ alarm.t = require("time_utils").encodeTime(time);
+ setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
})
},
- /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
- /*LANG*/'Auto Snooze': {
- value: a.as,
- format: v => v ? /*LANG*/"Yes" : /*LANG*/"No",
- onchange: v => a.as = v
+ /*LANG*/"Vibrate": require("buzz_menu").pattern(alarm.vibrate, v => alarm.vibrate = v),
+ /*LANG*/"Auto Snooze": {
+ value: alarm.as,
+ onchange: v => alarm.as = v
+ },
+ /*LANG*/"Cancel": () => showMainMenu()
+ };
+
+ if (!isNew) {
+ menu[/*LANG*/"Delete"] = () => {
+ E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Alarm" }).then((confirm) => {
+ if (confirm) {
+ alarms.splice(alarmIndex, 1);
+ saveAndReload();
+ showMainMenu();
+ } else {
+ alarm.t = require("time_utils").encodeTime(time);
+ setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex);
+ }
+ });
+ };
+ }
+
+ E.showMenu(menu);
+}
+
+function saveAlarm(alarm, alarmIndex, time) {
+ alarm.t = require("time_utils").encodeTime(time);
+ alarm.last = alarm.t < require("time_utils").getCurrentTimeMillis() ? new Date().getDate() : 0;
+
+ if (alarmIndex === undefined) {
+ alarms.push(alarm);
+ } else {
+ alarms[alarmIndex] = alarm;
+ }
+
+ saveAndReload();
+}
+
+function saveAndReload() {
+ // Before saving revert the dow to the standard format (alarms only!)
+ alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
+
+ require("sched").setAlarms(alarms);
+ require("sched").reload();
+
+ // Fix after save
+ alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
+}
+
+function decodeDOW(alarm) {
+ return alarm.rp
+ ? require("date_utils")
+ .dows(firstDayOfWeek, 2)
+ .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
+ .join("")
+ .toLowerCase()
+ : "Once"
+}
+
+function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
+ var originalRepeat = repeat;
+ var originalDow = dow;
+ var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
+
+ const menu = {
+ "": { "title": /*LANG*/"Repeat Alarm" },
+ "< Back": () => dowChangeCallback(repeat, dow),
+ /*LANG*/"Once": {
+ // The alarm will fire once. Internally it will be saved
+ // as "fire every days" BUT the repeat flag is false so
+ // we avoid messing up with the scheduler.
+ value: !repeat,
+ onchange: () => dowChangeCallback(false, EVERY_DAY)
+ },
+ /*LANG*/"Workdays": {
+ value: repeat && dow == WORKDAYS,
+ onchange: () => dowChangeCallback(true, WORKDAYS)
+ },
+ /*LANG*/"Weekends": {
+ value: repeat && dow == WEEKEND,
+ onchange: () => dowChangeCallback(true, WEEKEND)
+ },
+ /*LANG*/"Every Day": {
+ value: repeat && dow == EVERY_DAY,
+ onchange: () => dowChangeCallback(true, EVERY_DAY)
+ },
+ /*LANG*/"Custom": {
+ value: isCustom ? decodeDOW({ rp: true, dow: dow }) : false,
+ onchange: () => setTimeout(showCustomDaysMenu, 10, isCustom ? dow : EVERY_DAY, dowChangeCallback, originalRepeat, originalDow)
}
};
- menu[/*LANG*/"Cancel"] = () => showMainMenu();
-
- if (!newAlarm) {
- menu[/*LANG*/"Delete"] = function () {
- alarms.splice(alarmIndex, 1);
- saveAndReload();
- showMainMenu();
- };
- }
-
- return E.showMenu(menu);
+ E.showMenu(menu);
}
-function saveAlarm(newAlarm, alarmIndex, a, t) {
- a.t = require("sched").encodeTime(t);
- a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
-
- if (newAlarm) {
- alarms.push(a);
- } else {
- alarms[alarmIndex] = a;
- }
-
- saveAndReload();
-}
-
-function editTimer(alarmIndex, alarm) {
- let newAlarm = alarmIndex < 0;
- let a = require("sched").newDefaultTimer();
- if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
- if (alarm) Object.assign(a,alarm);
- let t = require("sched").decodeTime(a.timer);
-
+function showCustomDaysMenu(dow, dowChangeCallback, originalRepeat, originalDow) {
const menu = {
- '': { 'title': /*LANG*/'Timer' },
- /*LANG*/'< Back': () => {
- saveTimer(newAlarm, alarmIndex, a, t);
- showMainMenu();
- },
- /*LANG*/'Hours': {
- value: t.hrs, min : 0, max : 23, wrap : true,
- onchange: v => t.hrs=v
- },
- /*LANG*/'Minutes': {
- value: t.mins, min : 0, max : 59, wrap : true,
- onchange: v => t.mins=v
- },
- /*LANG*/'Enabled': {
- value: a.on,
- format: v => v ? /*LANG*/"On" : /*LANG*/"Off",
- onchange: v => a.on = v
- },
- /*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
+ "": { "title": /*LANG*/"Custom Days" },
+ "< Back": () => {
+ // If the user unchecks all the days then we assume repeat = once
+ // and we force the dow to every day.
+ var repeat = dow > 0;
+ dowChangeCallback(repeat, repeat ? dow : EVERY_DAY)
+ }
};
- menu[/*LANG*/"Cancel"] = () => showMainMenu();
-
- if (!newAlarm) {
- menu[/*LANG*/"Delete"] = function() {
- alarms.splice(alarmIndex,1);
- saveAndReload();
- showMainMenu();
+ require("date_utils").dows(firstDayOfWeek).forEach((day, i) => {
+ menu[day] = {
+ value: !!(dow & (1 << (i + firstDayOfWeek))),
+ onchange: v => v ? (dow |= 1 << (i + firstDayOfWeek)) : (dow &= ~(1 << (i + firstDayOfWeek)))
};
- }
- return E.showMenu(menu);
+ });
+
+ menu[/*LANG*/"Cancel"] = () => setTimeout(showEditRepeatMenu, 10, originalRepeat, originalDow, dowChangeCallback)
+
+ E.showMenu(menu);
}
-function saveTimer(newAlarm, alarmIndex, a, t) {
- a.timer = require("sched").encodeTime(t);
- a.t = getCurrentTime() + a.timer;
- a.last = 0;
+function showEditTimerMenu(selectedTimer, timerIndex) {
+ var isNew = timerIndex === undefined;
- if (newAlarm) {
- alarms.push(a);
+ var timer = require("sched").newDefaultTimer();
+
+ if (selectedTimer) {
+ Object.assign(timer, selectedTimer);
+ }
+
+ var time = require("time_utils").decodeTime(timer.timer);
+
+ const menu = {
+ "": { "title": isNew ? /*LANG*/"New Timer" : /*LANG*/"Edit Timer" },
+ "< Back": () => {
+ saveTimer(timer, timerIndex, time);
+ showMainMenu();
+ },
+ /*LANG*/"Hours": {
+ value: time.h,
+ min: 0,
+ max: 23,
+ wrap: true,
+ onchange: v => time.h = v
+ },
+ /*LANG*/"Minutes": {
+ value: time.m,
+ min: 0,
+ max: 59,
+ wrap: true,
+ onchange: v => time.m = v
+ },
+ /*LANG*/"Enabled": {
+ value: timer.on,
+ onchange: v => timer.on = v
+ },
+ /*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v),
+ };
+
+ if (!isNew) {
+ menu[/*LANG*/"Delete"] = () => {
+ E.showPrompt(/*LANG*/"Are you sure?", { title: /*LANG*/"Delete Timer" }).then((confirm) => {
+ if (confirm) {
+ alarms.splice(timerIndex, 1);
+ saveAndReload();
+ showMainMenu();
+ } else {
+ timer.timer = require("time_utils").encodeTime(time);
+ setTimeout(showEditTimerMenu, 10, timer, timerIndex)
+ }
+ });
+ };
+ }
+
+ E.showMenu(menu);
+}
+
+function saveTimer(timer, timerIndex, time) {
+ timer.timer = require("time_utils").encodeTime(time);
+ timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
+ timer.last = 0;
+
+ if (timerIndex === undefined) {
+ alarms.push(timer);
} else {
- alarms[alarmIndex] = a;
+ alarms[timerIndex] = timer;
}
saveAndReload();
}
+function showAdvancedMenu() {
+ E.showMenu({
+ "": { "title": /*LANG*/"Advanced" },
+ "< Back": () => showMainMenu(),
+ /*LANG*/"Scheduler Settings": () => eval(require("Storage").read("sched.settings.js"))(() => showAdvancedMenu()),
+ /*LANG*/"Enable All": () => enableAll(true),
+ /*LANG*/"Disable All": () => enableAll(false),
+ /*LANG*/"Delete All": () => deleteAll()
+ });
+}
+
function enableAll(on) {
- E.showPrompt(/*LANG*/"Are you sure?", {
- title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
- }).then((confirm) => {
- if (confirm) {
- alarms.forEach(alarm => alarm.on = on);
- saveAndReload();
- }
-
- showMainMenu();
- });
+ if (alarms.filter(e => e.on == !on).length == 0) {
+ E.showAlert(
+ on ? /*LANG*/"Nothing to Enable" : /*LANG*/"Nothing to Disable",
+ on ? /*LANG*/"Enable All" : /*LANG*/"Disable All"
+ ).then(() => showAdvancedMenu());
+ } else {
+ E.showPrompt(/*LANG*/"Are you sure?", { title: on ? /*LANG*/"Enable All" : /*LANG*/"Disable All" }).then((confirm) => {
+ if (confirm) {
+ alarms.forEach(alarm => alarm.on = on);
+ saveAndReload();
+ showMainMenu();
+ } else {
+ showAdvancedMenu();
+ }
+ });
+ }
}
function deleteAll() {
- E.showPrompt(/*LANG*/"Are you sure?", {
- title: /*LANG*/"Delete All"
- }).then((confirm) => {
- if (confirm) {
- alarms = [];
- saveAndReload();
- }
-
- showMainMenu();
- });
+ if (alarms.length == 0) {
+ E.showAlert(/*LANG*/"Nothing to delete", /*LANG*/"Delete All").then(() => showAdvancedMenu());
+ } else {
+ E.showPrompt(/*LANG*/"Are you sure?", {
+ title: /*LANG*/"Delete All"
+ }).then((confirm) => {
+ if (confirm) {
+ alarms = [];
+ saveAndReload();
+ showMainMenu();
+ } else {
+ showAdvancedMenu();
+ }
+ });
+ }
}
showMainMenu();
diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json
index 2084c2a30..cac837b5e 100644
--- a/apps/alarm/metadata.json
+++ b/apps/alarm/metadata.json
@@ -2,16 +2,29 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
- "version": "0.24",
+ "version": "0.29",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm,widget",
- "supports": ["BANGLEJS","BANGLEJS2"],
+ "supports": [ "BANGLEJS", "BANGLEJS2" ],
"readme": "README.md",
- "dependencies": {"scheduler":"type"},
+ "dependencies": { "scheduler":"type" },
"storage": [
- {"name":"alarm.app.js","url":"app.js"},
- {"name":"alarm.img","url":"app-icon.js","evaluate":true},
- {"name":"alarm.wid.js","url":"widget.js"}
+ { "name": "alarm.app.js", "url": "app.js" },
+ { "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
+ { "name": "alarm.wid.js", "url": "widget.js" }
+ ],
+ "screenshots": [
+ { "url": "screenshot-1.png" },
+ { "url": "screenshot-2.png" },
+ { "url": "screenshot-3.png" },
+ { "url": "screenshot-4.png" },
+ { "url": "screenshot-5.png" },
+ { "url": "screenshot-6.png" },
+ { "url": "screenshot-7.png" },
+ { "url": "screenshot-8.png" },
+ { "url": "screenshot-9.png" },
+ { "url": "screenshot-10.png" },
+ { "url": "screenshot-11.png" }
]
}
diff --git a/apps/alarm/screenshot-1.png b/apps/alarm/screenshot-1.png
new file mode 100644
index 000000000..d2bd3a409
Binary files /dev/null and b/apps/alarm/screenshot-1.png differ
diff --git a/apps/alarm/screenshot-10.png b/apps/alarm/screenshot-10.png
new file mode 100644
index 000000000..1e6e516c3
Binary files /dev/null and b/apps/alarm/screenshot-10.png differ
diff --git a/apps/alarm/screenshot-11.png b/apps/alarm/screenshot-11.png
new file mode 100644
index 000000000..197c84194
Binary files /dev/null and b/apps/alarm/screenshot-11.png differ
diff --git a/apps/alarm/screenshot-2.png b/apps/alarm/screenshot-2.png
new file mode 100644
index 000000000..1cbc255a9
Binary files /dev/null and b/apps/alarm/screenshot-2.png differ
diff --git a/apps/alarm/screenshot-3.png b/apps/alarm/screenshot-3.png
new file mode 100644
index 000000000..a165d3594
Binary files /dev/null and b/apps/alarm/screenshot-3.png differ
diff --git a/apps/alarm/screenshot-4.png b/apps/alarm/screenshot-4.png
new file mode 100644
index 000000000..7fd7e99b6
Binary files /dev/null and b/apps/alarm/screenshot-4.png differ
diff --git a/apps/alarm/screenshot-5.png b/apps/alarm/screenshot-5.png
new file mode 100644
index 000000000..4174c5670
Binary files /dev/null and b/apps/alarm/screenshot-5.png differ
diff --git a/apps/alarm/screenshot-6.png b/apps/alarm/screenshot-6.png
new file mode 100644
index 000000000..dc579ca5c
Binary files /dev/null and b/apps/alarm/screenshot-6.png differ
diff --git a/apps/alarm/screenshot-7.png b/apps/alarm/screenshot-7.png
new file mode 100644
index 000000000..49da44710
Binary files /dev/null and b/apps/alarm/screenshot-7.png differ
diff --git a/apps/alarm/screenshot-8.png b/apps/alarm/screenshot-8.png
new file mode 100644
index 000000000..86d69cd93
Binary files /dev/null and b/apps/alarm/screenshot-8.png differ
diff --git a/apps/alarm/screenshot-9.png b/apps/alarm/screenshot-9.png
new file mode 100644
index 000000000..2d8c7fc83
Binary files /dev/null and b/apps/alarm/screenshot-9.png differ
diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog
index 96b50c3a0..f13ccd95c 100644
--- a/apps/android/ChangeLog
+++ b/apps/android/ChangeLog
@@ -7,3 +7,5 @@
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
0.07: Include charging state in battery updates to phone
0.08: Handling of alarms
+0.09: Alarm vibration, repeat, and auto-snooze now handled by sched
+0.10: Fix SMS bug
diff --git a/apps/android/README.md b/apps/android/README.md
index 580eeec9a..c10718aac 100644
--- a/apps/android/README.md
+++ b/apps/android/README.md
@@ -21,7 +21,6 @@ of Gadgetbridge - making your phone make noise so you can find it.
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
keep any messages it has received, or should it delete them?
* `Messages` - launches the messages app, showing a list of messages
-* `Alarms` - opens a submenu where you can set default settings for alarms such as vibration pattern, repeat, and auto snooze
## How it works
diff --git a/apps/android/boot.js b/apps/android/boot.js
index 9e24c9893..efd7e7e46 100644
--- a/apps/android/boot.js
+++ b/apps/android/boot.js
@@ -3,6 +3,7 @@
Bluetooth.println("");
Bluetooth.println(JSON.stringify(message));
}
+ var lastMsg;
var settings = require("Storage").readJSON("android.settings.json",1)||{};
//default alarm settings
@@ -18,7 +19,17 @@
/* TODO: Call handling, fitness */
var HANDLERS = {
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
- "notify" : function() { Object.assign(event,{t:"add",positive:true, negative:true});require("messages").pushMessage(event); },
+ "notify" : function() {
+ Object.assign(event,{t:"add",positive:true, negative:true});
+ // Detect a weird GadgetBridge bug and fix it
+ // For some reason SMS messages send two GB notifications, with different sets of info
+ if (lastMsg && event.body == lastMsg.body && lastMsg.src == undefined && event.src == "Messages") {
+ // Mutate the other message
+ event.id = lastMsg.id;
+ }
+ lastMsg = event;
+ require("messages").pushMessage(event);
+ },
// {t:"notify~",id:int, title:string} // modified
"notify~" : function() { event.t="modify";require("messages").pushMessage(event); },
// {t:"notify-",id:int} // remove
@@ -67,17 +78,13 @@
var dow = event.d[j].rep;
if (!dow) dow = 127; //if no DOW selected, set alarm to all DOW
var last = (event.d[j].h * 3600000 + event.d[j].m * 60000 < currentTime) ? (new Date()).getDate() : 0;
- var a = {
- id : "gb"+j,
- appid : "gbalarms",
- on : true,
- t : event.d[j].h * 3600000 + event.d[j].m * 60000,
- dow : ((dow&63)<<1) | (dow>>6), // Gadgetbridge sends DOW in a different format
- last : last,
- rp : settings.rp,
- as : settings.as,
- vibrate : settings.vibrate
- };
+ var a = require("sched").newDefaultAlarm();
+ a.id = "gb"+j;
+ a.appid = "gbalarms";
+ a.on = true;
+ a.t = event.d[j].h * 3600000 + event.d[j].m * 60000;
+ a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format
+ a.last = last;
alarms.push(a);
}
sched.setAlarms(alarms);
diff --git a/apps/android/metadata.json b/apps/android/metadata.json
index 203cd18b1..bf37b8407 100644
--- a/apps/android/metadata.json
+++ b/apps/android/metadata.json
@@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
- "version": "0.08",
+ "version": "0.10",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",
diff --git a/apps/android/settings.js b/apps/android/settings.js
index 9f72947ab..695d483c6 100644
--- a/apps/android/settings.js
+++ b/apps/android/settings.js
@@ -25,27 +25,6 @@
}
},
/*LANG*/"Messages" : ()=>load("messages.app.js"),
- /*LANG*/"Alarms" : () => E.showMenu({
- "" : { "title" : /*LANG*/"Alarms" },
- "< Back" : ()=>E.showMenu(mainmenu),
- /*LANG*/"Vibrate": require("buzz_menu").pattern(settings.vibrate, v => {settings.vibrate = v; updateSettings();}),
- /*LANG*/"Repeat": {
- value: settings.rp,
- format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
- onchange: v => {
- settings.rp = v;
- updateSettings();
- }
- },
- /*LANG*/"Auto snooze": {
- value: settings.as,
- format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
- onchange: v => {
- settings.as = v;
- updateSettings();
- }
- },
- })
};
E.showMenu(mainmenu);
})
diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog
index 316660fc6..5df032c4d 100644
--- a/apps/barclock/ChangeLog
+++ b/apps/barclock/ChangeLog
@@ -7,3 +7,5 @@
0.07: Update to use Bangle.setUI instead of setWatch
0.08: Use theme colors, Layout library
0.09: Fix time/date disappearing after fullscreen notification
+0.10: Use ClockFace library
+0.11: Use ClockFace.is12Hour
diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js
index 5d46a1cb4..987d41cc6 100644
--- a/apps/barclock/clock-bar.js
+++ b/apps/barclock/clock-bar.js
@@ -3,7 +3,6 @@
* A simple digital clock showing seconds as a bar
**/
// Check settings for what type our clock should be
-const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
let locale = require("locale");
{ // add some more info to locale
let date = new Date();
@@ -11,13 +10,9 @@ let locale = require("locale");
date.setMonth(1, 3); // februari: months are zero-indexed
const localized = locale.date(date, true);
locale.dayFirst = /3.*2/.test(localized);
-
- locale.hasMeridian = false;
- if (typeof locale.meridian==="function") { // function does not exist if languages app is not installed
- locale.hasMeridian = (locale.meridian(date)!=="");
- }
+ locale.hasMeridian = (locale.meridian(date)!=="");
}
-Bangle.loadWidgets();
+
function renderBar(l) {
if (!this.fraction) {
// zero-size fillRect stills draws one line of pixels, we don't want that
@@ -27,35 +22,9 @@ function renderBar(l) {
g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1);
}
-const Layout = require("Layout");
-const layout = new Layout({
- type: "v", c: [
- {
- type: "h", c: [
- {id: "time", label: "88:88", type: "txt", font: "6x8:5", bgCol: g.theme.bg}, // size updated below
- {id: "ampm", label: " ", type: "txt", font: "6x8:2", bgCol: g.theme.bg},
- ],
- },
- {id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
- {height: 40},
- {id: "date", type: "txt", font: "10%", valign: 1},
- ],
-}, {lazy: true});
-// adjustments based on screen size and whether we display am/pm
-let thickness; // bar thickness, same as time font "pixel block" size
-if (is12Hour) {
- // Maximum font size = ( - ) / (5chars * 6px)
- thickness = Math.floor((g.getWidth()-24)/(5*6));
-} else {
- layout.ampm.label = "";
- thickness = Math.floor(g.getWidth()/(5*6));
-}
-layout.bar.height = thickness+1;
-layout.time.font = "6x8:"+thickness;
-layout.update();
function timeText(date) {
- if (!is12Hour) {
+ if (!clock.is12Hour) {
return locale.time(date, true);
}
const date12 = new Date(date.getTime());
@@ -68,7 +37,7 @@ function timeText(date) {
return locale.time(date12, true);
}
function ampmText(date) {
- return (is12Hour && locale.hasMeridian)? locale.meridian(date) : "";
+ return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
}
function dateText(date) {
const dayName = locale.dow(date, true),
@@ -78,31 +47,48 @@ function dateText(date) {
return `${dayName} ${dayMonth}`;
}
-draw = function draw(force) {
- if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled
- const date = new Date();
- layout.time.label = timeText(date);
- layout.ampm.label = ampmText(date);
- layout.date.label = dateText(date);
- const SECONDS_PER_MINUTE = 60;
- layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
- if (force) {
- Bangle.drawWidgets();
- layout.forgetLazyState();
- }
- layout.render();
- // schedule update at start of next second
- const millis = date.getMilliseconds();
- setTimeout(draw, 1000-millis);
-};
-// Show launcher when button pressed
-Bangle.setUI("clock");
-Bangle.on("lcdPower", function(on) {
- if (on) {
- draw(true);
- }
-});
-g.reset().clear();
-Bangle.drawWidgets();
-draw();
+const ClockFace = require("ClockFace"),
+ clock = new ClockFace({
+ precision:1,
+ init: function() {
+ const Layout = require("Layout");
+ this.layout = new Layout({
+ type: "v", c: [
+ {
+ type: "h", c: [
+ {id: "time", label: "88:88", type: "txt", font: "6x8:5", col:g.theme.fg, bgCol: g.theme.bg}, // size updated below
+ {id: "ampm", label: " ", type: "txt", font: "6x8:2", col:g.theme.fg, bgCol: g.theme.bg},
+ ],
+ },
+ {id: "bar", type: "custom", fraction: 0, fillx: 1, height: 6, col: g.theme.fg2, render: renderBar},
+ {height: 40},
+ {id: "date", type: "txt", font: "10%", valign: 1},
+ ],
+ }, {lazy: true});
+ // adjustments based on screen size and whether we display am/pm
+ let thickness; // bar thickness, same as time font "pixel block" size
+ if (this.is12Hour) {
+ // Maximum font size = ( - ) / (5chars * 6px)
+ thickness = Math.floor((Bangle.appRect.w-24)/(5*6));
+ } else {
+ this.layout.ampm.label = "";
+ thickness = Math.floor(Bangle.appRect.w/(5*6));
+ }
+ this.layout.bar.height = thickness+1;
+ this.layout.time.font = "6x8:"+thickness;
+ this.layout.update();
+ },
+ update: function(date, c) {
+ if (c.m) this.layout.time.label = timeText(date);
+ if (c.h) this.layout.ampm.label = ampmText(date);
+ if (c.d) this.layout.date.label = dateText(date);
+ const SECONDS_PER_MINUTE = 60;
+ if (c.s) this.layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE;
+ this.layout.render();
+ },
+ resume: function() {
+ this.layout.forgetLazyState();
+ },
+ });
+clock.start();
diff --git a/apps/barclock/metadata.json b/apps/barclock/metadata.json
index 2b7be355f..7bc61096d 100644
--- a/apps/barclock/metadata.json
+++ b/apps/barclock/metadata.json
@@ -1,7 +1,7 @@
{
"id": "barclock",
"name": "Bar Clock",
- "version": "0.09",
+ "version": "0.11",
"description": "A simple digital clock showing seconds as a bar",
"icon": "clock-bar.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog
index e3f492d3b..a43ecf86e 100644
--- a/apps/boot/ChangeLog
+++ b/apps/boot/ChangeLog
@@ -51,3 +51,4 @@
0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk)
0.46: Fix no clock found error on Bangle.js 2
0.47: Add polyfill for setUI with an object as an argument (fix regression for 2v12 devices after Layout module changed)
+0.48: Workaround for BTHRM issues on Bangle.js 1 (write .boot files in chunks)
diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js
index 119cd2c2c..4cb3c52e4 100644
--- a/apps/boot/bootupdate.js
+++ b/apps/boot/bootupdate.js
@@ -197,8 +197,18 @@ bootFiles.forEach(bootFile=>{
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
fileOffset+=2+bootFile.length+1;
var bf = require('Storage').read(bootFile);
- require('Storage').write('.boot0',bf,fileOffset);
- fileOffset+=bf.length;
+ // we can't just write 'bf' in one go because at least in 2v13 and earlier
+ // Espruino wants to read the whole file into RAM first, and on Bangle.js 1
+ // it can be too big (especially BTHRM).
+ var bflen = bf.length;
+ var bfoffset = 0;
+ while (bflen) {
+ var bfchunk = Math.min(bflen, 2048);
+ require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
+ fileOffset+=bfchunk;
+ bfoffset+=bfchunk;
+ bflen-=bfchunk;
+ }
require('Storage').write('.boot0',";\n",fileOffset);
fileOffset+=2;
});
diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json
index d1bf2edde..62adc4db1 100644
--- a/apps/boot/metadata.json
+++ b/apps/boot/metadata.json
@@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
- "version": "0.47",
+ "version": "0.48",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",
diff --git a/apps/bordle/ChangeLog b/apps/bordle/ChangeLog
index f45509a34..ddbd6239c 100644
--- a/apps/bordle/ChangeLog
+++ b/apps/bordle/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App
0.02: app keeps track of statistics now
+0.03: Fix bug in valid word detection
diff --git a/apps/bordle/bordle.app.js b/apps/bordle/bordle.app.js
index 20aa02bc2..07e954a6d 100644
--- a/apps/bordle/bordle.app.js
+++ b/apps/bordle/bordle.app.js
@@ -110,7 +110,12 @@ class Wordle {
}
}
addGuess(w) {
- if ((this.words.indexOf(w.toLowerCase())%5)!=0) {
+ let idx = -1;
+ do{
+ idx = this.words.indexOf(w.toLowerCase(), idx+1);
+ }
+ while(idx !== -1 && idx%5 !== 0);
+ if(idx%5 !== 0) {
E.showAlert(w+"\nis not a word", "Invalid word").then(function() {
layout = getKeyLayout("");
wordle.render(true);
diff --git a/apps/bordle/metadata.json b/apps/bordle/metadata.json
index 37ef5c855..f6011f798 100644
--- a/apps/bordle/metadata.json
+++ b/apps/bordle/metadata.json
@@ -2,7 +2,7 @@
"name": "Bordle",
"shortName":"Bordle",
"icon": "app.png",
- "version":"0.02",
+ "version":"0.03",
"description": "Bangle version of a popular word search game",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
diff --git a/apps/bowserWF/metadata.json b/apps/bowserWF/metadata.json
index 22df2dea4..a0bdfb8e9 100644
--- a/apps/bowserWF/metadata.json
+++ b/apps/bowserWF/metadata.json
@@ -1,14 +1,18 @@
-{ "id": "bowserWF",
+{
+ "id": "bowserWF",
"name": "Bowser Watchface",
"shortName":"Bowser Watchface",
- "version":"0.01",
+ "version":"0.02",
"description": "Let bowser show you the time",
"icon": "app.png",
- "tags": "",
- "supports" : ["BANGLEJS2"],
+ "type": "clock",
+ "tags": "clock",
+ "supports" : ["BANGLEJS2"],
+ "allow_emulator": true,
"readme": "README.md",
"storage": [
{"name":"bowserWF.app.js","url":"app.js"},
{"name":"bowserWF.img","url":"app-icon.js","evaluate":true}
- ]
+ ],
+ "data": [{"name":"bowserWF.json"}]
}
diff --git a/apps/bradbury/app-icon.js b/apps/bradbury/app-icon.js
new file mode 100644
index 000000000..07c4f5582
--- /dev/null
+++ b/apps/bradbury/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwcCkGSpEgwQCChICFkgCBgkQoMEyFJAoICByVBkgLBkkSpIaDEwWShEkFgcIBAIdCEYQCBAoQdBAoYsBC4Q7BpICBEYQCDF4Q7CEYYCCEYUSKYYUDyRlCJQQIBNYYvBMoQCBkgjBFgxxCL4REDFgaPEHYgmCIgosCNYZEEDoZ0CNwY7CIIYgDEYtB9+e/dg/4AB2EJkYEB/mC/fn33Ivvz598v4MB/0BgoRCyVHvmW7Mg2EA8uD/EAh/IkGP/8AgVLtkA5El+FJvoRBgmf4Mkh0HkEQo9kyEfkeQofsgf4kmPCIP+h/gwULkkCncEu/ZsmRI4cEv0H8ESpdgEwMjwXI9kTCIOANYkSEYOCncF+UAjuR/ED+FBg/3/f8RgNgiVPkYdBtkT/Egv0Il+AoMfI4PgyX7vkW799F4Nl//4//woH/+0Ztvx7Fs335sk//5EB/IRBhACB77CBpEkgEIgGQoDRBgEggVBgDdBgGAgPv317ku+5cj334t+OSoI+B8gCBtlx7dkuFfgvx4N8yPbvgOB8ACBR4MA9mf4Egz3IgeChEDwDOBx/AjuCoN8y/JgkX4ME2FBjuQn65BgMtwELkGOEYOO4Mh2EJh+Sh/jOIMd+3fskRcwMTEwOWo98gCSBwFJkm2pfgx3II4PBk++/aABhEfwEInpZBvkX7MkJQMl2FHfANBjgCBlmQhHsgwjB33IkeyBAOChMcEwM9+/ZsBHBboMJtv2hd9+FHZANBVoM7kGC/fv2FJ9+GEYOAh//+UIaIMBkkQpEAHwIIBoMgiFJBANJEAMIkGShEkwQIChIIBhIIBhIaCkmQpIFCgmSEwYpDEYwCCpAICBwUEiQdFEwIICyAIDHwQ7CEYYpCEYWSpA7FDocSEwojBCgIaDIgYCBNwR0BNYYjFEwZTDLgQjGOgYvBEYQ7ENYlJFgQCCDohuGTYpBFkhoCSoQICEYIA="))
diff --git a/apps/bradbury/app.js b/apps/bradbury/app.js
new file mode 100644
index 000000000..147242689
--- /dev/null
+++ b/apps/bradbury/app.js
@@ -0,0 +1,115 @@
+require("Font7x11Numeric7Seg").add(Graphics);
+require("Font5x9Numeric7Seg").add(Graphics);
+require("Font8x12").add(Graphics);
+require("FontDylex7x13").add(Graphics);
+const X = 98, Y = 46;
+var wizible = 0;
+
+function getImg() {
+ return require("heatshrink").decompress(atob("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"));
+}
+
+function draw() {
+ var d = new Date();
+ var h = d.getHours() % 12 || 12, m = d.getMinutes(), yyyy = d.getFullYear(), mm = d.getMonth(), dd = d.getDate();
+ var time = (""+h).substr(-2) + ":" + ("0"+m).substr(-2);
+ g.reset(); // Reset the state of the graphics library
+ g.clear();
+ g.drawImage(getImg()); //load bg image
+ //TIME
+ g.setFont("7x11Numeric7Seg",2);
+ g.setFontAlign(1,1);
+ g.setColor(0,0,1);
+ g.drawString(time, 97, 53, false /*clear background*/);
+ g.setColor(0,0,0);
+ g.drawString(time, 96, 52, false /*clear background*/);
+ //SECONDS
+ g.setFont("7x11Numeric7Seg",1);
+ //g.setFont("5x9Numeric7Seg");
+ g.setFontAlign(-1,1); // align right bottom
+ g.setColor(0,0,1);
+ g.drawString(("0"+d.getSeconds()).substr(-2), 100, 42, 0);
+ g.setColor(0,0,0);
+ g.drawString(("0"+d.getSeconds()).substr(-2), 99, 41, 0);
+ //DATE
+ g.setFont("5x9Numeric7Seg",1);
+ g.setFontAlign(1,1);
+ g.setColor(0,0,1);
+ g.drawString(yyyy+" "+("0"+mm)+" "+dd, 100, 65, 0);
+ g.setColor(0,0,0);
+ g.drawString(yyyy+" "+("0"+mm)+" "+dd, 99, 64, 0);
+ //BATTERY
+ g.setColor(0,0,1);
+ g.drawString(E.getBattery(), 137, 53, 0);
+ g.setColor(0,0,0);
+ g.drawString(E.getBattery(), 136, 52, 0);
+ //STEPS
+ g.setColor(0,0,1);
+ g.drawString(Bangle.getHealthStatus("day").steps, 137, 65, 0);
+ g.setColor(0,0,0);
+ g.drawString(Bangle.getHealthStatus("day").steps, 136, 64, 0);
+ //WEEK DAY
+ g.setFont("8x12");
+ g.setColor(0,0,1);
+ if (d.getDay()==0) {
+ g.drawString("SU", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("SU", 136, 42, 0);
+ } else if (d.getDay()==1) {
+ g.drawString("MO", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("MO", 136, 42, 0);
+ } else if (d.getDay()==2) {
+ g.drawString("TU", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("TU", 136, 42, 0);
+ } else if (d.getDay()==3) {
+ g.drawString("WE", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("WE", 136, 42, 0);
+ } else if (d.getDay()==4) {
+ g.setFont("Dylex7x13");
+ g.drawString("TH", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("TH", 136, 42, 0);
+ } else if (d.getDay()==5) {
+ g.drawString("FR", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("FR", 136, 42, 0);
+ } else {
+ g.drawString("SA", 137, 43, 0);
+ g.setColor(0,0,0);
+ g.drawString("SA", 136, 42, 0);
+ }
+ if(wizible==1){
+ Bangle.drawWidgets();
+ }
+}
+
+// Clear the screen once, at startup
+g.clear();
+// draw immediately at first
+draw();
+var secondInterval = setInterval(draw, 1000);
+// Stop updates when LCD is off, restart when on
+Bangle.on('lcdPower',on=>{
+ if (secondInterval) clearInterval(secondInterval);
+ secondInterval = undefined;
+ if (on) {
+ secondInterval = setInterval(draw, 1000);
+ draw(); // draw immediately
+ }
+});
+// Show launcher when middle button pressed
+Bangle.setUI("clock");
+
+//Toggle Widgets
+Bangle.loadWidgets();
+Bangle.on('touch', function(button) {
+ if(wizible==0){
+ wizible=1;
+ }
+ else if(wizible==1){
+ wizible=0;
+ }
+});
diff --git a/apps/bradbury/app.png b/apps/bradbury/app.png
new file mode 100644
index 000000000..f7141d15e
Binary files /dev/null and b/apps/bradbury/app.png differ
diff --git a/apps/bradbury/metadata.json b/apps/bradbury/metadata.json
new file mode 100644
index 000000000..456daa381
--- /dev/null
+++ b/apps/bradbury/metadata.json
@@ -0,0 +1,14 @@
+{ "id": "bradbury",
+ "name": "Bradbury Watch",
+ "shortName":"Bradbury",
+ "icon": "app.png",
+ "screenshots": [{"url":"screenshot.png"}],
+ "version":"0.01",
+ "description": "A watch face based on the classic Seiko model worn by one of my favorite authors. I didn't follow the original lcd layout exactly, opting for larger font for more easily readable time, and adding date, battery level, and step count; read from the device. Tapping the screen toggles visibility of widgets.",
+ "type": "clock",
+ "supports":["BANGLEJS2"],
+ "storage": [
+ {"name":"bradbury.app.js","url":"app.js"},
+ {"name":"bradbury.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/bradbury/screenshot.png b/apps/bradbury/screenshot.png
new file mode 100644
index 000000000..914266668
Binary files /dev/null and b/apps/bradbury/screenshot.png differ
diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog
index 41eec666a..7ca8319b6 100644
--- a/apps/bthrm/ChangeLog
+++ b/apps/bthrm/ChangeLog
@@ -21,3 +21,4 @@
Adds some preset modes and a custom one
Restructure the settings menu
0.08: Allow scanning for devices in settings
+0.09: Misc Fixes and improvements (https://github.com/espruino/BangleApps/pull/1655)
diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md
index 42ad619bd..8d5872670 100644
--- a/apps/bthrm/README.md
+++ b/apps/bthrm/README.md
@@ -2,7 +2,7 @@
When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.
-HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor.
+HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM')` event as if it came from the on board monitor.
This means it's compatible with many Bangle.js apps including:
@@ -16,19 +16,23 @@ as that requires live sensor data (rather than just BPM readings).
Just install the app, then install an app that uses the heart rate monitor.
-Once installed it'll automatically try and connect to the first bluetooth
-heart rate monitor it finds.
+Once installed you will have to go into this app's settings while your heart rate monitor
+ is available for bluetooth pairing and scan for devices.
**To disable this and return to normal HRM, uninstall the app**
## Compatible Heart Rate Monitors
This works with any heart rate monitor providing the standard Bluetooth
-Heart Rate Service (`180D`) and characteristic (`2A37`).
+Heart Rate Service (`180D`) and characteristic (`2A37`). It additionally supports
+the location (`2A38`) characteristic and the Battery Service (`180F`), reporting
+that information in the `BTHRM` event when they are available.
So far it has been tested on:
* CooSpo Bluetooth Heart Rate Monitor
+* Polar H10
+* Polar OH1
* Wahoo TICKR X 2
## Internals
@@ -38,7 +42,6 @@ This replaces `Bangle.setHRMPower` with its own implementation.
## TODO
* A widget to show connection state?
-* Specify a specific device by address?
## Creator
diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js
index 3a1f1cc4c..e9e640563 100644
--- a/apps/bthrm/boot.js
+++ b/apps/bthrm/boot.js
@@ -3,7 +3,7 @@
require('Storage').readJSON("bthrm.default.json", true) || {},
require('Storage').readJSON("bthrm.json", true) || {}
);
-
+
var log = function(text, param){
if (settings.debuglog){
var logline = new Date().toISOString() + " - " + text;
@@ -13,39 +13,38 @@
print(logline);
}
};
-
+
log("Settings: ", settings);
-
+
if (settings.enabled){
- function clearCache(){
+ var clearCache = function() {
return require('Storage').erase("bthrm.cache.json");
- }
+ };
- function getCache(){
+ var getCache = function() {
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
- if (settings.btname && settings.btname == cache.name) return cache;
+ if (settings.btid && settings.btid === cache.id) return cache;
clearCache();
return {};
- }
-
- function addNotificationHandler(characteristic){
+ };
+
+ var addNotificationHandler = function(characteristic) {
log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler);
- characteristic.on('characteristicvaluechanged', supportedCharacteristics[characteristic.uuid].handler);
- }
-
- function writeCache(cache){
+ characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
+ };
+
+ var writeCache = function(cache) {
var oldCache = getCache();
- if (oldCache != cache) {
+ if (oldCache !== cache) {
log("Writing cache");
- require('Storage').writeJSON("bthrm.cache.json", cache)
+ require('Storage').writeJSON("bthrm.cache.json", cache);
} else {
log("No changes, don't write cache");
}
-
- }
+ };
- function characteristicsToCache(characteristics){
+ var characteristicsToCache = function(characteristics) {
log("Cache characteristics");
var cache = getCache();
if (!cache.characteristics) cache.characteristics = {};
@@ -60,9 +59,9 @@
};
}
writeCache(cache);
- }
+ };
- function characteristicsFromCache(){
+ var characteristicsFromCache = function() {
log("Read cached characteristics");
var cache = getCache();
if (!cache.characteristics) return [];
@@ -81,38 +80,34 @@
restored.push(r);
}
return restored;
- }
+ };
log("Start");
var lastReceivedData={
};
- var serviceFilters = [{
- services: [ "180d" ]
- }];
-
- supportedServices = [
- "0x180d", "0x180f"
+ var supportedServices = [
+ "0x180d", // Heart Rate
+ "0x180f", // Battery
];
var supportedCharacteristics = {
"0x2a37": {
//Heart rate measurement
- handler: function (event){
- var dv = event.target.value;
+ handler: function (dv){
var flags = dv.getUint8(0);
-
+
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
-
+
var sensorContact;
-
+
if (flags & 2){
- sensorContact = (flags & 4) ? true : false;
+ sensorContact = !!(flags & 4);
}
-
+
var idx = 2 + (flags&1);
-
+
var energyExpended;
if (flags & 8){
energyExpended = dv.getUint16(idx,1);
@@ -121,11 +116,11 @@
var interval;
if (flags & 16) {
interval = [];
- maxIntervalBytes = (dv.byteLength - idx);
+ var maxIntervalBytes = (dv.byteLength - idx);
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
interval[i] = dv.getUint16(idx,1); // in milliseconds
- idx += 2
+ idx += 2;
}
}
@@ -140,45 +135,44 @@
}
if (settings.replace){
- var newEvent = {
+ var repEvent = {
bpm: bpm,
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
src: "bthrm"
};
-
- log("Emitting HRM: ", newEvent);
- Bangle.emit("HRM", newEvent);
+
+ log("Emitting HRM: ", repEvent);
+ Bangle.emit("HRM", repEvent);
}
var newEvent = {
bpm: bpm
};
-
+
if (location) newEvent.location = location;
if (interval) newEvent.rr = interval;
if (energyExpended) newEvent.energy = energyExpended;
if (battery) newEvent.battery = battery;
if (sensorContact) newEvent.contact = sensorContact;
-
+
log("Emitting BTHRM: ", newEvent);
Bangle.emit("BTHRM", newEvent);
}
},
"0x2a38": {
//Body sensor location
- handler: function(data){
+ handler: function(dv){
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
- if (!lastReceivedData["0x180d"]["0x2a38"]) lastReceivedData["0x180d"]["0x2a38"] = data.target.value;
+ lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
}
},
"0x2a19": {
//Battery
- handler: function (event){
+ handler: function (dv){
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
- if (!lastReceivedData["0x180f"]["0x2a19"]) lastReceivedData["0x180f"]["0x2a19"] = event.target.value.getUint8(0);
+ lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
}
}
-
};
var device;
@@ -195,7 +189,7 @@
maxInterval: 1500
};
- function waitingPromise(timeout) {
+ var waitingPromise = function(timeout) {
return new Promise(function(resolve){
log("Start waiting for " + timeout);
setTimeout(()=>{
@@ -203,7 +197,7 @@
resolve();
}, timeout);
});
- }
+ };
if (settings.enabled){
Bangle.isBTHRMOn = function(){
@@ -215,7 +209,6 @@
};
}
-
if (settings.replace){
var origIsHRMOn = Bangle.isHRMOn;
@@ -229,15 +222,15 @@
};
}
- function clearRetryTimeout(){
+ var clearRetryTimeout = function() {
if (currentRetryTimeout){
log("Clearing timeout " + currentRetryTimeout);
clearTimeout(currentRetryTimeout);
currentRetryTimeout = undefined;
}
- }
+ };
- function retry(){
+ var retry = function() {
log("Retry");
if (!currentRetryTimeout){
@@ -252,17 +245,17 @@
initBt();
}, clampedTime);
- retryTime = Math.pow(retryTime, 1.1);
+ retryTime = Math.pow(clampedTime, 1.1);
if (retryTime > maxRetryTime){
retryTime = maxRetryTime;
}
} else {
log("Already in retry...");
}
- }
+ };
var buzzing = false;
- function onDisconnect(reason) {
+ var onDisconnect = function(reason) {
log("Disconnect: " + reason);
log("GATT: ", gatt);
log("Characteristics: ", characteristics);
@@ -277,11 +270,23 @@
if (Bangle.isBTHRMOn()){
retry();
}
- }
+ };
- function createCharacteristicPromise(newCharacteristic){
+ var createCharacteristicPromise = function(newCharacteristic) {
log("Create characteristic promise: ", newCharacteristic);
var result = Promise.resolve();
+ // For values that can be read, go ahead and read them, even if we might be notified in the future
+ // Allows for getting initial state of infrequently updating characteristics, like battery
+ if (newCharacteristic.readValue){
+ result = result.then(()=>{
+ log("Reading data for " + JSON.stringify(newCharacteristic));
+ return newCharacteristic.readValue().then((data)=>{
+ if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
+ supportedCharacteristics[newCharacteristic.uuid].handler(data);
+ }
+ });
+ });
+ }
if (newCharacteristic.properties.notify){
result = result.then(()=>{
log("Starting notifications for: ", newCharacteristic);
@@ -290,31 +295,23 @@
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
startPromise = startPromise.then(()=>{
log("Wait after connect");
- waitingPromise(settings.gracePeriodNotification)
+ return waitingPromise(settings.gracePeriodNotification);
});
}
return startPromise;
});
- } else if (newCharacteristic.read){
- result = result.then(()=>{
- readData(newCharacteristic);
- log("Reading data for " + newCharacteristic);
- return newCharacteristic.read().then((data)=>{
- supportedCharacteristics[newCharacteristic.uuid].handler(data);
- });
- });
}
return result.then(()=>log("Handled characteristic: ", newCharacteristic));
- }
-
- function attachCharacteristicPromise(promise, characteristic){
+ };
+
+ var attachCharacteristicPromise = function(promise, characteristic) {
return promise.then(()=>{
log("Handling characteristic:", characteristic);
return createCharacteristicPromise(characteristic);
});
- }
-
- function createCharacteristicsPromise(newCharacteristics){
+ };
+
+ var createCharacteristicsPromise = function(newCharacteristics) {
log("Create characteristics promise: ", newCharacteristics);
var result = Promise.resolve();
for (var c of newCharacteristics){
@@ -324,13 +321,13 @@
if (c.properties.notify){
addNotificationHandler(c);
}
-
+
result = attachCharacteristicPromise(result, c);
}
return result.then(()=>log("Handled characteristics"));
- }
-
- function createServicePromise(service){
+ };
+
+ var createServicePromise = function(service) {
log("Create service promise: ", service);
var result = Promise.resolve();
result = result.then(()=>{
@@ -338,15 +335,13 @@
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
});
return result.then(()=>log("Handled service" + service.uuid));
- }
-
- function attachServicePromise(promise, service){
- return promise.then(()=>createServicePromise(service));
- }
-
- var reUseCounter = 0;
+ };
- function initBt() {
+ var attachServicePromise = function(promise, service) {
+ return promise.then(()=>createServicePromise(service));
+ };
+
+ var initBt = function () {
log("initBt with blockInit: " + blockInit);
if (blockInit){
retry();
@@ -355,63 +350,58 @@
blockInit = true;
- if (reUseCounter > 10){
- log("Reuse counter to high");
- gatt=undefined;
- reUseCounter = 0;
- }
-
var promise;
-
+ var filters;
+
if (!device){
- var filters = serviceFilters;
- if (settings.btname){
- log("Configured device name", settings.btname);
- filters = [{name: settings.btname}];
+ if (settings.btid){
+ log("Configured device id", settings.btid);
+ filters = [{ id: settings.btid }];
+ } else {
+ return;
}
log("Requesting device with filters", filters);
- promise = NRF.requestDevice({ filters: filters });
-
+ promise = NRF.requestDevice({ filters: filters, active: true });
+
if (settings.gracePeriodRequest){
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
}
-
+
promise = promise.then((d)=>{
log("Got device: ", d);
d.on('gattserverdisconnected', onDisconnect);
device = d;
});
-
+
promise = promise.then(()=>{
log("Wait after request");
return waitingPromise(settings.gracePeriodRequest);
});
-
} else {
promise = Promise.resolve();
log("Reuse device: ", device);
}
-
+
promise = promise.then(()=>{
if (gatt){
log("Reuse GATT: ", gatt);
} else {
log("GATT is new: ", gatt);
characteristics = [];
- var cachedName = getCache().name;
- if (device.name != cachedName){
- log("Device name changed from " + cachedName + " to " + device.name + ", clearing cache");
+ var cachedId = getCache().id;
+ if (device.id !== cachedId){
+ log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
clearCache();
}
var newCache = getCache();
- newCache.name = device.name;
+ newCache.id = device.id;
writeCache(newCache);
gatt = device.gatt;
}
-
+
return Promise.resolve(gatt);
});
-
+
promise = promise.then((gatt)=>{
if (!gatt.connected){
var connectPromise = gatt.connect(connectSettings);
@@ -427,16 +417,28 @@
return Promise.resolve();
}
});
-
+
+/* promise = promise.then(() => {
+ log(JSON.stringify(gatt.getSecurityStatus()));
+ if (gatt.getSecurityStatus()['bonded']) {
+ log("Already bonded");
+ return Promise.resolve();
+ } else {
+ log("Start bonding");
+ return gatt.startBonding()
+ .then(() => console.log(gatt.getSecurityStatus()));
+ }
+ });*/
+
promise = promise.then(()=>{
- if (!characteristics || characteristics.length == 0){
+ if (!characteristics || characteristics.length === 0){
characteristics = characteristicsFromCache();
}
});
promise = promise.then(()=>{
var characteristicsPromise = Promise.resolve();
- if (characteristics.length == 0){
+ if (characteristics.length === 0){
characteristicsPromise = characteristicsPromise.then(()=>{
log("Getting services");
return gatt.getPrimaryServices();
@@ -454,24 +456,22 @@
log("Add " + settings.gracePeriodService + "ms grace period after services");
result = result.then(()=>{
log("Wait after services");
- return waitingPromise(settings.gracePeriodService)
+ return waitingPromise(settings.gracePeriodService);
});
}
return result;
});
-
} else {
for (var characteristic of characteristics){
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
}
}
-
+
return characteristicsPromise;
});
-
- promise = promise.then(()=>{
+
+ return promise.then(()=>{
log("Connection established, waiting for notifications");
- reUseCounter = 0;
characteristicsToCache(characteristics);
clearRetryTimeout();
}).catch((e) => {
@@ -479,7 +479,7 @@
log("Error:", e);
onDisconnect(e);
});
- }
+ };
Bangle.setBTHRMPower = function(isOn, app) {
// Do app power handling
@@ -487,7 +487,7 @@
if (Bangle._PWR===undefined) Bangle._PWR={};
if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[];
if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app);
- if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app);
+ if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!==app);
isOn = Bangle._PWR.BTHRM.length;
// so now we know if we're really on
if (isOn) {
@@ -510,7 +510,7 @@
}
}
};
-
+
var origSetHRMPower = Bangle.setHRMPower;
if (settings.startWithHrm){
@@ -525,11 +525,10 @@
}
};
}
-
-
+
var fallbackInterval;
-
- function switchInternalHrm(){
+
+ var switchInternalHrm = function() {
if (settings.allowFallback && !fallbackInterval){
log("Fallback to HRM enabled");
origSetHRMPower(1, "bthrm_fallback");
@@ -542,7 +541,7 @@
}
}, settings.fallbackTimeout);
}
- }
+ };
if (settings.replace){
log("Replace HRM event");
@@ -557,11 +556,11 @@
}
switchInternalHrm();
}
-
+
E.on("kill", ()=>{
if (gatt && gatt.connected){
log("Got killed, trying to disconnect");
- var promise = gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
+ gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
}
});
}
diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js
index cc533eedd..dd9230386 100644
--- a/apps/bthrm/bthrm.js
+++ b/apps/bthrm/bthrm.js
@@ -1,7 +1,16 @@
-var btm = g.getHeight()-1;
var intervalInt;
var intervalBt;
+var BODY_LOCS = {
+ 0: 'Other',
+ 1: 'Chest',
+ 2: 'Wrist',
+ 3: 'Finger',
+ 4: 'Hand',
+ 5: 'Ear Lobe',
+ 6: 'Foot',
+}
+
function clear(y){
g.reset();
g.clearRect(0,y,g.getWidth(),y+75);
@@ -15,17 +24,17 @@ function draw(y, type, event) {
g.setFontAlign(0,0);
g.setFontVector(40).drawString(str,px,y+20);
str = "Event: " + type;
- if (type == "HRM") {
+ if (type === "HRM") {
str += " Confidence: " + event.confidence;
g.setFontVector(12).drawString(str,px,y+40);
str = " Source: " + (event.src ? event.src : "internal");
g.setFontVector(12).drawString(str,px,y+50);
}
- if (type == "BTHRM"){
+ if (type === "BTHRM"){
if (event.battery) str += " Bat: " + (event.battery ? event.battery : "");
g.setFontVector(12).drawString(str,px,y+40);
str= "";
- if (event.location) str += "Loc: " + event.location.toFixed(0) + "ms";
+ if (event.location) str += "Loc: " + BODY_LOCS[event.location];
if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(",");
g.setFontVector(12).drawString(str,px,y+50);
str= "";
@@ -45,7 +54,7 @@ function onBtHrm(e) {
firstEventBt = false;
}
draw(100, "BTHRM", e);
- if (e.bpm == 0){
+ if (e.bpm === 0){
Bangle.buzz(100,0.2);
}
if (intervalBt){
diff --git a/apps/bthrm/default.json b/apps/bthrm/default.json
index 64e638b8a..fb284bcd2 100644
--- a/apps/bthrm/default.json
+++ b/apps/bthrm/default.json
@@ -7,10 +7,10 @@
"allowFallback": true,
"warnDisconnect": false,
"fallbackTimeout": 10,
- "custom_replace": false,
+ "custom_replace": true,
"custom_debuglog": false,
- "custom_startWithHrm": false,
- "custom_allowFallback": false,
+ "custom_startWithHrm": true,
+ "custom_allowFallback": true,
"custom_warnDisconnect": false,
"custom_fallbackTimeout": 10,
"gracePeriodNotification": 0,
diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json
index b35ebd6af..39c1ff8bb 100644
--- a/apps/bthrm/metadata.json
+++ b/apps/bthrm/metadata.json
@@ -2,11 +2,11 @@
"id": "bthrm",
"name": "Bluetooth Heart Rate Monitor",
"shortName": "BT HRM",
- "version": "0.08",
+ "version": "0.09",
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
"icon": "app.png",
"type": "app",
- "tags": "health,bluetooth",
+ "tags": "health,bluetooth,hrm,bthrm",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js
index 4b564d670..b376d6a2d 100644
--- a/apps/bthrm/settings.js
+++ b/apps/bthrm/settings.js
@@ -5,14 +5,14 @@
require('Storage').writeJSON(FILE, s);
readSettings();
}
-
+
function readSettings(){
settings = Object.assign(
require('Storage').readJSON("bthrm.default.json", true) || {},
require('Storage').readJSON(FILE, true) || {}
);
}
-
+
var FILE="bthrm.json";
var settings;
readSettings();
@@ -61,12 +61,13 @@
}
};
- if (settings.btname){
- var name = "Clear " + settings.btname;
+ if (settings.btname || settings.btid){
+ var name = "Clear " + (settings.btname || settings.btid);
mainmenu[name] = function() {
- E.showPrompt("Clear current device name?").then((r)=>{
+ E.showPrompt("Clear current device?").then((r)=>{
if (r) {
writeSettings("btname",undefined);
+ writeSettings("btid",undefined);
}
E.showMenu(buildMainMenu());
});
@@ -78,9 +79,7 @@
mainmenu.Debug = function() { E.showMenu(submenu_debug); };
return mainmenu;
}
-
-
var submenu_debug = {
'' : { title: "Debug"},
'< Back': function() { E.showMenu(buildMainMenu()); },
@@ -103,35 +102,39 @@
function createMenuFromScan(){
E.showMenu();
- E.showMessage("Scanning");
+ E.showMessage("Scanning for 4 seconds");
var submenu_scan = {
- '' : { title: "Scan"},
'< Back': function() { E.showMenu(buildMainMenu()); }
};
- var packets=10;
- var scanStart=Date.now();
- NRF.setScan(function(d) {
- packets--;
- if (packets<=0 || Date.now() - scanStart > 5000){
- NRF.setScan();
- E.showMenu(submenu_scan);
- } else if (d.name){
- print("Found device", d);
- submenu_scan[d.name] = function(){
- E.showPrompt("Set "+d.name+"?").then((r)=>{
- if (r) {
- writeSettings("btname",d.name);
- }
- E.showMenu(buildMainMenu());
+ NRF.findDevices(function(devices) {
+ submenu_scan[''] = { title: `Scan (${devices.length} found)`};
+ if (devices.length === 0) {
+ E.showAlert("No devices found")
+ .then(() => E.showMenu(buildMainMenu()));
+ return;
+ } else {
+ devices.forEach((d) => {
+ print("Found device", d);
+ var shown = (d.name || d.id.substr(0, 17));
+ submenu_scan[shown] = function () {
+ E.showPrompt("Set " + shown + "?").then((r) => {
+ if (r) {
+ writeSettings("btid", d.id);
+ // Store the name for displaying later. Will connect by ID
+ if (d.name) {
+ writeSettings("btname", d.name);
+ }
+ }
+ E.showMenu(buildMainMenu());
+ });
+ };
});
- };
}
- }, { filters: [{services: [ "180d" ]}]});
+ E.showMenu(submenu_scan);
+ }, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
}
-
-
var submenu_custom = {
'' : { title: "Custom mode"},
'< Back': function() { E.showMenu(buildMainMenu()); },
@@ -167,7 +170,7 @@
}
},
};
-
+
var submenu_grace = {
'' : { title: "Grace periods"},
'< Back': function() { E.showMenu(submenu_debug); },
@@ -212,51 +215,6 @@
}
}
};
-
- var submenu = {
- '' : { title: "Grace periods"},
- '< Back': function() { E.showMenu(buildMainMenu()); },
- 'Request': {
- value: settings.gracePeriodRequest,
- min: 0,
- max: 3000,
- step: 100,
- format: v=>v+"ms",
- onchange: v => {
- writeSettings("gracePeriodRequest",v);
- }
- },
- 'Connect': {
- value: settings.gracePeriodConnect,
- min: 0,
- max: 3000,
- step: 100,
- format: v=>v+"ms",
- onchange: v => {
- writeSettings("gracePeriodConnect",v);
- }
- },
- 'Notification': {
- value: settings.gracePeriodNotification,
- min: 0,
- max: 3000,
- step: 100,
- format: v=>v+"ms",
- onchange: v => {
- writeSettings("gracePeriodNotification",v);
- }
- },
- 'Service': {
- value: settings.gracePeriodService,
- min: 0,
- max: 3000,
- step: 100,
- format: v=>v+"ms",
- onchange: v => {
- writeSettings("gracePeriodService",v);
- }
- }
- };
-
+
E.showMenu(buildMainMenu());
-})
+});
diff --git a/apps/bwclk/ChangeLog b/apps/bwclk/ChangeLog
index 11569af0c..ecd0c355f 100644
--- a/apps/bwclk/ChangeLog
+++ b/apps/bwclk/ChangeLog
@@ -3,4 +3,7 @@
0.03: Adapt colors based on the theme of the user.
0.04: Steps can be hidden now such that the time is even larger.
0.05: Included icons for information.
-0.06: Design and usability improvements.
\ No newline at end of file
+0.06: Design and usability improvements.
+0.07: Improved positioning.
+0.08: Select the color of widgets correctly. Additional settings to hide colon.
+0.09: Larger font size if colon is hidden to improve readability further.
\ No newline at end of file
diff --git a/apps/bwclk/README.md b/apps/bwclk/README.md
index f282bd187..f6a1c6522 100644
--- a/apps/bwclk/README.md
+++ b/apps/bwclk/README.md
@@ -8,6 +8,7 @@
- Enable / disable lock icon in the settings.
- If the "sched" app is installed tab top / bottom of the screen to set the timer.
- The design is adapted to the theme of your bangle.
+- The colon (e.g. 7:35 = 735) can be hidden now in the settings.
## Thanks to
Icons created by Flaticon
diff --git a/apps/bwclk/app.js b/apps/bwclk/app.js
index 5240e69ec..5bfec4097 100644
--- a/apps/bwclk/app.js
+++ b/apps/bwclk/app.js
@@ -18,6 +18,7 @@ const H = g.getHeight();
let settings = {
fullscreen: false,
showLock: true,
+ hideColon: false,
showInfo: 0,
};
@@ -33,11 +34,25 @@ for (const key in saved_settings) {
// Manrope font
Graphics.prototype.setLargeFont = function(scale) {
- // Actual height 49 (50 - 2)
- this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAAAfwAAAAAAAAf/AAAAAAAAf/8AAAAAAAf//wAAAAAAP///AAAAAAP///8AAAAAP////wAAAAP////4AAAAP////8AAAAH////8AAAAH////8AAAAB////8AAAAAH///+AAAAAAf//+AAAAAAB//+AAAAAAAH/+AAAAAAAAf+AAAAAAAAB/AAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///gAAAAAD////4AAAAA/////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAA///////4AAD/4AAAH/wAAP+AAAAP/AAB/wAAAAf8AAH/AAAAA/4AAf4AAAAB/gAB/gAAAAH+AAP8AAAAAf4AA/wAAAAB/gAD/AAAAAH+AAP8AAAAAf4AAf4AAAAB/gAB/gAAAAH+AAH+AAAAA/4AAf8AAAAH/AAB/4AAAA/8AAD/4AAAH/wAAP/8AAH/+AAAf//////4AAA///////AAAB//////4AAAD//////AAAAH/////4AAAAP////+AAAAAP////gAAAAAD///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAAB/wAAAAAAAAH/AAAAAAAAA/4AAAAAAAAH/gAAAAAAAAf8AAAAAAAAD/gAAAAAAAAP+AAAAAAAAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAf///////AAB///////8AAH///////wAAP///////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAfwAAAH8AAAD/AAAB/wAAAf8AAAP/AAAD/wAAB/8AAAf/AAAP/wAAD/8AAB//AAAf/wAAH/8AAD//AAA//gAAf/8AAD/wAAB//wAAf+AAAP//AAB/wAAB//8AAH+AAAP//wAAf4AAB///AAD/AAAP/v8AAP8AAB/8/wAA/wAAP/j/AAD/AAB/8P8AAH+AAH/g/wAAf4AA/8D/AAB/wAH/gP8AAH/AA/+A/wAAf/AP/wD/AAA//D/+AP8AAD////wA/wAAH///+AD/AAAP///wAP8AAAf//+AA/wAAA///wAD/AAAB//+AAP8AAAB//gAA/wAAAB/4AAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAD+AAAAHwAAAf4AAAAfwAAB/gAAAB/gAAH+AAAAP/AAAf4AAAA/8AAB/gAAAD/4AAH+ADAAH/wAAf4AeAAP/AAB/gD+AAP8AAH+Af+AA/4AAf4D/4AB/gAB/gP/AAH+AAH+B/8AAf4AAf4P/wAB/gAB/h//AAH+AAH+P/8AAf4AAf5//wAB/gAB/v//gAP+AAH+//+AA/4AAf//f8AH/AAB//5/8B/8AAH//D////gAAf/4P///+AAB//Af///wAAH/4A///+AAAf/AB///wAAB/4AD//+AAAH/AAH//gAAAP4AAD/4AAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAA/+AAAAAAAAP/4AAAAAAAH//gAAAAAAB//+AAAAAAAf//4AAAAAAH///gAAAAAB///+AAAAAAf///4AAAAAH//9/gAAAAD///H+AAAAA///wf4AAAAP//8B/gAAAD///AH+AAAA///wAf4AAAH//8AB/gAAAf//AAH+AAAB//gAAf4AAAH/4AAB/gAAAf+AAAH+AAAB/gAf///8AAH4AB////wAAeAAH////AABgAAf///8AAAAAB////wAAAAAH////AAAAAAf///8AAAAAB////wAAAAAH////AAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAeAAAAAf//AB+AAAH///+AP8AAAf///4A/4AAB////gD/wAAH////Af/gAAf///8B/+AAB////wB/8AAH///+AB/wAAf4Af4AD/gAB/gB/AAP+AAH+AP8AAf4AAf4A/wAB/gAB/gD+AAH+AAH+AP4AAf4AAf4A/gAB/gAB/gD/AAH+AAH+AP8AAf4AAf4A/wAD/gAB/gD/gAf8AAH+AH/AD/wAAf4Af/Af+AAB/gB////4AAH+AD////AAAf4AH///8AAB/gAP///gAAH+AA///8AAAAAAA///AAAAAAAB//4AAAAAAAB/+AAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///gAAAAAB////4AAAAAf////4AAAAH/////4AAAA//////wAAAH//////gAAA///////AAAH//////+AAAf//////4AAD/4D/wH/wAAP+AP8AP/AAB/wB/gAf8AAH/AH8AA/4AAf4A/wAB/gAB/gD/AAH+AAH8AP4AAf4AA/wA/gAB/gAD/AD+AAH+AAH8AP8AAf4AAf4A/wAB/gAB/gD/AAP+AAH+AP+AB/wAAf8Af8AP/AAA/4B/8B/8AAD/gH////gAAP8AP///8AAAfgAf///wAAA8AB///+AAADgAD///wAAAAAAD//+AAAAAAAH//gAAAAAAAH/4AAAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAAAH+AAAAAAAAAf4AAAAAAAAB/gAAAAAAAAH+AAAAAAAAAf4AAAAADgAB/gAAAAA+AAH+AAAAAf4AAf4AAAAH/gAB/gAAAD/+AAH+AAAA//4AAf4AAAf//gAB/gAAH//+AAH+AAD///wAAf4AA///8AAB/gAf//+AAAH+AH///gAAAf4D///wAAAB/g///8AAAAH+f//+AAAAAf////gAAAAB////wAAAAAH///8AAAAAAf//+AAAAAAB///gAAAAAAH//wAAAAAAAf/8AAAAAAAB/+AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAAB/wB//wAAAAf/wf//gAAAD//z///AAAAf/////+AAAD//////8AAAf//////4AAD///////gAAP////w//AAB/+f/8Af8AAH/Af/gA/4AAf4A/8AD/gAB/gB/wAH+AAP8AH+AAf4AA/wAf4AB/gAD/AB/gAH+AAP8AH+AAf4AA/wAf4AB/gAB/gB/wAH+AAH+AP/AAf4AAf8A/+AD/gAB/8f/8Af8AAD////4H/wAAP//////+AAAf//////4AAA///////AAAD//////8AAAH//z///gAAAH/+H//4AAAAH/gH//AAAAAAAAH/wAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAAAAAP/wAAAAAAAD//wAAAAAAAf//wAAAAAAH///gABgAAA////AAPAAAD///+AB+AAAf///4AP4AAD////wB/wAAP/AP/AH/AAB/4Af+AP+AAH/AA/4A/4AAf4AB/gB/gAB/gAH+AH+AAP8AAP4Af4AA/wAA/gB/gAD/AAD+AH+AAP8AAP4Af4AA/4AB/gB/gAB/gAH+AH+AAH+AAfwA/4AAf8AD/AH/AAB/4Af4A/8AAD/4H/gP/wAAP//////+AAAf//////wAAA///////AAAB//////4AAAD//////AAAAH/////wAAAAH////+AAAAAH////AAAAAAAf/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAH+AD/AAAAAAf4AP8AAAAAB/gA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("ExwqHCYlJyYoIicoFg=="), 64+(scale<<8)+(1<<16));
+ // Actual height 48 (49 - 2)
+ this.setFontCustom(
+ E.toString(require('heatshrink').decompress(atob('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'))),
+ 46,
+ atob("EhooGyUkJiUnISYnFQ=="),
+ 63+(scale<<8)+(1<<16)
+ );
return this;
};
+Graphics.prototype.setXLargeFont = function(scale) {
+ // Actual height 53 (55 - 3)
+ this.setFontCustom(
+ E.toString(require('heatshrink').decompress(atob('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'))),
+ 46,
+ atob("FR4uHyopKyksJSssGA=="),
+ 70+(scale<<8)+(1<<16)
+ );
+};
Graphics.prototype.setMediumFont = function(scale) {
// Actual height 41 (42 - 2)
@@ -259,11 +274,12 @@ function draw() {
function drawDate(){
// Draw background
- var y = H/5*2 + (settings.fullscreen ? 0 : 8);
+ var y = H/5*2;
g.reset().clearRect(0,0,W,W);
// Draw date
- y -= settings.fullscreen ? 8 : 0;
+ y = parseInt(y/2);
+ y += settings.fullscreen ? 2 : 15;
var date = new Date();
var dateStr = date.getDate();
dateStr = ("0" + dateStr).substr(-2);
@@ -276,14 +292,14 @@ function drawDate(){
var dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr));
var fullDateW = dateW + 10 + dayW;
- g.setFontAlign(-1,1);
+ g.setFontAlign(-1,0);
g.setMediumFont();
g.setColor(g.theme.fg);
- g.drawString(dateStr, W/2 - fullDateW / 2, y+5);
+ g.drawString(dateStr, W/2 - fullDateW / 2, y+1);
g.setSmallFont();
- g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+3);
- g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-23);
+ g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
+ g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
}
@@ -296,9 +312,16 @@ function drawTime(){
// Draw time
g.setColor(g.theme.bg);
- g.setFontAlign(0,-1);
- var timeStr = locale.time(date,1);
- y += settings.fullscreen ? 14 : 10;
+ g.setFontAlign(0,0);
+
+ var hours = String(date.getHours());
+ var minutes = date.getMinutes();
+ minutes = minutes < 10 ? String("0") + minutes : minutes;
+ var colon = settings.hideColon ? "" : ":";
+ var timeStr = hours + colon + minutes;
+
+ // Set y coordinates correctly
+ y += parseInt((H - y)/2) + 5;
var infoEntry = getInfoEntry();
var infoStr = infoEntry[0];
@@ -307,9 +330,13 @@ function drawTime(){
// Show large or small time depending on info entry
if(infoStr == null){
- y += 10;
- g.setLargeFont();
+ if(settings.hideColon){
+ g.setXLargeFont();
+ } else {
+ g.setLargeFont();
+ }
} else {
+ y -= 15;
g.setMediumFont();
}
g.drawString(timeStr, W/2, y);
@@ -319,7 +346,7 @@ function drawTime(){
return;
}
- y += H/5*2-5;
+ y += 35;
g.setFontAlign(0,0);
g.setSmallFont();
var imgWidth = 0;
@@ -370,17 +397,6 @@ function queueDraw() {
}
-/*
- * Load clock, widgets and listen for events
- */
-Bangle.loadWidgets();
-
-// Clear the screen once, at startup and set the correct theme.
-var bgOrig = g.theme.bg
-var fgOrig = g.theme.fg
-g.setTheme({bg:fgOrig,fg:bgOrig}).clear();
-draw();
-
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (on) {
@@ -446,5 +462,17 @@ E.on("kill", function(){
});
+/*
+ * Draw clock the first time
+ */
+// The upper part is inverse i.e. light if dark and dark if light theme
+// is enabled. In order to draw the widgets correctly, we invert the
+// dark/light theme as well as the colors.
+g.setTheme({bg:g.theme.fg,fg:g.theme.bg, dark:!g.theme.dark}).clear();
+
+// Load widgets and draw clock the first time
+Bangle.loadWidgets();
+draw();
+
// Show launcher when middle button pressed
Bangle.setUI("clock");
diff --git a/apps/bwclk/metadata.json b/apps/bwclk/metadata.json
index 8b13cd256..eba1449a6 100644
--- a/apps/bwclk/metadata.json
+++ b/apps/bwclk/metadata.json
@@ -1,7 +1,7 @@
{
"id": "bwclk",
"name": "BW Clock",
- "version": "0.06",
+ "version": "0.09",
"description": "BW Clock.",
"readme": "README.md",
"icon": "app.png",
diff --git a/apps/bwclk/screenshot.png b/apps/bwclk/screenshot.png
index b30ba4166..550913422 100644
Binary files a/apps/bwclk/screenshot.png and b/apps/bwclk/screenshot.png differ
diff --git a/apps/bwclk/screenshot_2.png b/apps/bwclk/screenshot_2.png
index ea2dc780b..ccbc9aae1 100644
Binary files a/apps/bwclk/screenshot_2.png and b/apps/bwclk/screenshot_2.png differ
diff --git a/apps/bwclk/screenshot_3.png b/apps/bwclk/screenshot_3.png
index fb5b153b8..5bf7083f0 100644
Binary files a/apps/bwclk/screenshot_3.png and b/apps/bwclk/screenshot_3.png differ
diff --git a/apps/bwclk/settings.js b/apps/bwclk/settings.js
index 0fdaf1a28..a421e81a9 100644
--- a/apps/bwclk/settings.js
+++ b/apps/bwclk/settings.js
@@ -6,6 +6,7 @@
let settings = {
fullscreen: false,
showLock: true,
+ hideColon: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
@@ -35,6 +36,14 @@
settings.showLock = !settings.showLock;
save();
},
+ },
+ 'Hide Colon': {
+ value: settings.hideColon,
+ format: () => (settings.hideColon ? 'Yes' : 'No'),
+ onchange: () => {
+ settings.hideColon = !settings.hideColon;
+ save();
+ },
}
});
})
diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog
index cc8bb6306..ea8934f84 100644
--- a/apps/calendar/ChangeLog
+++ b/apps/calendar/ChangeLog
@@ -5,3 +5,5 @@
0.05: Update calendar weekend colors for start on Sunday
0.06: Use larger font for dates
0.07: Fix off-by-one-error on previous month
+0.08: Do not register as watch, manually start clock on button
+ read start of week from system settings
diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js
index 3f4315811..fc7e93cf5 100644
--- a/apps/calendar/calendar.js
+++ b/apps/calendar/calendar.js
@@ -18,8 +18,7 @@ const blue = "#0000ff";
const yellow = "#ffff00";
let settings = require('Storage').readJSON("calendar.json", true) || {};
-if (settings.startOnSun === undefined)
- settings.startOnSun = false;
+let startOnSun = ((require("Storage").readJSON("setting.json", true) || {}).firstDayOfWeek || 0) === 0;
if (settings.ndColors === undefined)
if (process.env.HWVERSION == 2) {
settings.ndColors = true;
@@ -50,14 +49,14 @@ function getDowLbls(locale) {
case "de_AT":
case "de_CH":
case "de_DE":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
} else {
dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
}
break;
case "nl_NL":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"];
} else {
dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"];
@@ -66,14 +65,14 @@ function getDowLbls(locale) {
case "fr_BE":
case "fr_CH":
case "fr_FR":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
}
break;
case "sv_SE":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
@@ -81,21 +80,21 @@ function getDowLbls(locale) {
break;
case "it_CH":
case "it_IT":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
}
break;
case "oc_FR":
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"];
} else {
dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"];
}
break;
default:
- if (settings.startOnSun) {
+ if (startOnSun) {
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
} else {
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
@@ -110,7 +109,7 @@ function drawCalendar(date) {
g.clearRect(0, 0, maxX, maxY);
g.setBgColor(bgColorMonth);
g.clearRect(0, 0, maxX, headerH);
- if (settings.startOnSun){
+ if (startOnSun){
g.setBgColor(bgColorWeekend);
g.clearRect(0, headerH + rowH, colW, maxY);
g.setBgColor(bgColorDow);
@@ -150,7 +149,7 @@ function drawCalendar(date) {
});
date.setDate(1);
- const dow = date.getDay() + (settings.startOnSun ? 1 : 0);
+ const dow = date.getDay() + (startOnSun ? 1 : 0);
const dowNorm = dow === 0 ? 7 : dow;
const monthMaxDayMap = {
@@ -242,5 +241,5 @@ Bangle.on("touch", area => {
});
// Show launcher when button pressed
-Bangle.setUI("clock"); // TODO: ideally don't set 'clock' mode
+setWatch(() => load(), process.env.HWVERSION === 2 ? BTN : BTN3, { repeat: false, edge: "falling" });
// No space for widgets!
diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json
index 62d2513ae..5f968b364 100644
--- a/apps/calendar/metadata.json
+++ b/apps/calendar/metadata.json
@@ -1,7 +1,7 @@
{
"id": "calendar",
"name": "Calendar",
- "version": "0.07",
+ "version": "0.08",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
diff --git a/apps/calendar/settings.js b/apps/calendar/settings.js
index 3c8f7d8e8..192d2ece0 100644
--- a/apps/calendar/settings.js
+++ b/apps/calendar/settings.js
@@ -1,8 +1,6 @@
(function (back) {
var FILE = "calendar.json";
var settings = require('Storage').readJSON(FILE, true) || {};
- if (settings.startOnSun === undefined)
- settings.startOnSun = false;
if (settings.ndColors === undefined)
if (process.env.HWVERSION == 2) {
settings.ndColors = true;
@@ -17,14 +15,6 @@
E.showMenu({
"": { "title": "Calendar" },
"< Back": () => back(),
- 'Start Sunday': {
- value: settings.startOnSun,
- format: v => v ? "Yes" : "No",
- onchange: v => {
- settings.startOnSun = v;
- writeSettings();
- }
- },
'B2 Colors': {
value: settings.ndColors,
format: v => v ? "Yes" : "No",
diff --git a/apps/calibration/README.md b/apps/calibration/README.md
new file mode 100644
index 000000000..37f637d21
--- /dev/null
+++ b/apps/calibration/README.md
@@ -0,0 +1,11 @@
+# Banglejs - Touchscreen calibration
+A simple calibration app for the touchscreen
+
+## Usage
+
+Once lauched touch the cross that appear on the screen to make
+another spawn elsewhere.
+
+each new touch on the screen will help to calibrate the offset
+of your finger on the screen. After five or more input, press
+the button to save the calibration and close the application.
\ No newline at end of file
diff --git a/apps/calibration/app-icon.js b/apps/calibration/app-icon.js
new file mode 100644
index 000000000..af66c3f68
--- /dev/null
+++ b/apps/calibration/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwkB/4AJ+EPBhQXg+BBDCyJaGGR5zIDBoQEL4QYOLYR3GBIouJR5AYBGBILBU5QMGFwgiFX4wwIEI4XGGBAgHd44+HD44XHNw4XWM5IIHCIoXWV5IXICQgXvLxAAKCYYXh5nMC6n8C4PPC5MAAA8PC4ZxBACAXOI653hU5zvJABASEC5PwHI4XcMBIXICIoXXJBAXHCAwXXJBAXHB5AfGC4ygJEAwXGQ5BoIQxoiDBYgXECwIuIBgb5ECIQJFGBQmCC4QHEDBwAFCxoYICx5ZELZoZJFiIXpA="))
\ No newline at end of file
diff --git a/apps/calibration/app.js b/apps/calibration/app.js
new file mode 100644
index 000000000..d3823de63
--- /dev/null
+++ b/apps/calibration/app.js
@@ -0,0 +1,85 @@
+class BanglejsApp {
+ constructor() {
+ this.x = 0;
+ this.y = 0;
+ this.settings = {
+ xoffset: 0,
+ yoffset: 0,
+ };
+ }
+
+ load_settings() {
+ let settings = require('Storage').readJSON('calibration.json', true) || {active: false};
+
+ // do nothing if the calibration is deactivated
+ if (settings.active === true) {
+ // cancel the calibration offset
+ Bangle.on('touch', function(button, xy) {
+ xy.x += settings.xoffset;
+ xy.y += settings.yoffset;
+ });
+ }
+ if (!settings.xoffset) settings.xoffset = 0;
+ if (!settings.yoffset) settings.yoffset = 0;
+
+ console.log('loaded settings:');
+ console.log(settings);
+
+ return settings;
+ }
+
+ save_settings() {
+ this.settings.active = true;
+ this.settings.reload = false;
+ require('Storage').writeJSON('calibration.json', this.settings);
+
+ console.log('saved settings:');
+ console.log(this.settings);
+ }
+
+ explain() {
+ /*
+ * TODO:
+ * Present how to use the application
+ *
+ */
+ }
+
+ drawTarget() {
+ this.x = 16 + Math.floor(Math.random() * (g.getWidth() - 32));
+ this.y = 40 + Math.floor(Math.random() * (g.getHeight() - 80));
+
+ g.clearRect(0, 24, g.getWidth(), g.getHeight() - 24);
+ g.drawLine(this.x, this.y - 5, this.x, this.y + 5);
+ g.drawLine(this.x - 5, this.y, this.x + 5, this.y);
+ g.setFont('Vector', 10);
+ g.drawString('current offset: ' + this.settings.xoffset + ', ' + this.settings.yoffset, 0, 24);
+ }
+
+ setOffset(xy) {
+ this.settings.xoffset = Math.round((this.settings.xoffset + (this.x - Math.floor((this.x + xy.x)/2)))/2);
+ this.settings.yoffset = Math.round((this.settings.yoffset + (this.y - Math.floor((this.y + xy.y)/2)))/2);
+ }
+}
+
+
+E.srand(Date.now());
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+
+calibration = new BanglejsApp();
+calibration.load_settings();
+
+let modes = {
+ mode : 'custom',
+ btn : function(n) {
+ calibration.save_settings(this.settings);
+ load();
+ },
+ touch : function(btn, xy) {
+ calibration.setOffset(xy);
+ calibration.drawTarget();
+ },
+};
+Bangle.setUI(modes);
+calibration.drawTarget();
diff --git a/apps/calibration/boot.js b/apps/calibration/boot.js
new file mode 100644
index 000000000..237fb2e0d
--- /dev/null
+++ b/apps/calibration/boot.js
@@ -0,0 +1,14 @@
+let cal_settings = require('Storage').readJSON("calibration.json", true) || {active: false};
+Bangle.on('touch', function(button, xy) {
+ // do nothing if the calibration is deactivated
+ if (cal_settings.active === false) return;
+
+ // reload the calibration offset at each touch event /!\ bad for the flash memory
+ if (cal_settings.reload === true) {
+ cal_settings = require('Storage').readJSON("calibration.json", true);
+ }
+
+ // apply the calibration offset
+ xy.x += cal_settings.xoffset;
+ xy.y += cal_settings.yoffset;
+});
diff --git a/apps/calibration/calibration.png b/apps/calibration/calibration.png
new file mode 100644
index 000000000..3fb44beee
Binary files /dev/null and b/apps/calibration/calibration.png differ
diff --git a/apps/calibration/metadata.json b/apps/calibration/metadata.json
new file mode 100644
index 000000000..122a2c175
--- /dev/null
+++ b/apps/calibration/metadata.json
@@ -0,0 +1,17 @@
+{ "id": "calibration",
+ "name": "Touchscreen Calibration",
+ "shortName":"Calibration",
+ "icon": "calibration.png",
+ "version":"1.00",
+ "description": "A simple calibration app for the touchscreen",
+ "supports": ["BANGLEJS","BANGLEJS2"],
+ "readme": "README.md",
+ "tags": "tool",
+ "storage": [
+ {"name":"calibration.app.js","url":"app.js"},
+ {"name":"calibration.boot.js","url":"boot.js"},
+ {"name":"calibration.settings.js","url":"settings.js"},
+ {"name":"calibration.img","url":"app-icon.js","evaluate":true}
+ ],
+ "data": [{"name":"calibration.json"}]
+}
diff --git a/apps/calibration/settings.js b/apps/calibration/settings.js
new file mode 100644
index 000000000..6db8dd3bb
--- /dev/null
+++ b/apps/calibration/settings.js
@@ -0,0 +1,23 @@
+(function(back) {
+ var FILE = "calibration.json";
+ var settings = Object.assign({
+ active: true,
+ }, require('Storage').readJSON(FILE, true) || {});
+
+ function writeSettings() {
+ require('Storage').writeJSON(FILE, settings);
+ }
+
+ E.showMenu({
+ "" : { "title" : "Calibration" },
+ "< Back" : () => back(),
+ 'Active': {
+ value: !!settings.active,
+ format: v => v? "On":"Off",
+ onchange: v => {
+ settings.active = v;
+ writeSettings();
+ }
+ },
+ });
+})
\ No newline at end of file
diff --git a/apps/circlesclock/ChangeLog b/apps/circlesclock/ChangeLog
index 46eddd32b..c3e7918e7 100644
--- a/apps/circlesclock/ChangeLog
+++ b/apps/circlesclock/ChangeLog
@@ -23,3 +23,4 @@
0.11: New color option: foreground color
Improve performance, reduce memory usage
Small optical adjustments
+0.12: Allow configuration of update interval
diff --git a/apps/circlesclock/app.js b/apps/circlesclock/app.js
index a6aa1a8b1..48e3a1a1a 100644
--- a/apps/circlesclock/app.js
+++ b/apps/circlesclock/app.js
@@ -848,8 +848,8 @@ Bangle.loadWidgets();
// schedule a draw for the next minute
setTimeout(function() {
- // draw every 60 seconds
- setInterval(draw,60000);
+ // draw in interval
+ setInterval(draw, settings.updateInterval * 1000);
}, 60000 - (Date.now() % 60000));
draw();
diff --git a/apps/circlesclock/default.json b/apps/circlesclock/default.json
index cb6bfcff8..ea00dc347 100644
--- a/apps/circlesclock/default.json
+++ b/apps/circlesclock/default.json
@@ -21,5 +21,6 @@
"circle2colorizeIcon": true,
"circle3colorizeIcon": true,
"circle4colorizeIcon": false,
- "hrmValidity": 60
+ "hrmValidity": 60,
+ "updateInterval": 60
}
diff --git a/apps/circlesclock/metadata.json b/apps/circlesclock/metadata.json
index b16f14c06..c35d99334 100644
--- a/apps/circlesclock/metadata.json
+++ b/apps/circlesclock/metadata.json
@@ -1,7 +1,7 @@
{ "id": "circlesclock",
"name": "Circles clock",
"shortName":"Circles clock",
- "version":"0.11",
+ "version":"0.12",
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
"icon": "app.png",
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
diff --git a/apps/circlesclock/settings.js b/apps/circlesclock/settings.js
index 0b9e94aca..fb23f8d5e 100644
--- a/apps/circlesclock/settings.js
+++ b/apps/circlesclock/settings.js
@@ -58,6 +58,16 @@
min: 0, max: 2,
format: v => weatherData[v],
onchange: x => save('weatherCircleData', weatherData[x]),
+ },
+ /*LANG*/'update interval': {
+ value: settings.updateInterval,
+ min: 0,
+ max : 3600,
+ step: 30,
+ format: x => {
+ return x + 's';
+ },
+ onchange: x => save('updateInterval', x),
}
};
E.showMenu(menu);
@@ -100,7 +110,7 @@
/*LANG*/'valid period': {
value: settings.hrmValidity,
min: 10,
- max : 600,
+ max : 1800,
step: 10,
format: x => {
return x + "s";
@@ -117,9 +127,9 @@
/*LANG*/'< Back': ()=>showMainMenu(),
/*LANG*/'goal': {
value: settings.stepGoal,
- min: 2000,
+ min: 1000,
max : 50000,
- step: 2000,
+ step: 500,
format: x => {
return x;
},
@@ -127,9 +137,9 @@
},
/*LANG*/'distance goal': {
value: settings.stepDistanceGoal,
- min: 2000,
- max : 30000,
- step: 1000,
+ min: 1000,
+ max : 50000,
+ step: 500,
format: x => {
return x;
},
diff --git a/apps/diceroll/ChangeLog b/apps/diceroll/ChangeLog
new file mode 100644
index 000000000..89dff4011
--- /dev/null
+++ b/apps/diceroll/ChangeLog
@@ -0,0 +1 @@
+0.01: App created
\ No newline at end of file
diff --git a/apps/diceroll/app-icon.js b/apps/diceroll/app-icon.js
new file mode 100644
index 000000000..4d6e7da16
--- /dev/null
+++ b/apps/diceroll/app-icon.js
@@ -0,0 +1 @@
+E.toArrayBuffer(atob("ICABAAAAAAAAAAAAAAAAAAHAAAAP8AAAfn4AA/APwA+DwfAPg8HwD+AH8Az4HzAMPnwwDAfgMAwBgDAMCYAwDA2YMAwhmDAMIZAwDCGDMA2BgzAMgYAwDAGAMA8BgPADwYPAAPGPgAB9ngAAH/gAAAfgAAABgAAAAAAAAAAAAAAAAAA="))
diff --git a/apps/diceroll/app.js b/apps/diceroll/app.js
new file mode 100644
index 000000000..d514ce92f
--- /dev/null
+++ b/apps/diceroll/app.js
@@ -0,0 +1,108 @@
+var init_message = true;
+var acc_data;
+var die_roll = 1;
+var selected_die = 0;
+var roll = 0;
+const dices = [4, 6, 10, 12, 20];
+
+g.setFontAlign(0,0);
+
+Bangle.on('touch', function(button, xy) {
+ // Change die if not rolling
+ if(roll < 1){
+ if(selected_die <= 3){
+ selected_die++;
+ }else{
+ selected_die = 0;
+ }
+ }
+ //Disable initial message
+ init_message = false;
+});
+
+function rect(){
+ x1 = g.getWidth()/2 - 35;
+ x2 = g.getWidth()/2 + 35;
+ y1 = g.getHeight()/2 - 35;
+ y2 = g.getHeight()/2 + 35;
+ g.drawRect(x1, y1, x2, y2);
+}
+
+function pentagon(){
+ x1 = g.getWidth()/2;
+ y1 = g.getHeight()/2 - 50;
+ x2 = g.getWidth()/2 - 50;
+ y2 = g.getHeight()/2 - 10;
+ x3 = g.getWidth()/2 - 30;
+ y3 = g.getHeight()/2 + 30;
+ x4 = g.getWidth()/2 + 30;
+ y4 = g.getHeight()/2 + 30;
+ x5 = g.getWidth()/2 + 50;
+ y5 = g.getHeight()/2 - 10;
+ g.drawPoly([x1, y1, x2, y2, x3, y3, x4, y4, x5, y5], true);
+}
+
+function triangle(){
+ x1 = g.getWidth()/2;
+ y1 = g.getHeight()/2 - 57;
+ x2 = g.getWidth()/2 - 50;
+ y2 = g.getHeight()/2 + 23;
+ x3 = g.getWidth()/2 + 50;
+ y3 = g.getHeight()/2 + 23;
+ g.drawPoly([x1, y1, x2, y2, x3, y3], true);
+}
+
+function drawDie(variant) {
+ if(variant == 1){
+ //Rect, 6
+ rect();
+ }else if(variant == 3){
+ //Pentagon, 12
+ pentagon();
+ }else{
+ //Triangle, 4, 10, 20
+ triangle();
+ }
+}
+
+function initMessage(){
+ g.setFont("6x8", 2);
+ g.drawString("Dice-n-Roll", g.getWidth()/2, 20);
+ g.drawString("Shake to roll", g.getWidth()/2, 60);
+ g.drawString("Tap to change", g.getWidth()/2, 80);
+ g.drawString("Tap to start", g.getWidth()/2, 150);
+}
+
+function rollDie(){
+ acc_data = Bangle.getAccel();
+ if(acc_data.diff > 0.3){
+ roll = 3;
+ }
+ //Mange the die "roll" by chaning the number a few times
+ if(roll > 0){
+ g.drawString("Rolling!", g.getWidth()/2, 150);
+ die_roll = Math.abs(E.hwRand()) % dices[selected_die] + 1;
+ roll--;
+ }
+ //Draw dice graphics
+ drawDie(selected_die);
+ //Draw dice number
+ g.setFontAlign(0,0);
+ g.setFont("Vector", 45);
+ g.drawString(die_roll, g.getWidth()/2, g.getHeight()/2);
+ //Draw selected die in right corner
+ g.setFont("6x8", 2);
+ g.drawString(dices[selected_die], g.getWidth()-15, 15);
+}
+
+function main() {
+ g.clear();
+ if(init_message){
+ initMessage();
+ }else{
+ rollDie();
+ }
+ Bangle.setLCDPower(1);
+}
+
+var interval = setInterval(main, 300);
\ No newline at end of file
diff --git a/apps/diceroll/app.png b/apps/diceroll/app.png
new file mode 100644
index 000000000..b695b7080
Binary files /dev/null and b/apps/diceroll/app.png differ
diff --git a/apps/diceroll/diceroll_screenshot.png b/apps/diceroll/diceroll_screenshot.png
new file mode 100644
index 000000000..71024edbb
Binary files /dev/null and b/apps/diceroll/diceroll_screenshot.png differ
diff --git a/apps/diceroll/metadata.json b/apps/diceroll/metadata.json
new file mode 100644
index 000000000..81a2f8bfd
--- /dev/null
+++ b/apps/diceroll/metadata.json
@@ -0,0 +1,14 @@
+{ "id": "diceroll",
+ "name": "Dice-n-Roll",
+ "shortName":"Dice-n-Roll",
+ "icon": "app.png",
+ "version":"0.01",
+ "description": "A dice app with a few different dice.",
+ "screenshots": [{"url":"diceroll_screenshot.png"}],
+ "tags": "game",
+ "supports": ["BANGLEJS2"],
+ "storage": [
+ {"name":"diceroll.app.js","url":"app.js"},
+ {"name":"diceroll.img","url":"app-icon.js","evaluate":true}
+ ]
+ }
\ No newline at end of file
diff --git a/apps/dinoClock/README.md b/apps/dinoClock/README.md
new file mode 100644
index 000000000..7568731d9
--- /dev/null
+++ b/apps/dinoClock/README.md
@@ -0,0 +1,17 @@
+# dinoClock
+
+Watchface with T-Rex Dinosaur from Chrome.
+It displays current temperature and weather.
+
+**Warning**: Element position and styles can change in the future.
+
+Based on the [Weather Clock](https://github.com/espruino/BangleApps/tree/master/apps/weatherClock).
+
+# Requirements
+
+**This clock requires Gadgetbridge and the weather app in order to get weather data!**
+
+See the [Bangle.js Gadgetbridge documentation](https://www.espruino.com/Gadgetbridge) for instructions on setting up Gadgetbridge and weather.
+
+
+
diff --git a/apps/dinoClock/app.js b/apps/dinoClock/app.js
new file mode 100644
index 000000000..82192d234
--- /dev/null
+++ b/apps/dinoClock/app.js
@@ -0,0 +1,219 @@
+const storage = require('Storage');
+const locale = require("locale");
+
+
+
+
+// add modifiied 4x5 numeric font
+(function(graphics) {
+ graphics.prototype.setFont4x5NumPretty = function() {
+ this.setFontCustom(atob("IQAQDJgH4/An4QXr0Fa/BwnwdrcH63BCHwfr8Ha/"),45,atob("AwIEBAQEBAQEBAQEBA=="),5);
+ };
+})(Graphics);
+
+// add font for days of the week
+(function(graphics) {
+ graphics.prototype.setFontDoW = function() {
+ this.setFontCustom(atob("///////ADgB//////+AHAD//////gAAAH//////4D8B+A///////4AcAOAH//////4AcAOAAAAAB//////wA4AcAP//////wAAAAAAAA//////4AcAP//////wA4Af//////gAAAH//////5z85+c/OfnOAA4AcAOAH//////4AcAOAAAAAB//////wcAOAHB//////wAAAAAAAA///////ODnBzg5wc4AAAAD//////84OcH//8/+fAAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////84OcH//////AAAAAAAAAAAAA/z/5/8/OfnPz/5/8/wAAAD//////gBwA///////AAAAAAAAAAAAA"),48,24,13);
+ };
+})(Graphics);
+
+
+const SUN = 1;
+const PART_SUN = 2;
+const CLOUD = 3;
+const SNOW = 4;
+const RAIN = 5;
+const STORM = 6;
+const ERR = 7;
+
+/**
+Choose weather icon based on weather const
+Weather icons from https://icons8.com/icon/set/weather/ios-glyphs
+Error icon from https://icons8.com/icon/set/error-cloud/ios-glyphs
+**/
+function weatherIcon(weather) {
+ switch (weather) {
+ case SUN:
+ return atob("Hh4BAAAAAAAMAAAAMAAAAMAAAAMAABgMBgBwADgA4AHAAY/GAAB/gAAD/wAAH/4AAP/8AAP/8AfP/8+fP/8+AP/8AAP/8AAH/4AAD/wAAB/gAAY/GAA4AHABwADgBgMBgAAMAAAAMAAAAMAAAAMAAAAAAAA=");
+ case PART_SUN:
+ return atob("Hh4BAAAAAAAAAAAMAAAAMAAAEMIAAOAcAAGAYAAAeAAAA/AAAB/gAA5/gAA5/g+AB+D/gA4H/wAR//wGD//4OD//4EH//4AH//4Af//+Af//+A////A////A////A///+Af//+AH//4AAAAAAAAAAAAAAAA=");
+ case CLOUD:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB///wD///wD///wP///8f///+f///+////////////////////f///+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA=");
+ case SNOW:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf8/AA/8/AB/gHgH/wP4H/wP4P/gH8P/8/8P/8/8P///4H///4B///gAAAAAAMAAAAMAAAB/gGAA/AfgA/AfgB/gfgAMAfgAMAGAAAAAAAAAAAA=");
+ case RAIN:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H///4P///8P///8P///8P///4H///4B///gAAAAAAAAAABgBgABgBgABhhhgABgBgABgBgAAAAAAAAAAAAAAAAAAAAA=");
+ case STORM:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAHwAAAf8AAA/+AAH/+AAf//AAf//AA///AB///gH///4H/x/4P/g/8P/k/8P/E/8P/M/4H+MP4B+cHgAAfgAAA/gABg/AABgHAABgGBgAAGBgAAEBgAAEAAAAAAAAAAAAAAAAAA=");
+ case ERR:
+ default:
+ return atob("Hh4BAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAf+AAA//AAB//gAf//gB///wB/z/wD/z/wD/z/wP/z/8f/z/+f/z/+//z//////////////z//f/z/+f///+P///8D///wAAAAAAAAAAAAAAAAAAAAAAAAAA=");
+ }
+}
+
+
+/**
+Choose weather icon to display based on condition.
+Based on function from the Bangle weather app so it should handle all of the conditions
+sent from gadget bridge.
+*/
+function chooseIcon(condition) {
+ condition = condition.toLowerCase();
+ if (condition.includes("thunderstorm")) return weatherIcon(STORM);
+ if (condition.includes("freezing")||condition.includes("snow")||
+ condition.includes("sleet")) {
+ return weatherIcon(SNOW);
+ }
+ if (condition.includes("drizzle")||
+ condition.includes("shower")) {
+ return weatherIcon(RAIN);
+ }
+ if (condition.includes("rain")) return weatherIcon(RAIN);
+ if (condition.includes("clear")) return weatherIcon(SUN);
+ if (condition.includes("few clouds")) return weatherIcon(PART_SUN);
+ if (condition.includes("scattered clouds")) return weatherIcon(CLOUD);
+ if (condition.includes("clouds")) return weatherIcon(CLOUD);
+ if (condition.includes("mist") ||
+ condition.includes("smoke") ||
+ condition.includes("haze") ||
+ condition.includes("sand") ||
+ condition.includes("dust") ||
+ condition.includes("fog") ||
+ condition.includes("ash") ||
+ condition.includes("squalls") ||
+ condition.includes("tornado")) {
+ return weatherIcon(CLOUD);
+ }
+ return weatherIcon(CLOUD);
+}
+
+/*
+* Choose weather icon to display based on weather conditition code
+* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
+*/
+function chooseIconByCode(code) {
+ const codeGroup = Math.round(code / 100);
+ switch (codeGroup) {
+ case 2: return weatherIcon(STORM);
+ case 3: return weatherIcon(RAIN);
+ case 5: return weatherIcon(RAIN);
+ case 6: return weatherIcon(SNOW);
+ case 7: return weatherIcon(CLOUD);
+ case 8:
+ switch (code) {
+ case 800: return weatherIcon(SUN);
+ case 801: return weatherIcon(PART_SUN);
+ default: return weatherIcon(CLOUD);
+ }
+ default: return weatherIcon(CLOUD);
+ }
+}
+
+/**
+Get weather stored in json file by weather app.
+*/
+function getWeather() {
+ let jsonWeather = storage.readJSON('weather.json');
+ return jsonWeather;
+}
+
+// timeout used to update every minute
+var drawTimeout;
+
+// schedule a draw for the next minute
+function queueDraw() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ },60000-(Date.now()%60000));
+}
+
+// only draw the first time
+function drawBg() {
+ var bgImg = require("heatshrink").decompress(atob("2E7wINKn///+AEaIVUgIUB//wCs/5CtRXrCvMD8AVTg4LFCv4VZ/iSLCrwWMCrMOAQMPCp7cBCojjFCo/xFgIVQgeHCopABCpcH44Vuh/AQQX/wAV7+F/Cq/nCsw/CCqyvRCvgODCqfAgEDCp4QCSIIVQgIOBDQgGDABX/NgIECCp8HCrM/CgP4CqKaCCqSfCCqq1BCqBuB54VqgYVG/gCECp0BwgCDCp8HgYCDCo/wCo0MgHAjACBj7rDABS1Bv4lBv4rPAAsPCo3+gbbPJAIVFiAXMFZ2AUQsAuAQHiOAgJeEA"));
+ g.reset();
+ g.drawImage(bgImg,0,101);
+}
+
+function square(x,y,w,e) {
+ g.setColor("#000").fillRect(x,y,x+w,y+w);
+ g.setColor("#fff").fillRect(x+e,y+e,x+w-e,y+w-e);
+}
+
+function draw() {
+ var d = new Date();
+ var h = d.getHours(), m = d.getMinutes();
+ h = ("0"+h).substr(-2);
+ m = ("0"+m).substr(-2);
+
+ var day = d.getDate(), mon = d.getMonth(), dow = d.getDay();
+ day = ("0"+day).substr(-2);
+ mon = ("0"+(mon+1)).substr(-2);
+ dow = ((dow+6)%7).toString();
+ date = day+"."+mon;
+
+ var weatherJson = getWeather();
+ var wIcon;
+ var temp;
+ if(weatherJson && weatherJson.weather){
+ var currentWeather = weatherJson.weather;
+ temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/);
+ const code = currentWeather.code||-1;
+ if (code > 0) {
+ wIcon = chooseIconByCode(code);
+ } else {
+ wIcon = chooseIcon(currentWeather.txt);
+ }
+ }else{
+ temp = "";
+ wIcon = weatherIcon(ERR);
+ }
+ g.reset();
+ g.clearRect(22,35,153,75);
+ g.setFont("4x5NumPretty",8);
+ g.fillRect(84,42,92,49);
+ g.fillRect(84,60,92,67);
+ g.drawString(h,22,35);
+ g.drawString(m,98,35);
+
+ g.clearRect(22,95,22+4*2*4+2*4,95+2*5);
+ g.setFont("4x5NumPretty",2);
+ g.drawString(date,22,95);
+
+ g.clearRect(22,79,22+24,79+13);
+ g.setFont("DoW");
+ g.drawString(dow,22,79);
+
+ g.drawImage(wIcon,126,81);
+
+ g.clearRect(108,114,176,114+4*5);
+ if (temp != "") {
+ var tempWidth;
+ const mid=126+15;
+ if (temp[1][0]=="-") {
+ // do not account for - when aligning
+ const minusWidth=3*4;
+ tempWidth = minusWidth+(temp[1].length-1)*4*4;
+ x = mid-Math.round((tempWidth-minusWidth)/2)-minusWidth;
+ } else {
+ tempWidth = temp[1].length*4*4;
+ x = mid-Math.round(tempWidth/2);
+ }
+ g.setFont("4x5NumPretty",4);
+ g.drawString(temp[1],x,114);
+ square(x+tempWidth,114,6,2);
+ }
+
+ // queue draw in one minute
+ queueDraw();
+}
+
+g.clear();
+drawBg();
+Bangle.setUI("clock"); // Show launcher when middle button pressed
+Bangle.loadWidgets();
+Bangle.drawWidgets();
+draw();
+
diff --git a/apps/dinoClock/app.png b/apps/dinoClock/app.png
new file mode 100644
index 000000000..c05276ee3
Binary files /dev/null and b/apps/dinoClock/app.png differ
diff --git a/apps/dinoClock/icon.js b/apps/dinoClock/icon.js
new file mode 100644
index 000000000..2410dad14
--- /dev/null
+++ b/apps/dinoClock/icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgJC/AAVh/E/hgFC/O/AoMB8EZwc8AoUYgYFBgFgjAXDAowXBAo8B/ARBn4FGAAsBmAFE2ADBhwFEj4VEn+AgPvAontgfwv+ABIMCMwIVCgf4FIWAAoN3sAFCwERoEB0MHwF3gEF0MPwFEAoW/4ALD/4tCg/hAoYhB/5ZDwF+Aok0gEIkEf/4AB8eMBoM2bkw="))
diff --git a/apps/dinoClock/metadata.json b/apps/dinoClock/metadata.json
new file mode 100644
index 000000000..a61ce122b
--- /dev/null
+++ b/apps/dinoClock/metadata.json
@@ -0,0 +1,17 @@
+{
+ "id": "dinoClock",
+ "name": "Dino Clock",
+ "description": "Clock with dino from Chrome",
+ "screenshots": [{"url":"screens/screen1.png"}],
+ "icon": "app.png",
+ "version": "0.01",
+ "type": "clock",
+ "tags": "clock, weather, dino, trex, chrome",
+ "supports": ["BANGLEJS2"],
+ "allow_emulator": true,
+ "readme": "README.md",
+ "storage": [
+ {"name":"dinoClock.app.js","url":"app.js"},
+ {"name":"dinoClock.img","url":"icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/dinoClock/screens/screen1.png b/apps/dinoClock/screens/screen1.png
new file mode 100644
index 000000000..ca4386449
Binary files /dev/null and b/apps/dinoClock/screens/screen1.png differ
diff --git a/apps/dtlaunch/ChangeLog b/apps/dtlaunch/ChangeLog
index 95952b9fe..09804b82e 100644
--- a/apps/dtlaunch/ChangeLog
+++ b/apps/dtlaunch/ChangeLog
@@ -11,3 +11,4 @@
0.11: Fix bangle.js 1 white icons not displaying
0.12: On Bangle 2 change to swiping up/down to move between pages as to match page indicator. Swiping from left to right now loads the clock.
0.13: Added swipeExit setting so that left-right to exit is an option
+0.14: Don't move pages when doing exit swipe.
diff --git a/apps/dtlaunch/README.md b/apps/dtlaunch/README.md
index bea20ef65..55c9f53b8 100644
--- a/apps/dtlaunch/README.md
+++ b/apps/dtlaunch/README.md
@@ -29,6 +29,6 @@ Bangle 2:
**Touch** - icon to select, scond touch launches app
-**Swipe Left** - move to next page of app icons
+**Swipe Left/Up** - move to next page of app icons
-**Swipe Right** - move to previous page of app icons
+**Swipe Right/Down** - move to previous page of app icons
diff --git a/apps/dtlaunch/app-b2.js b/apps/dtlaunch/app-b2.js
index 8466a7414..46194ec5d 100644
--- a/apps/dtlaunch/app-b2.js
+++ b/apps/dtlaunch/app-b2.js
@@ -93,7 +93,7 @@ Bangle.on("swipe",(dirLeftRight, dirUpDown)=>{
if (dirUpDown==-1||dirLeftRight==-1){
++page; if (page>maxPage) page=0;
drawPage(page);
- } else if (dirUpDown==1||dirLeftRight==1){
+ } else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
--page; if (page<0) page=maxPage;
drawPage(page);
}
diff --git a/apps/dtlaunch/metadata.json b/apps/dtlaunch/metadata.json
index 7784972ca..4a0b8067c 100644
--- a/apps/dtlaunch/metadata.json
+++ b/apps/dtlaunch/metadata.json
@@ -1,7 +1,7 @@
{
"id": "dtlaunch",
"name": "Desktop Launcher",
- "version": "0.13",
+ "version": "0.14",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png",
diff --git a/apps/f9lander/ChangeLog b/apps/f9lander/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/f9lander/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/f9lander/README.md b/apps/f9lander/README.md
new file mode 100644
index 000000000..16202f166
--- /dev/null
+++ b/apps/f9lander/README.md
@@ -0,0 +1,33 @@
+# F9 Lander
+
+Land a Falcon 9 booster on a drone ship.
+
+## Game play
+
+Attempt to land your Falcon 9 booster on a drone ship before running out of fuel.
+A successful landing requires:
+ * setting down on the ship
+ * the booster has to be mostly vertical
+ * the landing speed cannot be too high
+
+## Controls
+
+The angle of the booster is controlled by tilting the watch side-to-side. The
+throttle level is controlled by tilting the watch forward and back:
+ * screen horizontal (face up) means no throttle
+ * screen vertical corresponds to full throttle
+
+The fuel burn rate is proportional to the throttle level.
+
+## Creators
+Liam Kl. B.
+
+Marko Kl. B.
+
+## Screenshots
+
+
+
+
+
+
diff --git a/apps/f9lander/app-icon.js b/apps/f9lander/app-icon.js
new file mode 100644
index 000000000..572768a28
--- /dev/null
+++ b/apps/f9lander/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwwcA/4AD/P8yVJkgCCye27dt2wRE//kCIuSuwRIBwgCCpwRQpIRRnYRQkmdCIvPCJICBEZ4RG/IRP/15CJ/z5IRPz4RM/gQB/n+BxICCn/z/P/BxQCDz7mIAX4Cq31/CJ+ebpiYE/IR/CNP/5IROnn//4jP5DFQ5sJCKAjPk3oCMMk4QRQAX4Ckn7jBAA/5CK8nCJPJNHA"))
diff --git a/apps/f9lander/app.js b/apps/f9lander/app.js
new file mode 100644
index 000000000..7e52104c0
--- /dev/null
+++ b/apps/f9lander/app.js
@@ -0,0 +1,150 @@
+const falcon9 = Graphics.createImage(`
+ xxxxx
+ xxxxx xxxxx
+ x x
+ x x
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxx
+ xxxxxxxxx
+ xx xxxxx xx
+xx xx`);
+
+const droneShip = Graphics.createImage(`
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+`);
+
+const droneX = Math.floor(Math.random()*(g.getWidth()-droneShip.width-40) + 20)
+const cloudOffs = Math.floor(Math.random()*g.getWidth()/2);
+
+const oceanHeight = g.getHeight()*0.1;
+
+const targetY = g.getHeight()-oceanHeight-falcon9.height/2;
+
+var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2,
+ y : 20,
+ vx : 0,
+ vy : 0,
+ mass : 100,
+ fuel : 100 };
+
+var exploded = false;
+var nExplosions = 0;
+var landed = false;
+
+const gravity = 4;
+const dt = 0.1;
+const fuelBurnRate = 20*(176/g.getHeight());
+const maxV = 12;
+
+function flameImageGen (throttle) {
+ var str = " xxx \n xxx \n";
+ str += "xxxxx\n".repeat(throttle);
+ str += " xxx \n x \n";
+ return Graphics.createImage(str);
+}
+
+function drawFalcon(x, y, throttle, angle) {
+ g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle});
+ if (throttle>0) {
+ var flameImg = flameImageGen(throttle);
+ var r = falcon9.height/2 + flameImg.height/2-1;
+ var xoffs = -Math.sin(angle)*r;
+ var yoffs = Math.cos(angle)*r;
+ if (Math.random()>0.7) g.setColor(1, 0.5, 0);
+ else g.setColor(1, 1, 0);
+ g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle});
+ }
+}
+
+function drawBG() {
+ g.setBgColor(0.2, 0.2, 1).clear();
+ g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1);
+ g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10);
+ g.setColor(1, 1, 0).fillCircle(g.getWidth(), 0, 20);
+ g.setColor(1, 1, 1).drawImage(droneShip, droneX, g.getHeight()-oceanHeight-1);
+}
+
+function showFuel() {
+ g.setColor(0, 0, 0).setFont("4x6:2").setFontAlign(-1, -1, 0).drawString("Fuel: "+Math.abs(booster.fuel).toFixed(0), 4, 4);
+}
+
+function renderScreen(input) {
+ drawBG();
+ showFuel();
+ drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle);
+}
+
+function getInputs() {
+ var accel = Bangle.getAccel();
+ var a = Math.PI/2 + Math.atan2(accel.y, accel.x);
+ var t = (1+accel.z);
+ if (t > 1) t = 1;
+ if (t < 0) t = 0;
+ if (booster.fuel<=0) t = 0;
+ return {throttle: t, angle: a};
+}
+
+function epilogue(str) {
+ g.setFont("Vector", 24).setFontAlign(0, 0, 0).setColor(0, 0, 0).drawString(str, g.getWidth()/2, g.getHeight()/2).flip();
+ g.setFont("Vector", 16).drawString("<= again exit =>", g.getWidth()/2, g.getHeight()/2+20);
+ clearInterval(stepInterval);
+ Bangle.on("swipe", (d) => { if (d>0) load(); else load('f9lander.app.js'); });
+}
+
+function gameStep() {
+ if (exploded) {
+ if (nExplosions++ < 15) {
+ var r = Math.random()*25;
+ var x = Math.random()*30 - 15;
+ var y = Math.random()*30 - 15;
+ g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r);
+ if (nExplosions==1) Bangle.buzz(600);
+ }
+ else epilogue("You crashed!");
+ }
+ else {
+ var input = getInputs();
+ if (booster.y >= targetY) {
+// console.log(booster.x + " " + booster.y + " " + booster.vy + " " + droneX + " " + input.angle);
+ if (Math.abs(booster.x-droneX-droneShip.width/2) {
+ stepInterval = setInterval(gameStep, Math.floor(1000*dt));
+ Bangle.removeListener("swipe");
+});
diff --git a/apps/f9lander/f9lander.png b/apps/f9lander/f9lander.png
new file mode 100644
index 000000000..f03cc1645
Binary files /dev/null and b/apps/f9lander/f9lander.png differ
diff --git a/apps/f9lander/f9lander_screenshot1.png b/apps/f9lander/f9lander_screenshot1.png
new file mode 100644
index 000000000..ea7d8a834
Binary files /dev/null and b/apps/f9lander/f9lander_screenshot1.png differ
diff --git a/apps/f9lander/f9lander_screenshot2.png b/apps/f9lander/f9lander_screenshot2.png
new file mode 100644
index 000000000..a2f13d6c7
Binary files /dev/null and b/apps/f9lander/f9lander_screenshot2.png differ
diff --git a/apps/f9lander/f9lander_screenshot3.png b/apps/f9lander/f9lander_screenshot3.png
new file mode 100644
index 000000000..61b8be82f
Binary files /dev/null and b/apps/f9lander/f9lander_screenshot3.png differ
diff --git a/apps/f9lander/metadata.json b/apps/f9lander/metadata.json
new file mode 100644
index 000000000..75c6a0164
--- /dev/null
+++ b/apps/f9lander/metadata.json
@@ -0,0 +1,15 @@
+{ "id": "f9lander",
+ "name": "Falcon9 Lander",
+ "shortName":"F9lander",
+ "version":"0.01",
+ "description": "Land a rocket booster",
+ "icon": "f9lander.png",
+ "screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }],
+ "readme": "README.md",
+ "tags": "game",
+ "supports" : ["BANGLEJS", "BANGLEJS2"],
+ "storage": [
+ {"name":"f9lander.app.js","url":"app.js"},
+ {"name":"f9lander.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/ffcniftya/ChangeLog b/apps/ffcniftya/ChangeLog
index 420c553f5..cb520193b 100644
--- a/apps/ffcniftya/ChangeLog
+++ b/apps/ffcniftya/ChangeLog
@@ -1,2 +1,4 @@
0.01: New Clock Nifty A
-0.02: Shows the current week number (ISO8601), can be disabled via settings ""
+0.02: Shows the current week number (ISO8601), can be disabled via settings
+0.03: Call setUI before loading widgets
+ Improve settings page
diff --git a/apps/ffcniftya/README.md b/apps/ffcniftya/README.md
index 86f1f5c2d..80005fd3c 100644
--- a/apps/ffcniftya/README.md
+++ b/apps/ffcniftya/README.md
@@ -1,13 +1,12 @@
# Nifty-A Clock
-Colors are black/white - photos have non correct camera color "blue"
+Colors are black/white - photos have non correct camera color "blue".
-## This is the clock
+This is the clock:

-## The week number (ISO8601) can be turned of in settings
-(default is **"On"**)
+The week number (ISO8601) can be turned off in settings (default is `On`)

diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js
index 5da1ec48e..4000a1578 100644
--- a/apps/ffcniftya/app.js
+++ b/apps/ffcniftya/app.js
@@ -1,6 +1,6 @@
const locale = require("locale");
-const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
-const CFG = require('Storage').readJSON("ffcniftya.json", 1) || {showWeekNum: true};
+const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
+const showWeekNum = Object.assign({ showWeekNum: true }, require('Storage').readJSON("ffcniftya.json", true))["showWeekNum"];
/* Clock *********************************************/
const scale = g.getWidth() / 176;
@@ -17,16 +17,17 @@ const center = {
y: Math.round(((viewport.height - widget) / 2) + widget),
}
-function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
- var tdt = new Date(date.valueOf());
- var dayn = (date.getDay() + 6) % 7;
- tdt.setDate(tdt.getDate() - dayn + 3);
- var firstThursday = tdt.valueOf();
- tdt.setMonth(0, 1);
- if (tdt.getDay() !== 4) {
- tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
- }
- return 1 + Math.ceil((firstThursday - tdt) / 604800000);
+// copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
+function ISO8601_week_no(date) {
+ var tdt = new Date(date.valueOf());
+ var dayn = (date.getDay() + 6) % 7;
+ tdt.setDate(tdt.getDate() - dayn + 3);
+ var firstThursday = tdt.valueOf();
+ tdt.setMonth(0, 1);
+ if (tdt.getDay() !== 4) {
+ tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
+ }
+ return 1 + Math.ceil((firstThursday - tdt) / 604800000);
}
function d02(value) {
@@ -59,7 +60,7 @@ function draw() {
g.drawString(year, centerDatesScaleX, center.y - 62 * scale);
g.drawString(month, centerDatesScaleX, center.y - 44 * scale);
g.drawString(day, centerDatesScaleX, center.y - 26 * scale);
- if (CFG.showWeekNum) g.drawString(d02(ISO8601_week_no(now)), centerDatesScaleX, center.y + 15 * scale);
+ if (showWeekNum) g.drawString(weekNum, centerDatesScaleX, center.y + 15 * scale);
g.drawString(monthName, centerDatesScaleX, center.y + 48 * scale);
g.drawString(dayName, centerDatesScaleX, center.y + 66 * scale);
}
@@ -79,7 +80,6 @@ function clearTickTimer() {
function queueNextTick() {
clearTickTimer();
tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000));
- // tickTimer = setTimeout(tick, 3000);
}
function tick() {
@@ -91,21 +91,16 @@ function tick() {
// Clear the screen once, at startup
g.clear();
-// Start ticking
tick();
-// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower', (on) => {
if (on) {
- tick(); // Start ticking
+ tick();
} else {
- clearTickTimer(); // stop ticking
+ clearTickTimer();
}
});
-// Load widgets
+Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
-
-// Show launcher when middle button pressed
-Bangle.setUI("clock");
\ No newline at end of file
diff --git a/apps/ffcniftya/metadata.json b/apps/ffcniftya/metadata.json
index ce91cc225..91b426cd0 100644
--- a/apps/ffcniftya/metadata.json
+++ b/apps/ffcniftya/metadata.json
@@ -1,7 +1,7 @@
{
"id": "ffcniftya",
"name": "Nifty-A Clock",
- "version": "0.02",
+ "version": "0.03",
"description": "A nifty clock with time and date",
"icon": "app.png",
"screenshots": [{"url":"screenshot_nifty.png"}],
diff --git a/apps/ffcniftya/settings.js b/apps/ffcniftya/settings.js
index 46e4ef5aa..aec1d680a 100644
--- a/apps/ffcniftya/settings.js
+++ b/apps/ffcniftya/settings.js
@@ -1,23 +1,15 @@
-(function(back) {
- var FILE = "ffcniftya.json";
- // Load settings
- var cfg = require('Storage').readJSON(FILE, 1) || { showWeekNum: true };
+(function (back) {
+ const settings = Object.assign({ showWeekNum: true }, require("Storage").readJSON("ffcniftya.json", true));
- function writeSettings() {
- require('Storage').writeJSON(FILE, cfg);
- }
-
- // Show the menu
E.showMenu({
- "" : { "title" : "Nifty-A Clock" },
- "< Back" : () => back(),
- 'week number?': {
- value: cfg.showWeekNum,
- format: v => v?"On":"Off",
+ "": { "title": "Nifty-A Clock" },
+ "< Back": () => back(),
+ /*LANG*/"Show Week Number": {
+ value: settings.showWeekNum,
onchange: v => {
- cfg.showWeekNum = v;
- writeSettings();
+ settings.showWeekNum = v;
+ require("Storage").writeJSON("ffcniftya.json", settings);
}
}
});
-})
\ No newline at end of file
+})
diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog
index dedd31452..9fc7e3c5c 100644
--- a/apps/ffcniftyb/ChangeLog
+++ b/apps/ffcniftyb/ChangeLog
@@ -1,2 +1,5 @@
0.01: New Clock Nifty B
-0.02: Added configuration
\ No newline at end of file
+0.02: Added configuration
+0.03: Call setUI before loading widgets
+ Fix bug with black being unselectable
+ Improve settings page
diff --git a/apps/ffcniftyb/README.md b/apps/ffcniftyb/README.md
index e04243a0b..072f71cce 100644
--- a/apps/ffcniftyb/README.md
+++ b/apps/ffcniftyb/README.md
@@ -1,9 +1,6 @@
# Nifty Series B Clock
- Display Time and Date
-- Color Configuration
-
-##
+- Colour Configuration

-
diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js
index 75d217ab4..65c74dbd7 100644
--- a/apps/ffcniftyb/app.js
+++ b/apps/ffcniftyb/app.js
@@ -1,9 +1,5 @@
-const locale = require("locale");
-const storage = require('Storage');
-
-const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"];
-const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */;
-
+const is12Hour = Object.assign({ "12hour": false }, require("Storage").readJSON("setting.json", true))["12hour"];
+const color = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true)).color; // Default to RED
/* Clock *********************************************/
const scale = g.getWidth() / 176;
@@ -19,7 +15,7 @@ const center = {
};
function d02(value) {
- return ('0' + value).substr(-2);
+ return ("0" + value).substr(-2);
}
function renderEllipse(g) {
@@ -35,8 +31,8 @@ function renderText(g) {
const month = d02(now.getMonth() + 1);
const year = now.getFullYear();
- const month2 = locale.month(now, 3);
- const day2 = locale.dow(now, 3);
+ const month2 = require("locale").month(now, 3);
+ const day2 = require("locale").dow(now, 3);
g.setFontAlign(1, 0).setFont("Vector", 90 * scale);
g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale);
@@ -96,7 +92,6 @@ function startTick(run) {
stopTick();
run();
ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000));
- // ticker = setTimeout(() => startTick(run), 3000);
}
/* Init **********************************************/
@@ -104,7 +99,7 @@ function startTick(run) {
g.clear();
startTick(draw);
-Bangle.on('lcdPower', (on) => {
+Bangle.on("lcdPower", (on) => {
if (on) {
startTick(draw);
} else {
@@ -112,7 +107,6 @@ Bangle.on('lcdPower', (on) => {
}
});
+Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
-
-Bangle.setUI("clock");
diff --git a/apps/ffcniftyb/metadata.json b/apps/ffcniftyb/metadata.json
index 73f93ed36..3d26c27ea 100644
--- a/apps/ffcniftyb/metadata.json
+++ b/apps/ffcniftyb/metadata.json
@@ -1,8 +1,8 @@
{
"id": "ffcniftyb",
"name": "Nifty-B Clock",
- "version": "0.02",
- "description": "A nifty clock (series B) with time, date and color configuration",
+ "version": "0.03",
+ "description": "A nifty clock (series B) with time, date and colour configuration",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
diff --git a/apps/ffcniftyb/settings.js b/apps/ffcniftyb/settings.js
index 00abf80b5..da350edd8 100644
--- a/apps/ffcniftyb/settings.js
+++ b/apps/ffcniftyb/settings.js
@@ -1,49 +1,31 @@
(function (back) {
- const storage = require('Storage');
- const SETTINGS_FILE = "ffcniftyb.json";
+ const settings = Object.assign({ color: 63488 }, require("Storage").readJSON("ffcniftyb.json", true));
const colors = {
- 65535: 'White',
- 63488: 'Red',
- 65504: 'Yellow',
- 2047: 'Cyan',
- 2016: 'Green',
- 31: 'Blue',
- 0: 'Black',
+ 65535: /*LANG*/"White",
+ 63488: /*LANG*/"Red",
+ 65504: /*LANG*/"Yellow",
+ 2047: /*LANG*/"Cyan",
+ 2016: /*LANG*/"Green",
+ 31: /*LANG*/"Blue",
+ 0: /*LANG*/"Black"
}
- function load(settings) {
- return Object.assign(settings, storage.readJSON(SETTINGS_FILE, 1) || {});
- }
+ const menu = {};
+ menu[""] = { title: "Nifty-B Clock" };
+ menu["< Back"] = back;
- function save(settings) {
- storage.write(SETTINGS_FILE, settings)
- }
-
- const settings = load({
- color: 63488 /* red */,
+ Object.keys(colors).forEach(color => {
+ var label = colors[color];
+ menu[label] = {
+ value: settings.color == color,
+ onchange: () => {
+ settings.color = color;
+ require("Storage").write("ffcniftyb.json", settings);
+ setTimeout(load, 10);
+ }
+ };
});
- const saveColor = (color) => () => {
- settings.color = color;
- save(settings);
- back();
- };
-
- function showMenu(items, opt) {
- items[''] = opt || {};
- items['< Back'] = back;
- E.showMenu(items);
- }
-
- showMenu(
- Object.keys(colors).reduce((menu, color) => {
- menu[colors[color]] = saveColor(color);
- return menu;
- }, {}),
- {
- title: 'Color',
- selected: Object.keys(colors).indexOf(settings.color)
- }
- );
+ E.showMenu(menu);
});
diff --git a/apps/game1024/ChangeLog b/apps/game1024/ChangeLog
index 29838413e..800fa6b9d 100644
--- a/apps/game1024/ChangeLog
+++ b/apps/game1024/ChangeLog
@@ -6,4 +6,5 @@
0.06: Fixed issue 1609 added a message popup state handler to control unwanted screen redraw
0.07: Optimized the mover algorithm for efficiency (work in progress)
0.08: Bug fix at end of the game with victorious splash and glorious orchestra
-0.09: Added settings menu, removed symbol selection button (*), added highscore reset
\ No newline at end of file
+0.09: Added settings menu, removed symbol selection button (*), added highscore reset
+0.10: fixed clockmode in settings
diff --git a/apps/game1024/metadata.json b/apps/game1024/metadata.json
index e2c4bdb3e..728b5dc0e 100644
--- a/apps/game1024/metadata.json
+++ b/apps/game1024/metadata.json
@@ -1,7 +1,7 @@
{ "id": "game1024",
"name": "1024 Game",
"shortName" : "1024 Game",
- "version": "0.09",
+ "version": "0.10",
"icon": "game1024.png",
"screenshots": [ {"url":"screenshot.png" } ],
"readme":"README.md",
diff --git a/apps/game1024/settings.js b/apps/game1024/settings.js
index c8e393663..24a972600 100644
--- a/apps/game1024/settings.js
+++ b/apps/game1024/settings.js
@@ -32,10 +32,10 @@
}
},
"Exit press:": {
- value: !settings.debugMode, // ! converts undefined to true
+ value: !settings.clockMode, // ! converts undefined to true
format: v => v?"short":"long",
onchange: v => {
- settings.debugMode = v;
+ settings.clockMode = v;
writeSettings();
},
},
@@ -67,4 +67,4 @@
}
// Show the menu
E.showMenu(settingsMenu);
- })
\ No newline at end of file
+ })
diff --git a/apps/golfview/ChangeLog b/apps/golfview/ChangeLog
index b243db101..909b8feca 100644
--- a/apps/golfview/ChangeLog
+++ b/apps/golfview/ChangeLog
@@ -1 +1,2 @@
0.01: New App! Very limited course support.
+0.02: Course search added to BangleApps page
\ No newline at end of file
diff --git a/apps/golfview/custom.html b/apps/golfview/custom.html
index 94bc551c0..f80e8dee5 100644
--- a/apps/golfview/custom.html
+++ b/apps/golfview/custom.html
@@ -7,30 +7,55 @@
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous">
+
+
-
-
-
Search
-
+
+
+
+
+
No course loaded, please search for a course then choose 'select'.
Upload to Device
Download Course
-
A course needs a few things to be parsed correctly by this tool.
-
- See official mapping guidelines here .
- All holes and features must be within the target course's area.
- Supported features are greens, fairways, tees, bunkers, water hazards and holes.
- All features for a given hole should have the "ref" tag with the hole number as value. Shared features should
- list ref values separated by ';'. example .
- There must be 18 holes and they must have the following tags: handicap, par, ref, dist
- For any mapping assistance or issues, please file in the official
- repo
-
-
Example Course
-
© OpenStreetMap contributors
+
+
A course needs a few things to be parsed correctly by this tool.
+
+ See official mapping guidelines here .
+ All holes and features must be within the target course's area.
+ Supported features are greens, fairways, tees, bunkers, water hazards and holes.
+ All features for a given hole should have the "ref" tag with the hole number as value. Shared features
+ should
+ list ref values separated by ';'. example .
+ There must be 18 holes and they must have the following tags: handicap, par, ref, dist
+ For any mapping assistance or issues, please file in the official
+ repo
+
+
Example Course
+
+
@@ -38,13 +63,47 @@
+
+
+
+