diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog
index 398396c96..ab7db522c 100644
--- a/apps/alarm/ChangeLog
+++ b/apps/alarm/ChangeLog
@@ -41,3 +41,4 @@
0.38: Display date in locale
When switching 'repeat' from 'Workdays', 'Weekends' to 'Custom' preset Custom menu with previous selection
Display alarm label in delete prompt
+0.39: Dated event repeat option
diff --git a/apps/alarm/README.md b/apps/alarm/README.md
index 0298e0836..9da142dab 100644
--- a/apps/alarm/README.md
+++ b/apps/alarm/README.md
@@ -13,6 +13,7 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
- `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 (triggered based on amount of time elapsed in hours/minutes/seconds)
- `New Event` → Configure a new event (triggered based on time and date)
+ - `Repeat` → Alarm can be be fired only once or repeated (every X number of _days_, _weeks_, _months_ or _years_)
- `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
diff --git a/apps/alarm/app.js b/apps/alarm/app.js
index e97b61917..c0ae17f00 100644
--- a/apps/alarm/app.js
+++ b/apps/alarm/app.js
@@ -6,6 +6,8 @@ const firstDayOfWeek = (require("Storage").readJSON("setting.json", true) || {})
const WORKDAYS = 62
const WEEKEND = firstDayOfWeek ? 192 : 65;
const EVERY_DAY = firstDayOfWeek ? 254 : 127;
+const INTERVALS = ["day", "week", "month", "year"];
+const INTERVAL_LABELS = [/*LANG*/"Day", /*LANG*/"Week", /*LANG*/"Month", /*LANG*/"Year"];
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
@@ -44,8 +46,8 @@ function getLabel(e) {
const dateStr = e.date && require("locale").date(new Date(e.date), 1);
return (e.timer
? require("time_utils").formatDuration(e.timer)
- : (dateStr ? `${dateStr} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeDOW(e)}` : ""))
- ) + (e.msg ? " " + e.msg : "");
+ : (dateStr ? `${dateStr}${e.rp?"*":""} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeRepeat(e)}` : ""))
+ ) + (e.msg ? ` ${e.msg}` : "");
}
function showMainMenu() {
@@ -152,8 +154,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
onchange: v => alarm.on = v
},
/*LANG*/"Repeat": {
- value: decodeDOW(alarm),
- onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, alarm.dow, (repeat, dow) => {
+ value: decodeRepeat(alarm),
+ onchange: () => setTimeout(showEditRepeatMenu, 100, alarm.rp, date || alarm.dow, (repeat, dow) => {
alarm.rp = repeat;
alarm.dow = dow;
prepareAlarmForSave(alarm, alarmIndex, time, date, true);
@@ -178,9 +180,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate) {
};
if (!keyboard) delete menu[/*LANG*/"Message"];
- if (alarm.date || withDate) {
- delete menu[/*LANG*/"Repeat"];
- } else {
+ if (!alarm.date) {
delete menu[/*LANG*/"Day"];
delete menu[/*LANG*/"Month"];
delete menu[/*LANG*/"Year"];
@@ -229,49 +229,77 @@ function saveAndReload() {
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
}
-function decodeDOW(alarm) {
+function decodeRepeat(alarm) {
return alarm.rp
- ? require("date_utils")
- .dows(firstDayOfWeek, 2)
- .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
- .join("")
- .toLowerCase()
+ ? (alarm.date
+ ? `${alarm.rp.num}*${INTERVAL_LABELS[INTERVALS.indexOf(alarm.rp.interval)]}`
+ : require("date_utils")
+ .dows(firstDayOfWeek, 2)
+ .map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
+ .join("")
+ .toLowerCase())
: /*LANG*/"Once"
}
-function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
+function showEditRepeatMenu(repeat, day, dowChangeCallback) {
var originalRepeat = repeat;
- var originalDow = dow;
- var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
+ var dow;
const menu = {
"": { "title": /*LANG*/"Repeat Alarm" },
"< Back": () => dowChangeCallback(repeat, dow),
- /*LANG*/"Once": {
+ /*LANG*/"Only Once": () => dowChangeCallback(false, EVERY_DAY)
// 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, dow, dowChangeCallback, originalRepeat, originalDow)
- }
};
+ let restOfMenu;
+ if (typeof day === "number") {
+ dow = day;
+ var originalDow = dow;
+ var isCustom = repeat && dow != WORKDAYS && dow != WEEKEND && dow != EVERY_DAY;
+
+ restOfMenu = {
+ /*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 ? decodeRepeat({ rp: true, dow: dow }) : false,
+ onchange: () => setTimeout(showCustomDaysMenu, 10, dow, dowChangeCallback, originalRepeat, originalDow)
+ }
+ };
+ } else {
+ var date = day; // eventually: detect day of date and configure a repeat e.g. 3rd Monday of Month
+ dow = EVERY_DAY;
+ repeat = repeat || {interval: "month", num: 1};
+
+ restOfMenu = {
+ /*LANG*/"Every": {
+ value: repeat.num,
+ min: 1,
+ onchange: v => repeat.num = v
+ },
+ /*LANG*/"Interval": {
+ value: INTERVALS.indexOf(repeat.interval),
+ format: v => INTERVAL_LABELS[v],
+ min: 0,
+ max: INTERVALS.length - 1,
+ onchange: v => repeat.interval = INTERVALS[v]
+ }
+ };
+ }
+
+ Object.assign(menu, restOfMenu);
E.showMenu(menu);
}
diff --git a/apps/alarm/metadata.json b/apps/alarm/metadata.json
index 28d48daab..8522b07c0 100644
--- a/apps/alarm/metadata.json
+++ b/apps/alarm/metadata.json
@@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
- "version": "0.38",
+ "version": "0.39",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",
diff --git a/apps/altimeter/ChangeLog b/apps/altimeter/ChangeLog
index 29388520e..8d21cf797 100644
--- a/apps/altimeter/ChangeLog
+++ b/apps/altimeter/ChangeLog
@@ -1,2 +1,3 @@
0.01: New App!
0.02: Actually upload correct code
+0.03: Display sea-level pressure, too, and allow calibration
diff --git a/apps/altimeter/app.js b/apps/altimeter/app.js
index cac4e80fd..de664af39 100644
--- a/apps/altimeter/app.js
+++ b/apps/altimeter/app.js
@@ -1,4 +1,4 @@
-Bangle.setBarometerPower(true, "app");
+Bangle.setBarometerPower(true, "altimeter");
g.clear(1);
Bangle.loadWidgets();
@@ -10,21 +10,62 @@ var MEDIANLENGTH = 20;
var avr = [], median;
var value = 0;
+function getStandardPressure(altitude) {
+ const P0 = 1013.25; // standard pressure at sea level in hPa
+ const T0 = 288.15; // standard temperature at sea level in K
+ const g0 = 9.80665; // standard gravitational acceleration in m/s^2
+ const R = 8.31432; // gas constant in J/(mol*K)
+ const M = 0.0289644; // molar mass of air in kg/mol
+ const L = -0.0065; // temperature lapse rate in K/m
+
+ const temperature = T0 + L * altitude; // temperature at the given altitude
+ const pressure = P0 * Math.pow((temperature / T0), (-g0 * M) / (R * L)); // pressure at the given altitude
+
+ return pressure;
+}
+
+function convertToSeaLevelPressure(pressure, altitude) {
+ return 1013.25 * (pressure / getStandardPressure(altitude));
+}
+
Bangle.on('pressure', function(e) {
while (avr.length>MEDIANLENGTH) avr.pop();
avr.unshift(e.altitude);
median = avr.slice().sort();
- g.reset().clearRect(0,y-30,g.getWidth()-10,y+30);
+ g.reset().clearRect(0,y-30,g.getWidth()-10,R.h);
if (median.length>10) {
var mid = median.length>>1;
value = E.sum(median.slice(mid-4,mid+5)) / 9;
- g.setFont("Vector",50).setFontAlign(0,0).drawString((value-zero).toFixed(1), g.getWidth()/2, y);
+ t = value-zero;
+ if ((t > -100) && (t < 1000))
+ t = t.toFixed(1);
+ else
+ t = t.toFixed(0);
+ g.setFont("Vector",50).setFontAlign(0,0).drawString(t, g.getWidth()/2, y);
+ sea = convertToSeaLevelPressure(e.pressure, value-zero);
+ t = sea.toFixed(1) + " " + e.temperature.toFixed(1);
+ if (0) {
+ print("alt raw:", value.toFixed(1));
+ print("temperature:", e.temperature);
+ print("pressure:", e.pressure);
+ print("sea pressure:", sea);
+ print("std pressure:", getStandardPressure(value-zero));
+ }
+ g.setFont("Vector",25).setFontAlign(-1,0).drawString(t,
+ 10, R.y+R.h - 35);
}
});
+print(g.getFonts());
g.reset();
-g.setFont("6x8").setFontAlign(0,0).drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
+g.setFont("Vector:15");
+g.setFontAlign(0,0);
+g.drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
+g.drawString(/*LANG*/"SEA L (hPa) TEMP (C)", g.getWidth()/2, y+62);
+g.flip();
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"ZERO", g.getWidth()-5, g.getHeight()/2);
-setWatch(function() {
- zero = value;
-}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});
+Bangle.setUI("updown", btn=> {
+ if (!btn) zero=value;
+ if (btn<0) zero-=5;
+ if (btn>0) zero+=5;
+});
diff --git a/apps/altimeter/metadata.json b/apps/altimeter/metadata.json
index 8bdbf3022..8bf3772ed 100644
--- a/apps/altimeter/metadata.json
+++ b/apps/altimeter/metadata.json
@@ -1,6 +1,6 @@
{ "id": "altimeter",
"name": "Altimeter",
- "version":"0.02",
+ "version":"0.03",
"description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
"icon": "app.png",
"tags": "tool,outdoors",
diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog
index a63a54eaf..82e55fa91 100644
--- a/apps/boot/ChangeLog
+++ b/apps/boot/ChangeLog
@@ -65,3 +65,4 @@
Only add boot info comments if settings.bootDebug was set
If settings.bootDebug is set, output timing for each section of .boot0
0.56: Settings.log = 0,1,2,3 for off,display, log, both
+0.57: Handle the whitelist being disabled
diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js
index d929b26a0..626171490 100644
--- a/apps/boot/bootupdate.js
+++ b/apps/boot/bootupdate.js
@@ -79,7 +79,7 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
-if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
+if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
// ================================================== FIXING OLDER FIRMWARES
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
diff --git a/apps/boot/metadata.json b/apps/boot/metadata.json
index 9f64b672b..c652f6136 100644
--- a/apps/boot/metadata.json
+++ b/apps/boot/metadata.json
@@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
- "version": "0.56",
+ "version": "0.57",
"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/bwclklite/ChangeLog b/apps/bwclklite/ChangeLog
new file mode 100644
index 000000000..c728997da
--- /dev/null
+++ b/apps/bwclklite/ChangeLog
@@ -0,0 +1,36 @@
+0.01: New App.
+0.02: Use build in function for steps and other improvements.
+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.
+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.
+0.10: HomeAssistant integration if HomeAssistant is installed.
+0.11: Performance improvements.
+0.12: Implements a 2D menu.
+0.13: Clicks < 24px are for widgets, if fullscreen mode is disabled.
+0.14: Adds humidity to weather data.
+0.15: Added option for a dynamic mode to show widgets only if unlocked.
+0.16: You can now show your agenda if your calendar is synced with Gadgetbridge.
+0.17: Fix - Step count was no more shown in the menu.
+0.18: Set timer for an agenda entry by simply clicking in the middle of the screen. Only one timer can be set.
+0.19: Fix - Compatibility with "Digital clock widget"
+0.20: Better handling of async data such as getPressure.
+0.21: On the default menu the week of year can be shown.
+0.22: Use the new clkinfo module for the menu.
+0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
+0.24: Update clock_info to avoid a redraw
+0.25: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on fw2v16.
+ ClockInfo Fix: Use .get instead of .show as .show is not implemented for weather etc.
+0.26: Use clkinfo.addInteractive instead of a custom implementation
+0.27: Clean out some leftovers in the remove function after switching to
+clkinfo.addInteractive that would cause ReferenceError.
+0.28: Option to show (1) time only and (2) week of year.
+0.29: use setItem of clockInfoMenu to change the active item
+0.30: Use widget_utils
+0.31: Use clock_info module as an app
+0.32: Diverge from BW Clock. Change out the custom font for a standard bitmap one to speed up loading times.
+ Remove invertion of theme as this doesn'twork very well with fastloading.
+ Do an quick inital fillRect on theclock info area.
diff --git a/apps/bwclklite/README.md b/apps/bwclklite/README.md
new file mode 100644
index 000000000..1ad320894
--- /dev/null
+++ b/apps/bwclklite/README.md
@@ -0,0 +1,30 @@
+# BW Clock Lite
+This is a fork of a very minimalistic clock.
+
+
+
+## Features
+The BW clock implements features that are exposed by other apps through the `clkinfo` module.
+For example, if you install the Simple Timer app, this menu item will be shown if you first
+touch the bottom of the screen and then swipe left/right to the Simple Timer menu. To select
+sub-items simply swipe up/down. To run an action (e.g. add 5 min), simply select the clkinfo (border) and touch on the item again. See also the screenshot below:
+
+
+
+Note: Check out the settings to change different themes.
+
+## Settings
+- Screen: Normal (widgets shown), Dynamic (widgets shown if unlocked) or Full (widgets are hidden).
+- Enable/disable lock icon in the settings. Useful if fullscreen mode is on.
+- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further.
+- Your bangle uses the sys color settings so you can change the color too.
+
+## Thanks to
+- Thanks to Gordon Williams not only for the great BangleJs, but specifically also for the implementation of `clkinfo` which simplified the BWClock a lot and moved complexety to the apps where it should be located.
+- Icons created by Flaticon
+
+## Creator
+[David Peer](https://github.com/peerdavid)
+
+## Contributors
+thyttan
diff --git a/apps/bwclklite/app-icon.js b/apps/bwclklite/app-icon.js
new file mode 100644
index 000000000..1df0fa6a5
--- /dev/null
+++ b/apps/bwclklite/app-icon.js
@@ -0,0 +1 @@
+require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA=="))
diff --git a/apps/bwclklite/app.js b/apps/bwclklite/app.js
new file mode 100644
index 000000000..1008eae9c
--- /dev/null
+++ b/apps/bwclklite/app.js
@@ -0,0 +1,331 @@
+{ // must be inside our own scope here so that when we are unloaded everything disappears
+
+/************************************************
+ * Includes
+ */
+const locale = require('locale');
+const storage = require('Storage');
+const clock_info = require("clock_info");
+const widget_utils = require("widget_utils");
+
+/************************************************
+ * Globals
+ */
+const SETTINGS_FILE = "bwclklite.setting.json";
+const W = g.getWidth();
+const H = g.getHeight();
+
+/************************************************
+ * Settings
+ */
+let settings = {
+ screen: "Normal",
+ showLock: true,
+ hideColon: false,
+ menuPosX: 0,
+ menuPosY: 0,
+};
+
+let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
+for (const key in saved_settings) {
+ settings[key] = saved_settings[key];
+}
+
+let isFullscreen = function() {
+ let s = settings.screen.toLowerCase();
+ if(s == "dynamic"){
+ return Bangle.isLocked();
+ } else {
+ return s == "full";
+ }
+};
+
+let getLineY = function(){
+ return H/5*2 + (isFullscreen() ? 0 : 8);
+};
+
+/************************************************
+ * Assets
+ */
+let imgLock = function() {
+ return {
+ width : 16, height : 16, bpp : 1,
+ transparent : 0,
+ buffer : E.toArrayBuffer(atob("A8AH4A5wDDAYGBgYP/w//D/8Pnw+fD58Pnw//D/8P/w="))
+ };
+};
+
+
+/************************************************
+ * Clock Info
+ */
+let clockInfoItems = clock_info.load();
+
+// Add some custom clock-infos
+let weekOfYear = function() {
+ let date = new Date();
+ date.setHours(0, 0, 0, 0);
+ // Thursday in current week decides the year.
+ date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
+ // January 4 is always in week 1.
+ let week1 = new Date(date.getFullYear(), 0, 4);
+ // Adjust to Thursday in week 1 and count number of weeks from date to week1.
+ return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
+ - 3 + (week1.getDay() + 6) % 7) / 7);
+};
+
+clockInfoItems[0].items.unshift({ name : "weekofyear",
+ get : function() { return { text : "Week " + weekOfYear(),
+ img : null};},
+ show : function() {},
+ hide : function() {},
+});
+
+// Empty for large time
+clockInfoItems[0].items.unshift({ name : "nop",
+ get : function() { return { text : null,
+ img : null};},
+ show : function() {},
+ hide : function() {},
+});
+
+
+
+let clockInfoMenu = clock_info.addInteractive(clockInfoItems, {
+ app: "bwclklite",
+ x : 0,
+ y: 135,
+ w: W,
+ h: H-135,
+ draw : (itm, info, options) => {
+ let hideClkInfo = info.text == null;
+
+ g.setColor(g.theme.fg);
+ g.fillRect(options.x, options.y, options.x+options.w, options.y+options.h);
+
+ g.setFontAlign(0,0);
+ g.setColor(g.theme.bg);
+
+ if (options.focus){
+ let y = hideClkInfo ? options.y+20 : options.y+2;
+ let h = hideClkInfo ? options.h-20 : options.h-2;
+ g.drawRect(options.x, y, options.x+options.w-2, y+h-1); // show if focused
+ g.drawRect(options.x+1, y+1, options.x+options.w-3, y+h-2); // show if focused
+ }
+
+ // In case we hide the clkinfo, we show the time again as the time should
+ // be drawn larger.
+ if(hideClkInfo){
+ drawTime();
+ return;
+ }
+
+ // Set text and font
+ let image = info.img;
+ let text = String(info.text);
+ if(text.split('\n').length > 1){
+ g.setFont("6x8"); //g.setMiniFont();
+ } else {
+ g.setFont("6x8:3"); //g.setSmallFont();
+ }
+
+ // Compute sizes
+ let strWidth = g.stringWidth(text);
+ let imgWidth = image == null ? 0 : 24;
+ let midx = options.x+options.w/2;
+
+ // Draw
+ if (image) {
+ let scale = imgWidth / image.width;
+ g.drawImage(image, midx-parseInt(imgWidth*1.3/2)-parseInt(strWidth/2), options.y+6, {scale: scale});
+ }
+ g.drawString(text, midx+parseInt(imgWidth*1.3/2), options.y+20);
+
+ // In case we are in focus and the focus box changes (fullscreen yes/no)
+ // we draw the time again. Otherwise it could happen that a while line is
+ // not cleared correctly.
+ if(options.focus) drawTime();
+ }
+});
+
+
+/************************************************
+ * Draw
+ */
+let draw = function() {
+ // Queue draw again
+ queueDraw();
+
+ // Draw clock
+ drawDate();
+ drawTime();
+ drawLock();
+ drawWidgets();
+};
+
+
+let drawDate = function() {
+ // Draw background
+ let y = getLineY();
+ g.reset().clearRect(0,0,W,y);
+
+ // Draw date
+ y = parseInt(y/2)+4;
+ y += isFullscreen() ? 0 : 8;
+ let date = new Date();
+ let dateStr = date.getDate();
+ dateStr = ("0" + dateStr).substr(-2);
+ g.setFont("6x8:4"); //g.setMediumFont(); // Needed to compute the width correctly
+ let dateW = g.stringWidth(dateStr);
+
+ g.setFont("6x8:3"); //g.setSmallFont();
+ let dayStr = locale.dow(date, true);
+ let monthStr = locale.month(date, 1);
+ let dayW = Math.max(g.stringWidth(dayStr), g.stringWidth(monthStr));
+ let fullDateW = dateW + 10 + dayW;
+
+ g.setFontAlign(-1,0);
+ g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
+ g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
+
+ g.setFont("6x8:4"); //g.setMediumFont();
+ g.setColor(g.theme.fg);
+ g.drawString(dateStr, W/2 - fullDateW / 2, y+2);
+};
+
+
+let drawTime = function() {
+ let hideClkInfo = clockInfoMenu.menuA == 0 && clockInfoMenu.menuB == 0;
+
+ // Draw background
+ let y1 = getLineY();
+ let y = y1;
+ let date = new Date();
+
+ let hours = String(date.getHours());
+ let minutes = date.getMinutes();
+ minutes = minutes < 10 ? String("0") + minutes : minutes;
+ let colon = settings.hideColon ? "" : ":";
+ let timeStr = hours + colon + minutes;
+
+ // Set y coordinates correctly
+ y += parseInt((H - y)/2) + 5;
+
+ if (hideClkInfo){
+ g.setFont("6x8:5"); //g.setLargeFont();
+ } else {
+ y -= 15;
+ g.setFont("6x8:4"); //g.setMediumFont();
+ }
+
+ // Clear region and draw time
+ g.setColor(g.theme.fg);
+ g.fillRect(0,y1,W,y+20 + (hideClkInfo ? 1 : 0) + (isFullscreen() ? 3 : 0));
+
+ g.setColor(g.theme.bg);
+ g.setFontAlign(0,0);
+ g.drawString(timeStr, W/2, y);
+};
+
+
+let drawLock = function() {
+ if(settings.showLock && Bangle.isLocked()){
+ g.setColor(g.theme.fg);
+ g.drawImage(imgLock(), W-16, 2);
+ }
+};
+
+
+let drawWidgets = function() {
+ if(isFullscreen()){
+ widget_utils.hide();
+ } else {
+ Bangle.drawWidgets();
+ }
+};
+
+
+/************************************************
+ * Listener
+ */
+// timeout used to update every minute
+let drawTimeout;
+
+// schedule a draw for the next minute
+let queueDraw = function() {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = setTimeout(function() {
+ drawTimeout = undefined;
+ draw();
+ }, 60000 - (Date.now() % 60000));
+};
+
+
+// Stop updates when LCD is off, restart when on
+let lcdListenerBw = function(on) {
+ if (on) {
+ draw(); // draw immediately, queue redraw
+ } else { // stop draw timer
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ }
+};
+Bangle.on('lcdPower', lcdListenerBw);
+
+let lockListenerBw = function(isLocked) {
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+
+ if(!isLocked && settings.screen.toLowerCase() == "dynamic"){
+ // If we have to show the widgets again, we load it from our
+ // cache and not through Bangle.loadWidgets as its much faster!
+ widget_utils.show();
+ }
+
+ draw();
+};
+Bangle.on('lock', lockListenerBw);
+
+let charging = function(charging){
+ // Jump to battery
+ clockInfoMenu.setItem(0, 2);
+ drawTime();
+};
+Bangle.on('charging', charging);
+
+let kill = function(){
+ clockInfoMenu.remove();
+ delete clockInfoMenu;
+};
+E.on("kill", kill);
+
+/************************************************
+ * Startup Clock
+ */
+
+// Show launcher when middle button pressed
+Bangle.setUI({
+ mode : "clock",
+ remove : function() {
+ // Called to unload all of the clock app
+ Bangle.removeListener('lcdPower', lcdListenerBw);
+ Bangle.removeListener('lock', lockListenerBw);
+ Bangle.removeListener('charging', charging);
+ if (drawTimeout) clearTimeout(drawTimeout);
+ drawTimeout = undefined;
+ // save settings
+ kill();
+ E.removeListener("kill", kill);
+ Bangle.removeListener('charging', charging);
+ widget_utils.show();
+ }
+});
+
+// Load widgets and draw clock the first time
+Bangle.loadWidgets();
+
+// Draw first time
+g.setColor(g.theme.fg).fillRect(0,135,W,H); // Otherwise this rect will wait for clock_info before updating
+draw();
+
+} // End of app scope
diff --git a/apps/bwclklite/app.png b/apps/bwclklite/app.png
new file mode 100644
index 000000000..5073f0ed0
Binary files /dev/null and b/apps/bwclklite/app.png differ
diff --git a/apps/bwclklite/metadata.json b/apps/bwclklite/metadata.json
new file mode 100644
index 000000000..bab852623
--- /dev/null
+++ b/apps/bwclklite/metadata.json
@@ -0,0 +1,43 @@
+{
+ "id": "bwclklite",
+ "name": "BW Clock Lite",
+ "version": "0.32",
+ "description": "A very minimalistic clock. This version of BW Clock is quicker at the cost of the custom font.",
+ "readme": "README.md",
+ "icon": "app.png",
+ "screenshots": [
+ {
+ "url": "screenshot.png"
+ },
+ {
+ "url": "screenshot_2.png"
+ },
+ {
+ "url": "screenshot_3.png"
+ }
+ ],
+ "type": "clock",
+ "tags": "clock,clkinfo",
+ "supports": [
+ "BANGLEJS2"
+ ],
+ "dependencies": {
+ "clock_info": "module"
+ },
+ "allow_emulator": true,
+ "storage": [
+ {
+ "name": "bwclklite.app.js",
+ "url": "app.js"
+ },
+ {
+ "name": "bwclklite.img",
+ "url": "app-icon.js",
+ "evaluate": true
+ },
+ {
+ "name": "bwclklite.settings.js",
+ "url": "settings.js"
+ }
+ ]
+}
diff --git a/apps/bwclklite/screenshot.png b/apps/bwclklite/screenshot.png
new file mode 100644
index 000000000..28983c9c4
Binary files /dev/null and b/apps/bwclklite/screenshot.png differ
diff --git a/apps/bwclklite/screenshot_2.png b/apps/bwclklite/screenshot_2.png
new file mode 100644
index 000000000..8d2f1717f
Binary files /dev/null and b/apps/bwclklite/screenshot_2.png differ
diff --git a/apps/bwclklite/screenshot_3.png b/apps/bwclklite/screenshot_3.png
new file mode 100644
index 000000000..573675d28
Binary files /dev/null and b/apps/bwclklite/screenshot_3.png differ
diff --git a/apps/bwclklite/settings.js b/apps/bwclklite/settings.js
new file mode 100644
index 000000000..2d3916a3d
--- /dev/null
+++ b/apps/bwclklite/settings.js
@@ -0,0 +1,50 @@
+(function(back) {
+ const SETTINGS_FILE = "bwclklite.setting.json";
+
+ // initialize with default settings...
+ const storage = require('Storage')
+ let settings = {
+ screen: "Normal",
+ showLock: true,
+ hideColon: false,
+ };
+ let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
+ for (const key in saved_settings) {
+ settings[key] = saved_settings[key]
+ }
+
+ function save() {
+ storage.write(SETTINGS_FILE, settings)
+ }
+
+ var screenOptions = ["Normal", "Dynamic", "Full"];
+ E.showMenu({
+ '': { 'title': 'BW Clock' },
+ '< Back': back,
+ 'Screen': {
+ value: 0 | screenOptions.indexOf(settings.screen),
+ min: 0, max: 2,
+ format: v => screenOptions[v],
+ onchange: v => {
+ settings.screen = screenOptions[v];
+ save();
+ },
+ },
+ 'Show Lock': {
+ value: settings.showLock,
+ format: () => (settings.showLock ? 'Yes' : 'No'),
+ onchange: () => {
+ 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/clockcal/ChangeLog b/apps/clockcal/ChangeLog
index 4c8c13366..5657bf26d 100644
--- a/apps/clockcal/ChangeLog
+++ b/apps/clockcal/ChangeLog
@@ -5,3 +5,4 @@
0.05: Improved colors (connected vs disconnected)
0.06: Tell clock widgets to hide.
0.07: Convert Yes/No On/Off in settings to checkboxes
+0.08: Fixed typo in settings.js for DRAGDOWN to make option work
\ No newline at end of file
diff --git a/apps/clockcal/metadata.json b/apps/clockcal/metadata.json
index 03f5c3df2..998115827 100644
--- a/apps/clockcal/metadata.json
+++ b/apps/clockcal/metadata.json
@@ -1,7 +1,7 @@
{
"id": "clockcal",
"name": "Clock & Calendar",
- "version": "0.07",
+ "version": "0.08",
"description": "Clock with Calendar",
"readme":"README.md",
"icon": "app.png",
diff --git a/apps/clockcal/settings.js b/apps/clockcal/settings.js
index 4d8be6fbd..a406f3cf7 100644
--- a/apps/clockcal/settings.js
+++ b/apps/clockcal/settings.js
@@ -93,7 +93,7 @@
value: actions.indexOf(settings.DRAGDOWN),
format: v => actions[v],
onchange: v => {
- settings.DRGDOWN = actions[v];
+ settings.DRAGDOWN = actions[v];
writeSettings();
}
},
diff --git a/apps/draguboard/ChangeLog b/apps/draguboard/ChangeLog
new file mode 100644
index 000000000..a228aab54
--- /dev/null
+++ b/apps/draguboard/ChangeLog
@@ -0,0 +1 @@
+0.01: New App based on dragboard, but with a U shaped drag area
diff --git a/apps/draguboard/README.md b/apps/draguboard/README.md
new file mode 100644
index 000000000..2386c7658
--- /dev/null
+++ b/apps/draguboard/README.md
@@ -0,0 +1,8 @@
+Swipe along the drag bars and release to select a letter, number or punctuation.
+
+Tap on left for backspace or right for space.
+
+Settings:
+- ABC Color: color of the characters row
+- Num Color: color of the digits and symbols row
+- Highlight Color: color of the currently shown character
diff --git a/apps/draguboard/app.png b/apps/draguboard/app.png
new file mode 100644
index 000000000..ae7262b47
Binary files /dev/null and b/apps/draguboard/app.png differ
diff --git a/apps/draguboard/lib.js b/apps/draguboard/lib.js
new file mode 100644
index 000000000..258f8b02d
--- /dev/null
+++ b/apps/draguboard/lib.js
@@ -0,0 +1,156 @@
+exports.input = function(options) {
+ options = options||{};
+ var text = options.text;
+ if ("string"!=typeof text) text="";
+ let settings = require('Storage').readJSON('draguboard.json',1)||{};
+
+ var R;
+ const paramToColor = (param) => g.toColor(`#${settings[param].toString(16).padStart(3,0)}`);
+ var BGCOLOR = g.theme.bg;
+ var HLCOLOR = settings.Highlight ? paramToColor("Highlight") : g.theme.fg;
+ var ABCCOLOR = settings.ABC ? paramToColor("ABC") : g.toColor(1,0,0);//'#FF0000';
+ var NUMCOLOR = settings.Num ? paramToColor("Num") : g.toColor(0,1,0);//'#00FF00';
+ var BIGFONT = '6x8:3';
+ var SMALLFONT = '6x8:1';
+
+ var LEFT = "IJKLMNOPQ";
+ var MIDDLE = "ABCDEFGH";
+ var RIGHT = "RSTUVWXYZ";
+
+ var NUM = ' 1234567890!?,.-@';
+ var rectHeight = 40;
+ var vLength = LEFT.length;
+ var MIDPADDING;
+ var NUMPADDING;
+ var showCharY;
+ var middleWidth;
+ var middleStart;
+ var topStart;
+
+ function drawAbcRow() {
+ g.clear();
+ try { // Draw widgets if they are present in the current app.
+ if (WIDGETS) Bangle.drawWidgets();
+ } catch (_) {}
+ g.setColor(ABCCOLOR);
+ g.setFont('6x8:2x1');
+ g.setFontAlign(-1, -1, 0);
+ g.drawString(RIGHT.split("").join("\n\n"), R.x2-28, topStart);
+ g.drawString(LEFT.split("").join("\n\n"), R.x+22, topStart);
+ g.setFont('6x8:1x2');
+ var spaced = MIDDLE.split("").join(" ");
+ middleWidth = g.stringWidth(spaced);
+ middleStart = (R.x2-middleWidth)/2;
+ g.drawString(spaced, (R.x2-middleWidth)/2, (R.y2)/2);
+ g.fillRect(MIDPADDING, (R.y2)-26, (R.x2-MIDPADDING), (R.y2));
+ // Draw left and right drag rectangles
+ g.fillRect(R.x, R.y, 12, R.y2);
+ g.fillRect(R.x2, R.y, R.x2-12, R.y2);
+ }
+
+ function drawNumRow() {
+ g.setFont('6x8:1x2');
+ g.setColor(NUMCOLOR);
+ NUMPADDING = (R.x2-g.stringWidth(NUM))/2;
+ g.setFontAlign(-1, -1, 0);
+ g.drawString(NUM, NUMPADDING, (R.y2)/4);
+ g.drawString("<-", NUMPADDING+10, showCharY+5);
+ g.drawString("->", R.x2-(NUMPADDING+20), showCharY+5);
+
+ g.fillRect(NUMPADDING, (R.y2)-rectHeight*4/3, (R.x2)-NUMPADDING, (R.y2)-rectHeight*2/3);
+ }
+
+ function updateTopString() {
+ g.setFont(SMALLFONT);
+ g.setColor(BGCOLOR);
+ g.fillRect(R.x,R.y,R.x2,R.y+9);
+ var rectLen = text.length<27? text.length*6:27*6;
+ g.setColor(0.7,0,0);
+ //draw cursor at end of text
+ g.fillRect(R.x+rectLen+5,R.y,R.x+rectLen+10,R.y+9);
+ g.setColor(HLCOLOR);
+ g.setFontAlign(-1, -1, 0);
+ g.drawString(text.length<=27? text : '<- '+text.substr(-24,24), R.x+5, R.y+1);
+ }
+
+ function showChars(chars) {
+ "ram";
+
+ // clear large character
+ g.setColor(BGCOLOR);
+ g.fillRect(R.x+65,showCharY,R.x2-65,showCharY+28);
+
+ // show new large character
+ g.setColor(HLCOLOR);
+ g.setFont(BIGFONT);
+ g.setFontAlign(-1, -1, 0);
+ g.drawString(chars, (R.x2 - g.stringWidth(chars))/2, showCharY+4);
+ }
+
+ var charPos;
+ var char;
+ var prevChar;
+
+ function moveCharPos(list, select, posPixels) {
+ charPos = Math.min(list.length-1, Math.max(0, Math.floor(posPixels)));
+ char = list.charAt(charPos);
+
+ if (char != prevChar) showChars(char);
+ prevChar = char;
+
+ if (select) {
+ text += char;
+ updateTopString();
+ }
+ }
+
+ return new Promise((resolve,reject) => {
+ // Interpret touch input
+ Bangle.setUI({
+ mode: 'custom',
+ back: ()=>{
+ Bangle.setUI();
+ g.clearRect(Bangle.appRect);
+ resolve(text);
+ },
+ drag: function(event) {
+ "ram";
+
+ // drag on middle bottom rectangle
+ if (event.x > MIDPADDING - 2 && event.x < (R.x2-MIDPADDING + 2) && event.y >= ( (R.y2) - 12 )) {
+ moveCharPos(MIDDLE, event.b == 0, (event.x-middleStart)/(middleWidth/MIDDLE.length));
+ }
+ // drag on left or right rectangle
+ else if (event.y > R.y && (event.x < MIDPADDING-2 || event.x > (R.x2-MIDPADDING + 2))) {
+ moveCharPos(event.x ( (R.y2) - 52 ))) {
+ moveCharPos(NUM, event.b == 0, (event.x-NUMPADDING)/6);
+ }
+ // Make a space or backspace by tapping right or left on screen above green rectangle
+ else if (event.y > R.y && event.b == 0) {
+ if (event.x < (R.x2)/2) {
+ showChars('<-');
+ text = text.slice(0, -1);
+ } else {
+ //show space sign
+ showChars('->');
+ text += ' ';
+ }
+ prevChar = null;
+ updateTopString();
+ }
+ }
+ });
+
+ R = Bangle.appRect;
+ MIDPADDING = R.x + 35;
+ showCharY = (R.y2)/3;
+ topStart = R.y+12;
+
+ drawAbcRow();
+ drawNumRow();
+ updateTopString();
+ });
+};
diff --git a/apps/draguboard/metadata.json b/apps/draguboard/metadata.json
new file mode 100644
index 000000000..926e36807
--- /dev/null
+++ b/apps/draguboard/metadata.json
@@ -0,0 +1,15 @@
+{ "id": "draguboard",
+ "name": "DragUboard",
+ "version":"0.01",
+ "description": "A library for text input via swiping U-shaped keyboard.",
+ "icon": "app.png",
+ "type":"textinput",
+ "tags": "keyboard",
+ "supports" : ["BANGLEJS2"],
+ "screenshots": [{"url":"screenshot.png"}],
+ "readme": "README.md",
+ "storage": [
+ {"name":"textinput","url":"lib.js"},
+ {"name":"draguboard.settings.js","url":"settings.js"}
+ ]
+}
diff --git a/apps/draguboard/screenshot.png b/apps/draguboard/screenshot.png
new file mode 100644
index 000000000..f2cb91717
Binary files /dev/null and b/apps/draguboard/screenshot.png differ
diff --git a/apps/draguboard/settings.js b/apps/draguboard/settings.js
new file mode 100644
index 000000000..c94ebee70
--- /dev/null
+++ b/apps/draguboard/settings.js
@@ -0,0 +1,44 @@
+(function(back) {
+ let settings = require('Storage').readJSON('draguboard.json',1)||{};
+ const colors = {
+ 4095: /*LANG*/"White",
+ 4080: /*LANG*/"Yellow",
+ 3840: /*LANG*/"Red",
+ 3855: /*LANG*/"Magenta",
+ 255: /*LANG*/"Cyan",
+ 240: /*LANG*/"Green",
+ 15: /*LANG*/"Blue",
+ 0: /*LANG*/"Black",
+ '-1': /*LANG*/"Default"
+ };
+
+ const save = () => require('Storage').write('draguboard.json', settings);
+ function colorMenu(key) {
+ let menu = {'': {title: key}, '< Back': () => E.showMenu(appMenu)};
+ Object.keys(colors).forEach(color => {
+ var label = colors[color];
+ menu[label] = {
+ value: settings[key] == color,
+ onchange: () => {
+ if (color >= 0) {
+ settings[key] = color;
+ } else {
+ delete settings[key];
+ }
+ save();
+ setTimeout(E.showMenu, 10, appMenu);
+ }
+ };
+ });
+ return menu;
+ }
+
+ const appMenu = {
+ '': {title: 'draguboard'}, '< Back': back,
+ /*LANG*/'ABC Color': () => E.showMenu(colorMenu("ABC")),
+ /*LANG*/'Num Color': () => E.showMenu(colorMenu("Num")),
+ /*LANG*/'Highlight Color': () => E.showMenu(colorMenu("Highlight"))
+ };
+
+ E.showMenu(appMenu);
+});
\ No newline at end of file
diff --git a/apps/fwupdate/ChangeLog b/apps/fwupdate/ChangeLog
index ea0b48eb9..a9673f6de 100644
--- a/apps/fwupdate/ChangeLog
+++ b/apps/fwupdate/ChangeLog
@@ -6,3 +6,4 @@
Add CRC checks for common bootloaders that we know don't work
0.04: Include a precompiled bootloader for easy bootloader updates
0.05: Rename Bootloader->DFU and add explanation to avoid confusion with Bootloader app
+0.06: Lower chunk size to 1024 (from 2048) to make firmware updates more reliable
diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html
index 31eb4a256..a648030c1 100644
--- a/apps/fwupdate/custom.html
+++ b/apps/fwupdate/custom.html
@@ -98,6 +98,8 @@ function onInit(device) {
if (crc==4056371285) version = "2v13";
if (crc==1038322422) version = "2v14";
if (crc==2560806221) version = "2v15";
+ if (crc==2886730689) version = "2v16";
+ if (crc==156320890) version = "2v17";
if (!ok) {
version += `(⚠ update required)`;
}
@@ -317,7 +319,7 @@ function createJS_app(binary, startAddress, endAddress) {
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("DFU 2v10.236 needs update"); load();}\n`;
hexJS += '\x10var s = require("Storage");\n';
hexJS += '\x10s.erase(".firmware");\n';
- var CHUNKSIZE = 2048;
+ var CHUNKSIZE = 1024;
for (var i=0;iCHUNKSIZE) l=CHUNKSIZE;
diff --git a/apps/fwupdate/metadata.json b/apps/fwupdate/metadata.json
index 372f6850c..e3294f316 100644
--- a/apps/fwupdate/metadata.json
+++ b/apps/fwupdate/metadata.json
@@ -1,7 +1,7 @@
{
"id": "fwupdate",
"name": "Firmware Update",
- "version": "0.05",
+ "version": "0.06",
"description": "Uploads new Espruino firmwares to Bangle.js 2",
"icon": "app.png",
"type": "RAM",
diff --git a/apps/iconlaunch/ChangeLog b/apps/iconlaunch/ChangeLog
index 6f0e8194c..8bad496bf 100644
--- a/apps/iconlaunch/ChangeLog
+++ b/apps/iconlaunch/ChangeLog
@@ -21,3 +21,4 @@
0.15: Ensure that we hide widgets if in fullscreen mode
(So that widgets are still hidden if launcher is fast-loaded)
0.16: Use firmware provided E.showScroller method
+0.17: fix fullscreen with oneClickExit
diff --git a/apps/iconlaunch/app.js b/apps/iconlaunch/app.js
index 8d155c73e..9f8cedb0f 100644
--- a/apps/iconlaunch/app.js
+++ b/apps/iconlaunch/app.js
@@ -9,6 +9,7 @@
timeOut:"Off"
}, s.readJSON("iconlaunch.json", true) || {});
+
if (!settings.fullscreen) {
Bangle.loadWidgets();
Bangle.drawWidgets();
@@ -103,15 +104,12 @@
};
const itemsN = Math.ceil(launchCache.apps.length / appsN);
- let back = ()=>{};
- if (settings.oneClickExit) back = Bangle.showClock;
-
+ let idWatch = null;
let options = {
h: itemSize,
c: itemsN,
draw: drawItem,
select: selectItem,
- back: back,
remove: function() {
if (timeout) clearTimeout(timeout);
Bangle.removeListener("drag", updateTimeout);
@@ -120,8 +118,26 @@
if (settings.fullscreen) { // for fast-load, if we hid widgets then we should show them again
require("widget_utils").show();
}
- }
+ if(idWatch) clearWatch(idWatch);
+ },
+ btn:Bangle.showClock
};
+
+ //work both the fullscreen and the oneClickExit
+ if( settings.fullscreen && settings.oneClickExit)
+ {
+ idWatch=setWatch(function(e) {
+ Bangle.showClock();
+ }, BTN, {repeat:false, edge:'rising' });
+
+ }
+ else if( settings.oneClickExit )
+ {
+ options.back=Bangle.showClock;
+ }
+
+
+
let scroller = E.showScroller(options);
@@ -141,4 +157,4 @@
Bangle.on("touch", updateTimeout);
updateTimeout();
-}
+}
\ No newline at end of file
diff --git a/apps/iconlaunch/metadata.json b/apps/iconlaunch/metadata.json
index 435a29b39..35a7907bd 100644
--- a/apps/iconlaunch/metadata.json
+++ b/apps/iconlaunch/metadata.json
@@ -2,7 +2,7 @@
"id": "iconlaunch",
"name": "Icon Launcher",
"shortName" : "Icon launcher",
- "version": "0.16",
+ "version": "0.17",
"icon": "app.png",
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
"tags": "tool,system,launcher",
diff --git a/apps/messages_light/ChangeLog b/apps/messages_light/ChangeLog
index 328e2a120..23d9ba053 100644
--- a/apps/messages_light/ChangeLog
+++ b/apps/messages_light/ChangeLog
@@ -4,4 +4,6 @@
settings now points to message settings
implemented use of the "messageicons" library
removed lib no longer used
-1.3: icon changed
\ No newline at end of file
+1.3: icon changed
+1.4: new management of events implemented; removed code no longer used (from now the music will be managed by the Messagesgui app)
+1.5: Fix graphic bug; View via popup while there are other open apps
\ No newline at end of file
diff --git a/apps/messages_light/README.md b/apps/messages_light/README.md
index 00fe39bd0..2bc162cb8 100644
--- a/apps/messages_light/README.md
+++ b/apps/messages_light/README.md
@@ -5,7 +5,17 @@ This app handles the display of messages and message notifications.
It is a GUI replacement for the `messages` apps.
+To work, you must install:
+- Messages
+- Messages UI
+- Messages Light (obviously)
+
+The Messages UI is recalled for the management of the "Music" notification (up to implementing a dedicated app)
+
+
## Creator
Rarder44
+Thanks to @halemmerich for having "reviewed" the code. I applied some of your changes.
+
diff --git a/apps/messages_light/messages_light.app.js b/apps/messages_light/messages_light.app.js
index 5d5363d38..40f94dd0f 100644
--- a/apps/messages_light/messages_light.app.js
+++ b/apps/messages_light/messages_light.app.js
@@ -10,35 +10,39 @@
}
*/
+
+
+
let LOG=function(){
//print.apply(null, arguments);
}
-
-
-let settings= (()=>{
- let tmp={};
- tmp.NewEventFileName="messages_light.NewEvent.json";
-
- tmp.fontSmall = "6x8";
- tmp.fontMedium = g.getFonts().includes("Vector")?"Vector:16":"6x8:2";
- tmp.fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
- tmp.fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
+let settings= {
+ NewEventFileName:"messages_light.NewEvent.json",
+ fontSmall : "6x8",
+ fontMedium : "Vector:16",
+ fontBig : "Vector:20",
+ fontLarge : "Vector:30",
+ colHeadBg : g.theme.dark ? "#141":"#4f4",
- tmp.colHeadBg = g.theme.dark ? "#141":"#4f4";
- tmp.colBg = g.theme.dark ? "#000":"#fff";
- tmp.colLock = g.theme.dark ? "#ff0000":"#ff0000";
+ colBg : g.theme.dark ? "#000":"#fff",
+ colLock : g.theme.dark ? "#ff0000":"#ff0000",
+
+ quiet:!!((require('Storage').readJSON('setting.json', 1) || {}).quiet),
+ timeOut:(require('Storage').readJSON("messages_light.settings.json", true) || {}).timeOut || "Off",
+};
+
+
- tmp.quiet=((require('Storage').readJSON('setting.json', 1) || {}).quiet)
- return tmp;
-})();
let EventQueue=[]; //in posizione 0, c'è quello attualmente visualizzato
let callInProgress=false;
+let justOpened=true;
+
//TODO: RICORDARSI DI FARE IL DELETE
@@ -46,68 +50,73 @@ var manageEvent = function(event) {
event.new=true;
-
LOG("manageEvent");
- if( event.id=="call")
- {
- showCall(event);
- return;
+ LOG(event);
+
+ if( event.id=="call"){
+ showCall(event);
}
- switch(event.t)
- {
- case "add":
- EventQueue.unshift(event);
+ else if( event.id=="music"){
+ //la musica non la gestisco più ( uso l'app standard o un altra app)
+ }
+ else{
- if(!callInProgress)
- showMessage(event);
- break;
-
- case "modify":
- //cerco l'evento nella lista, se lo trovo, lo modifico, altrimenti lo pusho
- let find=false;
- EventQueue.forEach(element => {
- if(element.id == event.id)
- {
- find=true;
- Object.assign(element,event);
- }
- });
- if(!find) //se non l'ho trovato, lo aggiungo in fondo
+ //-----------------
+ //notification
+ //-----------------
+ if(event.t=="add"){
EventQueue.unshift(event);
-
- if(!callInProgress)
- showMessage(event);
- break;
-
- case "remove":
-
- //se non c'è niente nella queue e non c'è una chiamata in corso
- if( EventQueue.length==0 && !callInProgress)
- next();
-
- //se l'id è uguale a quello attualmente visualizzato ( e non siamo in chiamata )
- if(!callInProgress && EventQueue[0] !== undefined && EventQueue[0].id == event.id)
- next(); //passo al messaggio successivo ( per la rimozione ci penserà la next )
-
- else{
- //altrimenti rimuovo tutti gli elementi con quell'id( creando un nuovo array )
- let newEventQueue=[];
+
+ if(!callInProgress)
+ showMessage(event);
+ }
+ else if(event.t=="modify"){
+ //cerco l'evento nella lista, se lo trovo, lo modifico, altrimenti lo pusho
+ let find=false;
EventQueue.forEach(element => {
- if(element.id != event.id)
- newEventQueue.push(element);
+ if(element.id == event.id)
+ {
+ find=true;
+ Object.assign(element,event);
+ }
});
- EventQueue=newEventQueue;
- }
-
-
+ if(!find) //se non l'ho trovato, lo aggiungo in fondo
+ EventQueue.unshift(event);
+
+ if(!callInProgress)
+ showMessage(event);
+ }
+ else if(event.t=="remove"){
+ //se non c'è niente nella queue e non c'è una chiamata in corso
+ if( EventQueue.length==0 && !callInProgress)
+ next();
+
+ //se l'id è uguale a quello attualmente visualizzato ( e non siamo in chiamata )
+ if(!callInProgress && EventQueue[0] !== undefined && EventQueue[0].id == event.id)
+ next(); //passo al messaggio successivo ( per la rimozione ci penserà la next )
+
+ else{
+ //altrimenti rimuovo tutti gli elementi con quell'id( creando un nuovo array )
+ let newEventQueue=[];
+ EventQueue.forEach(element => {
+ if(element.id != event.id)
+ newEventQueue.push(element);
+ });
+
+ //non sovrascrivo, cosi uso lo stesso oggetto in memoria e dovrei avere meno problemi di memory leak
+ EventQueue.length=0;
+ newEventQueue.forEach(element => {
+ EventQueue.push(element);
+ });
+ }
+ }
+ //-----------------
+ //notification
+ //-----------------
+
-
- break;
- case "musicstate":
- case "musicinfo":
-
- break;
}
+
};
@@ -118,6 +127,10 @@ var manageEvent = function(event) {
let showMessage = function(msg){
LOG("showMessage");
LOG(msg);
+
+ updateTimeout();
+
+
g.setBgColor(settings.colBg);
@@ -208,7 +221,7 @@ let showCall = function(msg)
}
callInProgress=true;
-
+ updateTimeout();
//se è una chiamata ( o una nuova chiamata, diversa dalla precedente )
@@ -262,7 +275,6 @@ let showCall = function(msg)
-
let next=function(){
LOG("next");
@@ -274,9 +286,10 @@ let next=function(){
EventQueue.shift(); //passa al messaggio successivo, se presente - tolgo il primo
callInProgress=false;
+ LOG(EventQueue.length);
if( EventQueue.length == 0)
{
- LOG("no element in queue - closing")
+ LOG("no element in queue - closing");
setTimeout(_ => load());
return;
}
@@ -288,31 +301,12 @@ let next=function(){
-
-
-
-
-
-
-
-
-let showMapMessage=function(msg) {
-
- g.clearRect(Bangle.appRect);
- PrintMessageStrings({body:"Not implemented!"});
-
-}
-
-
-
-
-
-let CallBuzzTimer=null;
+let CallBuzzTimer=undefined;
let StopBuzzCall=function()
{
if (CallBuzzTimer){
clearInterval(CallBuzzTimer);
- CallBuzzTimer=null;
+ CallBuzzTimer=undefined;
}
}
let DrawTriangleUp=function()
@@ -443,13 +437,16 @@ let PrintMessageStrings=function(msg)
let doubleTapUnlock=function(data) {
+ updateTimeout();
if( data.double) //solo se in double
{
Bangle.setLocked(false);
Bangle.setLCDPower(1);
}
}
-let toushScroll=function(button, xy) {
+let toushScroll=function(_, xy) {
+ updateTimeout();
+
let height=176; //g.getHeight(); -> 176 B2
height/=2;
@@ -464,6 +461,24 @@ let toushScroll=function(button, xy) {
}
+
+let timeout;
+const updateTimeout = function(){
+if (settings.timeOut!="Off"){
+ removeTimeout();
+ if( callInProgress) return; //c'è una chiamata in corso -> no timeout
+ if( music!=undefined && EventQueue.length==0 ) return; //ho aperto l'interfaccia della musica e non ho messaggi davanti -> no timeout
+
+
+ let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
+ timeout = setTimeout(next,time*1000); //next or Bangle.showClock/load()???
+ }
+};
+const removeTimeout=function(){
+ if (timeout) clearTimeout(timeout);
+}
+
+
let main = function(){
LOG("Main");
@@ -478,16 +493,18 @@ let main = function(){
Bangle.on('tap', doubleTapUnlock);
Bangle.on('touch', toushScroll);
- //quando apro quest'app, do per scontato che c'è un messaggio da leggere posto in un file particolare ( NewMessage.json )
+ //quando apro quest'app, do per scontato che c'è un messaggio da leggere posto in un file particolare ( messages_light.NewEvent.json )
let eventToShow = require('Storage').readJSON(settings.NewEventFileName, true);
require("Storage").erase(settings.NewEventFileName)
if( eventToShow!==undefined)
manageEvent(eventToShow);
else
{
- LOG("file not found!");
- setTimeout(_ => load(), 0);
+ LOG("file event not found! -> ?? open debug text");
+ setTimeout(_=>{ GB({"t":"notify","id":15754117198411,"src":"Hangouts","title":"A Name","body":"Debug notification \nmessage contents demo demo demo demo"}) },0);
}
+ justOpened=false;
+
};
diff --git a/apps/messages_light/messages_light.boot.js b/apps/messages_light/messages_light.boot.js
index 741d08b96..f28e50661 100644
--- a/apps/messages_light/messages_light.boot.js
+++ b/apps/messages_light/messages_light.boot.js
@@ -1,33 +1,13 @@
-/*
-//OLD CODE -> backup purpose
-
-let messageBootManager=function(type,event){
- //se l'app non è aperta
- if ("undefined"==typeof manageEvent)
- {
- if(event.t=="remove") return; //l'app non è aperta, non c'è nessun messaggio da rimuovere dalla queue -> non lancio l'app
-
- //la apro
- require("Storage").writeJSON("messages_light.NewEvent.json",{"event":event,"type":type});
- load("messages_light.app.js");
- }
- else
- {
- //altrimenti gli dico di gestire il messaggio
- manageEvent(type,event);
- }
-}
-Bangle.on("message", messageBootManager);
-Bangle.on("call", messageBootManager);*/
-
-
-
+//OLD Code
//override require to filter require("message")
-global.require_real=global.require;
+/*global.require_real=global.require;
global.require = (_require => file => {
- if (file==="messages") file = "messagesProxy";
- //else if (file==="messages_REAL") file = "messages"; //backdoor to real message
-
+ if (file==="messages") file = "messagesProxy";
return _require(file);
-})(require);
-
+})(require);*/
+
+//the file on the device is called "boot_messages_light.boot.js"
+//it's NOT an error!
+//it's for the boot order
+
+Bangle.on("message", (type, msg) => require("messages_light.listener.js").listener(type, msg));
diff --git a/apps/messages_light/messages_light.listener.js b/apps/messages_light/messages_light.listener.js
new file mode 100644
index 000000000..2525a52bd
--- /dev/null
+++ b/apps/messages_light/messages_light.listener.js
@@ -0,0 +1,122 @@
+
+let overlayTimeout=undefined;
+exports.listener = function(type, event) {
+
+
+ //salva gli eventi che arrivano su file
+ /* events=require("Storage").readJSON("events_log",true) || [];
+ events.push ( event)
+ require("Storage").writeJSON("events_log",events);
+ */
+ //if (event.handled) return; // already handled/app open
+ if( type=="clearAll" || type=="music" || event.id=="music") return; //lo lascio gestire a qualcun altro
+
+ //se arrivo qua gestisco io
+ //non mi preoccupo di salvare ( a meno di problemi a mantenere tanti messaggi in queue nella ram...)
+ event.handled=true;
+
+
+ if( Bangle.CLOCK || global.__FILE__ === undefined || global.__FILE__ === ".bootcde" || global.__FILE__.startsWith("messages_light."))
+ {
+ //se non ci sono app aperte ( clock oppure c'è messages_light)
+ //continuo con la visualizzazione dell messaggio
+
+ let callApp;
+ //se l'app non è aperta
+ if ("undefined"==typeof manageEvent)
+ {
+ if(event.t=="remove") return; //l'app non è aperta, non c'è nessun messaggio da rimuovere dalla queue -> non lancio l'app
+
+ //chiamo la load dell'app
+ callApp=function(event){
+ require("Storage").writeJSON("messages_light.NewEvent.json",event);
+ load("messages_light.app.js");
+ }
+ }
+ else
+ {
+ //dico all'app di gestire l'evento
+ callApp=function(event){
+ manageEvent(event);
+ }
+ }
+
+
+ callApp(event);
+
+
+ }
+ else{
+ //TODO: BHOO!!!
+ //vibro e basta?
+ //faccio comparire un overlay veloce?
+ //uso l'overlay sempre? ( gestione di tutti gli eventi smadonnosa... )
+ //salvo lo stato dell'app attuale( NON SO COME ), lancio la mia app e alla chiusura torno allo stato precedente?
+
+ console.log(event);
+ let ovr=undefined;
+ let palette;
+ let timeout;
+
+ if(event.id=="call" && event.t!="remove")
+ {
+ let count=3;
+ let idInter= setInterval(()=>{
+ if(--count<=0)
+ clearInterval(idInter);
+
+ Bangle.buzz(100,1);
+ },200);
+
+ ovr = Graphics.createArrayBuffer(136,136,2,{msb:true});
+ ovr.setColor(1).fillRect({x:0,y:0,w:135,h:135,r:8});
+ ovr.setColor(2).setFont("Vector:30").setFontAlign(0,0).drawString("Call",68,20);
+ var lines=ovr.wrapString(event.title,136);
+ for(let i=0;i< lines.length;i++)
+ ovr.setColor(2).setFont("Vector:20").setFontAlign(0,0).drawString(lines[i],68,50+i*15);
+
+ palette=[0,g.toColor("#141"),g.toColor("#fff"),g.toColor("#FFF")];
+ timeout=4000;
+ }
+ else if(event.t=="add" || event.t=="modify")
+ {
+ Bangle.buzz();
+ ovr = Graphics.createArrayBuffer(136,136,2,{msb:true});
+ ovr.setColor(1).fillRect({x:0,y:0,w:135,h:135,r:8});
+ ovr.setColor(2).setFont("Vector:20").setFontAlign(0,0).drawString(event.src,68,15);
+ ovr.setColor(2).setFont("Vector:15").setFontAlign(0,0).drawString(event.title,68,35);
+ var lines=ovr.wrapString(event.body,136);
+ for(let i=0;i< lines.length;i++)
+ ovr.setColor(2).setFont("Vector:15").setFontAlign(0,0).drawString(lines[i],68,60+i*15);
+
+
+ palette=[0,g.toColor("#09c"),g.toColor("#fff"),g.toColor("#FFF")];
+ timeout=5000;
+ }
+
+
+ if(ovr===undefined)
+ return;
+
+
+ Bangle.setLCDPower(true);
+
+ Bangle.setLCDOverlay({
+ width:ovr.getWidth(), height:ovr.getHeight(),
+ bpp:2, transparent:0,
+ palette:new Uint16Array(palette),
+ buffer:ovr.buffer
+ },20,20);
+
+ Bangle.setLCDPower(true);
+
+ if(overlayTimeout) clearTimeout(overlayTimeout);
+ overlayTimeout=setTimeout(()=>{
+ Bangle.setLCDOverlay();
+ overlayTimeout=undefined;
+ },timeout);
+ }
+
+}
+
+
diff --git a/apps/messages_light/messages_light.messagesProxy.js b/apps/messages_light/messages_light.messagesProxy.js
deleted file mode 100644
index 723397057..000000000
--- a/apps/messages_light/messages_light.messagesProxy.js
+++ /dev/null
@@ -1,30 +0,0 @@
-
-//gestisco il messaggio a modo mio
-exports.pushMessage = function(event) {
-
- //TODO: now i can't handle the music, so i call the real message app
- if( event.id=="music") return require_real("messages").pushMessage(event);
-
- //se l'app non è aperta
- if ("undefined"==typeof manageEvent)
- {
- if(event.t=="remove") return; //l'app non è aperta, non c'è nessun messaggio da rimuovere dalla queue -> non lancio l'app
-
- //la apro
- require_real("Storage").writeJSON("messages_light.NewEvent.json",event);
- load("messages_light.app.js");
- }
- else
- {
- //altrimenti gli dico di gestire il messaggio
- manageEvent(event);
- }
-}
-
-
-//Call original message library
-exports.clearAll = function() { return require_real("messages").clearAll()}
-exports.getMessages = function() { return require_real("messages").getMessages()}
-exports.status = function() { return require_real("messages").status()}
-exports.buzz = function() { return require_real("messages").buzz(msgSrc)}
-exports.stopBuzz = function() { return require_real("messages").stopBuzz()}
\ No newline at end of file
diff --git a/apps/messages_light/messages_light.settings.js b/apps/messages_light/messages_light.settings.js
index b7197c70a..cd813d928 100644
--- a/apps/messages_light/messages_light.settings.js
+++ b/apps/messages_light/messages_light.settings.js
@@ -1 +1,27 @@
-eval(require("Storage").read("messages.settings.js"));
+(function(back) {
+ const SETTINGS_FILE_NAME="messages_light.settings.json";
+ let settings = function() {
+ let settings = require('Storage').readJSON(SETTINGS_FILE_NAME, true) || {};
+ return settings;
+ }
+ function updateSetting(setting, value) {
+ let settings = require('Storage').readJSON(SETTINGS_FILE_NAME, true) || {};
+ settings[setting] = value;
+ require('Storage').writeJSON(SETTINGS_FILE_NAME, settings);
+ }
+ const timeOutChoices = [/*LANG*/"Off", "10s", "15s", "20s", "30s"];
+ var mainmenu = {
+ "" : { "title" : /*LANG*/"Messages Light" },
+ "< Back" : back,
+ /*LANG*/'Time Out': {
+ value: timeOutChoices.indexOf(settings.timeOut),
+ min: 0, max: timeOutChoices.length-1,
+ format: v => timeOutChoices[v],
+ onchange: m => {
+ updateSetting("timeOut", timeOutChoices[m]);
+ }
+ },
+ };
+ E.showMenu(mainmenu);
+ });
+
\ No newline at end of file
diff --git a/apps/messages_light/metadata.json b/apps/messages_light/metadata.json
index 3515a75c2..eeab7d9f4 100644
--- a/apps/messages_light/metadata.json
+++ b/apps/messages_light/metadata.json
@@ -1,7 +1,7 @@
{
"id": "messages_light",
"name": "Messages Light",
- "version": "1.3",
+ "version": "1.5",
"description": "A light implementation of messages App (display notifications from iOS and Gadgetbridge/Android)",
"icon": "app.png",
"type": "app",
@@ -13,8 +13,8 @@
{"name":"messages_light.app.js","url":"messages_light.app.js"},
{"name":"messages_light.settings.js","url":"messages_light.settings.js"},
{"name":"messages_light.img","url":"app-icon.js","evaluate":true},
- {"name":"messagesProxy","url":"messages_light.messagesProxy.js"},
- {"name":"messages_light.boot.js","url":"messages_light.boot.js"}
+ {"name":"boot_messages_light.boot.js","url":"messages_light.boot.js"},
+ {"name":"messages_light.listener.js","url":"messages_light.listener.js"}
],
"data": [{"name":"messages_light.settings.json"},{"name":"messages_light.NewMessage.json"}],
"screenshots": [{"url":"screenshot-notify.png"} ,{"url":"screenshot-long-text1.png"},{"url":"screenshot-long-text2.png"}, {"url":"screenshot-call.png"} ]
diff --git a/apps/messagesoverlay/ChangeLog b/apps/messagesoverlay/ChangeLog
index da98bfbce..0a2cf27b0 100644
--- a/apps/messagesoverlay/ChangeLog
+++ b/apps/messagesoverlay/ChangeLog
@@ -1 +1,3 @@
-0.01: Initial fork from messages_light
\ No newline at end of file
+0.01: Initial fork from messages_light
+0.02: Fix touch/drag/swipe handlers not being restored correctly if a message is removed
+0.03: Scroll six lines per swipe, leaving the previous top/bottom row visible.
diff --git a/apps/messagesoverlay/app-icon.png b/apps/messagesoverlay/app-icon.png
deleted file mode 100644
index c9b4b62ac..000000000
Binary files a/apps/messagesoverlay/app-icon.png and /dev/null differ
diff --git a/apps/messagesoverlay/lib.js b/apps/messagesoverlay/lib.js
index cc6b63176..5587fce19 100644
--- a/apps/messagesoverlay/lib.js
+++ b/apps/messagesoverlay/lib.js
@@ -279,6 +279,8 @@ let drawTriangleDown = function(ovr) {
ovr.fillPoly([ovr.getWidth()-9, ovr.getHeight()-6, ovr.getWidth()-14, ovr.getHeight()-16, ovr.getWidth()-4, ovr.getHeight()-16]);
};
+let linesScroll = 6;
+
let scrollUp = function(ovr) {
msg = eventQueue[0];
LOG("up", msg);
@@ -289,7 +291,7 @@ let scrollUp = function(ovr) {
if (!msg.CanscrollUp) return;
- msg.FirstLine = msg.FirstLine > 0 ? msg.FirstLine - 1 : 0;
+ msg.FirstLine = msg.FirstLine > 0 ? msg.FirstLine - linesScroll : 0;
drawMessage(ovr, msg);
};
@@ -304,7 +306,7 @@ let scrollDown = function(ovr) {
if (!msg.CanscrollDown) return;
- msg.FirstLine = msg.FirstLine + 1;
+ msg.FirstLine = msg.FirstLine + linesScroll;
drawMessage(ovr, msg);
};
@@ -389,19 +391,17 @@ let getTouchHandler = function(ovr){
};
};
-let touchHandler;
-let swipeHandler;
-
let restoreHandler = function(event){
- if (backup[event]){
- Bangle["#on" + event]=backup[event];
- backup[event] = undefined;
- }
+ LOG("Restore", event, backup[event]);
+ Bangle.removeAllListeners(event);
+ Bangle["#on" + event]=backup[event];
+ backup[event] = undefined;
};
let backupHandler = function(event){
- if (eventQueue.length > 1 && ovr) return; // do not backup, overlay is already up
+ if (backupDone) return; // do not backup, overlay is already up
backup[event] = Bangle["#on" + event];
+ LOG("Backed up", backup[event]);
Bangle.removeAllListeners(event);
};
@@ -414,21 +414,16 @@ let cleanup = function(){
restoreHandler("swipe");
restoreHandler("drag");
- if (touchHandler) {
- Bangle.removeListener("touch", touchHandler);
- touchHandler = undefined;
- }
- if (swipeHandler) {
- Bangle.removeListener("swipe", swipeHandler);
- swipeHandler = undefined;
- }
Bangle.setLCDOverlay();
+ backupDone = false;
ovr = undefined;
quiet = undefined;
};
let backup = {};
+let backupDone = false;
+
let main = function(ovr, event) {
LOG("Main", event, settings);
@@ -441,13 +436,11 @@ let main = function(ovr, event) {
backupHandler("touch");
backupHandler("swipe");
backupHandler("drag");
-
- if (touchHandler) Bangle.removeListener("touch",touchHandler);
- if (swipeHandler) Bangle.removeListener("swipe",swipeHandler);
- touchHandler = getTouchHandler(ovr);
- swipeHandler = getSwipeHandler(ovr);
- Bangle.on('touch', touchHandler);
- Bangle.on('swipe', swipeHandler);
+ if (!backupDone){
+ Bangle.on('touch', getTouchHandler(ovr));
+ Bangle.on('swipe', getSwipeHandler(ovr));
+ }
+ backupDone=true;
if (event !== undefined){
drawBorder(ovr);
@@ -492,4 +485,4 @@ exports.clearAll = function() { return require_real("messages").clearAll();};
exports.getMessages = function() { return require_real("messages").getMessages();};
exports.status = function() { return require_real("messages").status();};
exports.buzz = function() { return require_real("messages").buzz(msgSrc);};
-exports.stopBuzz = function() { return require_real("messages").stopBuzz();};
\ No newline at end of file
+exports.stopBuzz = function() { return require_real("messages").stopBuzz();};
diff --git a/apps/messagesoverlay/metadata.json b/apps/messagesoverlay/metadata.json
index 9efe95d26..6a3f953d8 100644
--- a/apps/messagesoverlay/metadata.json
+++ b/apps/messagesoverlay/metadata.json
@@ -1,18 +1,35 @@
{
"id": "messagesoverlay",
"name": "Messages Overlay",
- "version": "0.01",
+ "version": "0.03",
"description": "An overlay based implementation of a messages UI (display notifications from iOS and Gadgetbridge/Android)",
"icon": "app.png",
"type": "bootloader",
"tags": "tool,system",
- "supports": ["BANGLEJS2"],
- "dependencies" : { "messageicons":"module","messages":"app" },
+ "supports": [
+ "BANGLEJS2"
+ ],
+ "dependencies": {
+ "messageicons": "module",
+ "messages": "app"
+ },
"readme": "README.md",
"storage": [
- {"name":"messagesoverlay.settings.js","url":"settings.js"},
- {"name":"messagesoverlay","url":"lib.js"},
- {"name":"messagesoverlay.boot.js","url":"boot.js"}
+ {
+ "name": "messagesoverlay",
+ "url": "lib.js"
+ },
+ {
+ "name": "messagesoverlay.boot.js",
+ "url": "boot.js"
+ }
],
- "screenshots": [{"url":"screen_call.png"} ,{"url":"screen_message.png"} ]
+ "screenshots": [
+ {
+ "url": "screen_call.png"
+ },
+ {
+ "url": "screen_message.png"
+ }
+ ]
}
diff --git a/apps/messagesoverlay/settings.js b/apps/messagesoverlay/settings.js
deleted file mode 100644
index b7197c70a..000000000
--- a/apps/messagesoverlay/settings.js
+++ /dev/null
@@ -1 +0,0 @@
-eval(require("Storage").read("messages.settings.js"));
diff --git a/apps/multidice/ChangeLog b/apps/multidice/ChangeLog
new file mode 100644
index 000000000..cb0cce2aa
--- /dev/null
+++ b/apps/multidice/ChangeLog
@@ -0,0 +1,32 @@
+0.90: got most of the features done, lacking some polish and real-hardware testing
+1.00: overhauled the whole app, made some margins larger to be easier to tap on
+1.01: fixed bug that caused rolled dice on the right of screen to be writ off-screen
+1.02: added vibration when dice is rolled
+1.03: vibration caused the accelerometer to never stop
+1.04: decreased vibration strength
+1.05: toggled the acceleration handler to prevent infinite buzz loop
+1.06: increased vibration again
+1.07: IDK how to use promises properly
+1.08: still trying to fix the lack of vibrations
+1.09: hopefully now it's fixed?
+1.10: not having web bluetooth to debug is a PAIN
+1.11: decreased vibration time, decreased accel requirement
+1.12: issue with app calling roll function too many times at startup
+1.13: added a delay after the buzzer stops to prevent multi-rolling
+1.14: made the delay needlessly long to see if it even does anything
+1.15: moved accel & vibration commands to the accelHandler function
+1.16: enabled button usage & temporarily disabled acceleration
+1.17: made changes to when accelHandler gets overwritten, temporarily disabled button usage
+1.18: decided to keep around the button even while testing, disabled all safety round the accelHandler self-triggering
+1.19: added longer delay before resetting accelHandler
+1.20: removed all traces of accel b/c I've given up
+1.21: added a drawWidgets command to see if I have the padding right
+1.22: ok the buzzing *might* work now
+1.23: forgot to resolve the promise
+1.24: fixed dumb errors
+1.25: god I hope this works
+1.26: trying to add timeout after it's done buzzing... again
+1.27: OH GOD IT FINALLY WORKS
+1.28: increased vibration strength, added some comments, & some QOL
+1.29: changed image
+1.30: changed image, again
diff --git a/apps/multidice/README.md b/apps/multidice/README.md
new file mode 100644
index 000000000..72a2d8af5
--- /dev/null
+++ b/apps/multidice/README.md
@@ -0,0 +1,20 @@
+# multiple dice roller
+
+roll anywhere from 1-8 dice at the same time.
+
+## Usage
+
+
+On the menu screen: tap on the dice to change what variant is selected, & shake/or press BTN to roll the dice
+
+On the dice screen: tap anywhere on the screen to go back to the menu, or shake/or press BTN to roll the dice
+
+## Features
+
+roll anywhere from 1-8 dice (d4, d6, d8, d10, d12, d20, & d percentile). You can select multiple different dice at the same time
+
+
+
+## Controls
+
+App uses touchscreen to cycle through different dice, and accelerometer/BTN to roll them
diff --git a/apps/multidice/app-icon.js b/apps/multidice/app-icon.js
new file mode 100644
index 000000000..88feb83cd
--- /dev/null
+++ b/apps/multidice/app-icon.js
@@ -0,0 +1 @@
+atob("MDABAAAAA8AAAAAAB+AAf//+DDAAwAACGBgAwAADMAwAwAADYAYAgAAAwYMAgAABg+GAgAADBiDAgDwGBiBggH4MAmAwgMMYA8AYgIEwAAAMgIFg8AeGgIHBkAyDgMNBGAjDgH5BGAjDgDxh8A+DgAAwYAMGgAAYAYAMgAAMA+AYwAAGBiAwwAADBiBgwAADgmDAf//+w8Gwf///4AM8wAADMAYEzwADGA3En4ABDBvkmYABBjIkmYABI+IkH4ABPAPkjwABHAHEgDwBAHAEAH4BAPgEgGYBAIgEgGYBAIgEgH4BAPgEgDwBAHAEgADxHAHEgAH5PgPkgAGZIgIkgAGZIgIkgAH5PgPkwADzHAHEwAADAAAEcAAPwAAcP//8///w")
diff --git a/apps/multidice/app.js b/apps/multidice/app.js
new file mode 100644
index 000000000..53f67e21e
--- /dev/null
+++ b/apps/multidice/app.js
@@ -0,0 +1,185 @@
+var menu = true; // default to have the selection menu open
+const DICE_ARRAY = [0, 4, 6, 8, 10, 12, 20, 100]; // 0 means nothing selected
+const SELECTION_ARRAY = [6, 0, 0, 0, 0, 0, 0, 0]; // default to selecting a single d20
+
+// function to draw the selection menu
+function drawMenu() {
+
+ stringArr = new Array ("", "", "", "", "", "", "", "");
+ for (i = 0; i < 8; i++) {
+
+ if (SELECTION_ARRAY [i] != 0) {
+
+ stringArr [i] = "" + DICE_ARRAY [SELECTION_ARRAY [i]];
+ } else {
+
+ stringArr [i] = " . "; // more clearly defines where the user can tap
+ }
+ }
+
+ g.clear();
+ g.setFont ("Vector", 40);
+
+ // " ".slice(-3) left-pads all numbers with spaces
+ g.drawString ((" " + stringArr [0]).slice (-3), 5, 10);
+ g.drawString ((" " + stringArr [1]).slice (-3), 5, 50);
+ g.drawString ((" " + stringArr [2]).slice (-3), 5, 90);
+ g.drawString ((" " + stringArr [3]).slice (-3), 5, 130);
+ g.drawString ((" " + stringArr [4]).slice (-3), 96, 10);
+ g.drawString ((" " + stringArr [5]).slice (-3), 96, 50);
+ g.drawString ((" " + stringArr [6]).slice (-3), 96, 90);
+ g.drawString ((" " + stringArr [7]).slice (-3), 96, 130);
+}
+
+// function to change what dice is selected in the menu
+function touchHandler (button, xy) {
+
+ if (! menu) { // if menu isn't open, open it & return
+
+ menu = true;
+ drawMenu();
+ return;
+ }
+
+ if (xy.x <= 87) { // left
+
+ if (xy.y <= 43) { // first
+
+ selection = 0;
+ } else if (xy.y <= 87) { // second
+
+ selection = 1;
+ } else if (xy.y <= 131) { // third
+
+ selection = 2;
+ } else { // fourth
+
+ selection = 3;
+ }
+ } else { // right
+
+ if (xy.y <= 43) { // first
+
+ selection = 4;
+ } else if (xy.y <= 87) { // second
+
+ selection = 5;
+ } else if (xy.y <= 131) { // third
+
+ selection = 6;
+ } else { // fourth
+
+ selection = 7;
+ }
+ }
+
+ if (SELECTION_ARRAY [selection] == SELECTION_ARRAY.length - 1) { // if last dice is selected, go back to first
+
+ SELECTION_ARRAY [selection] = 0;
+ } else {
+
+ SELECTION_ARRAY [selection] += 1;
+ }
+
+ drawMenu();
+}
+
+function accelHandler (xyz) {
+
+ if (xyz.diff >= 0.3) {
+
+ menu = false;
+ mutex (rollDice).catch (() => {
+
+ return; // not necessary, but prevents spamming the logs
+ });
+ }
+}
+
+// returns a resolved promise if no other mutex call is active, all further ones return a rejected one
+let lock = false;
+function mutex (functionRef) {
+
+ if (lock) {
+
+ return Promise.reject (new Error ("mutex is busy"));
+ }
+
+ lock = true;
+ return new Promise ((resolve, reject) => {
+
+ functionRef().then ((result) => {
+
+ lock = false;
+ resolve (result);
+ }).catch ((error) => {
+
+ lock = false;
+ reject (error);
+ });
+ });
+}
+
+// function to roll all selected dice, and display them
+function rollDice() {
+
+ resultsArr = new Uint8Array (8);
+ for (i = 0; i < 8; i++) {
+
+ if (SELECTION_ARRAY [i] != 0) {
+
+ resultsArr [i] = random (DICE_ARRAY [SELECTION_ARRAY [i]]);
+ }
+ }
+
+ g.clear();
+ g.setFont ("Vector", 40);
+
+ for (i = 0; i < 4; i++) {
+
+ if (SELECTION_ARRAY [i] != 0) {
+
+ g.drawString ((" " + resultsArr [i]).slice (-3), 5, 10 + 40 * i);
+ }
+ }
+
+ for (i = 4; i < 8; i++) {
+
+ if (SELECTION_ARRAY [i] != 0) {
+
+ g.drawString ((" " + resultsArr [i]).slice (-3), 96, 10 + 40 * (i - 4));
+ }
+ }
+
+ return vibrate();
+}
+
+// triggers the vibration, then pauses before returning
+function vibrate() {
+
+ return new Promise ((resolve, reject) => {
+
+ return Bangle.buzz (50, 1).then ((value) => {
+
+ setTimeout (() => {
+
+ resolve (value);
+ }, 200);
+ });
+ });
+}
+
+// returns a integer [1, max]
+function random (max) {
+
+ return Math.round (Math.random() * (max - 1) + 1);
+}
+
+drawMenu();
+Bangle.on ('touch', touchHandler);
+Bangle.on ('accel', accelHandler);
+setWatch (function() {
+
+ menu = false;
+ mutex (rollDice);
+}, BTN, {repeat: true, edge: "falling", debounce: 10});
diff --git a/apps/multidice/app.png b/apps/multidice/app.png
new file mode 100644
index 000000000..75ee0514a
Binary files /dev/null and b/apps/multidice/app.png differ
diff --git a/apps/multidice/many_rolled.png b/apps/multidice/many_rolled.png
new file mode 100644
index 000000000..4cf34e9c7
Binary files /dev/null and b/apps/multidice/many_rolled.png differ
diff --git a/apps/multidice/many_selected.png b/apps/multidice/many_selected.png
new file mode 100644
index 000000000..137e2c363
Binary files /dev/null and b/apps/multidice/many_selected.png differ
diff --git a/apps/multidice/metadata.json b/apps/multidice/metadata.json
new file mode 100644
index 000000000..304c789e4
--- /dev/null
+++ b/apps/multidice/metadata.json
@@ -0,0 +1,15 @@
+{ "id": "multidice",
+ "name": "multiple dice roller",
+ "shortName":"multidice",
+ "version":"1.30",
+ "description": "roll anywhere from 1-8 dice at the same time",
+ "icon": "app.png",
+ "tags": "tool,game",
+ "supports" : ["BANGLEJS2"],
+ "readme": "README.md",
+ "allow_emulator": true,
+ "storage": [
+ {"name":"multidice.app.js","url":"app.js"},
+ {"name":"multidice.img","url":"app-icon.js","evaluate":true}
+ ]
+}
diff --git a/apps/multidice/single_rolled.png b/apps/multidice/single_rolled.png
new file mode 100644
index 000000000..3ce67ab74
Binary files /dev/null and b/apps/multidice/single_rolled.png differ
diff --git a/apps/multidice/startup.png b/apps/multidice/startup.png
new file mode 100644
index 000000000..587281e5c
Binary files /dev/null and b/apps/multidice/startup.png differ
diff --git a/apps/notanalog/ChangeLog b/apps/notanalog/ChangeLog
index 094125f52..e011c4ae1 100644
--- a/apps/notanalog/ChangeLog
+++ b/apps/notanalog/ChangeLog
@@ -4,3 +4,4 @@
0.04: Use alarm for timer instead of own alarm implementation.
0.05: Use internal step counter if no widget is available.
0.06: Use widget_utils.
+0.07: Respect system setting for 12h or 24h time
diff --git a/apps/notanalog/metadata.json b/apps/notanalog/metadata.json
index 319d396a9..851e95ec8 100644
--- a/apps/notanalog/metadata.json
+++ b/apps/notanalog/metadata.json
@@ -3,7 +3,7 @@
"name": "Not Analog",
"shortName":"Not Analog",
"icon": "notanalog.png",
- "version":"0.06",
+ "version":"0.07",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "An analog watch face for people that can not read analog watch faces.",
diff --git a/apps/notanalog/notanalog.app.js b/apps/notanalog/notanalog.app.js
index 29fb1730f..b37c34721 100644
--- a/apps/notanalog/notanalog.app.js
+++ b/apps/notanalog/notanalog.app.js
@@ -13,6 +13,9 @@ let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
+const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})[
+ "12hour"
+];
/*
* Set some important constants such as width, height and center
@@ -199,6 +202,9 @@ function drawTime(){
// Hour
var h = state.currentDate.getHours();
+ if (is12Hour && h > 12) {
+ h = h - 12;
+ }
var h1 = parseInt(h / 10);
var h2 = h < 10 ? h : h - h1*10;
drawTextCleared(h1, cx, posY+8);
diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog
index 7f788c139..a256b459c 100644
--- a/apps/openstmap/ChangeLog
+++ b/apps/openstmap/ChangeLog
@@ -16,3 +16,4 @@
Support for zooming in on map
Satellite count moved to widget bar to leave more room for the map
0.15: Make track drawing an option (default off)
+0.16: Draw waypoints, too.
diff --git a/apps/openstmap/README.md b/apps/openstmap/README.md
index f19b13bd1..bf247c7b7 100644
--- a/apps/openstmap/README.md
+++ b/apps/openstmap/README.md
@@ -17,20 +17,20 @@ To add a map:
* Scroll and zoom to the area of interest or use the Search button in the top left
* Now choose the size you want to upload (Small/Medium/etc)
* On Bangle.js 1 you can choose if you want a 3 bits per pixel map (this is lower
-quality but uploads faster and takes less space). On Bangle.js 2 you only have a 3bpp
-display so can only use 3bpp.
+quality, but uploads faster and takes less space). Bangle.js 2 is limited to 3bpp.
* Click `Get Map`, and a preview will be displayed. If you need to adjust the area you
can change settings, move the map around, and click `Get Map` again.
* When you're ready, click `Upload`
## Bangle.js App
-The Bangle.js app allows you to view a map - it also turns the GPS on and marks
-the path that you've been travelling (if enabled).
+The Bangle.js app allows you to view a map. It also turns the GPS on
+and marks the path that you've been travelling (if enabled), and
+displays waypoints in the watch (if dependencies exist).
* Drag on the screen to move the map
-* Press the button to bring up a menu, where you can zoom, go to GPS location
-, put the map back in its default location, or choose whether to draw the currently
+* Press the button to bring up a menu, where you can zoom, go to GPS location,
+put the map back in its default location, or choose whether to draw the currently
recording GPS track (from the `Recorder` app).
**Note:** If enabled, drawing the currently recorded GPS track can take a second
diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js
index 89e2d2ddb..a5130d23e 100644
--- a/apps/openstmap/app.js
+++ b/apps/openstmap/app.js
@@ -10,6 +10,7 @@ var settings = require("Storage").readJSON("openstmap.json",1)||{};
function redraw() {
g.setClipRect(R.x,R.y,R.x2,R.y2);
m.draw();
+ drawPOI();
drawMarker();
// if track drawing is enabled...
if (settings.drawTrack) {
@@ -25,6 +26,26 @@ function redraw() {
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
}
+// Draw the POIs
+function drawPOI() {
+ try {
+ var waypoints = require("waypoints").load();
+ } catch (ex) {
+ // Waypoints module not available.
+ return;
+ }
+ g.setFont("Vector", 18);
+ waypoints.forEach((wp, idx) => {
+ var p = m.latLonToXY(wp.lat, wp.lon);
+ var sz = 2;
+ g.setColor(0,0,0);
+ g.fillRect(p.x-sz, p.y-sz, p.x+sz, p.y+sz);
+ g.setColor(0,0,0);
+ g.drawString(wp.name, p.x, p.y);
+ print(wp.name);
+ })
+}
+
// Draw the marker for where we are
function drawMarker() {
if (!fix.fix) return;
diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json
index 819dc4122..4419cd411 100644
--- a/apps/openstmap/metadata.json
+++ b/apps/openstmap/metadata.json
@@ -2,7 +2,7 @@
"id": "openstmap",
"name": "OpenStreetMap",
"shortName": "OpenStMap",
- "version": "0.15",
+ "version": "0.16",
"description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps",
"readme": "README.md",
"icon": "app.png",
diff --git a/apps/qrcode/metadata.json b/apps/qrcode/metadata.json
index 24af7b813..89c859a0c 100644
--- a/apps/qrcode/metadata.json
+++ b/apps/qrcode/metadata.json
@@ -1,6 +1,7 @@
{
"id": "qrcode",
"name": "Custom QR Code",
+ "shortName": "QR Code",
"version": "0.06",
"description": "Use this to upload a customised QR code to Bangle.js",
"icon": "app.png",
diff --git a/apps/quicklaunch/metadata.json b/apps/quicklaunch/metadata.json
index a0ff9fa9d..e38d25d09 100644
--- a/apps/quicklaunch/metadata.json
+++ b/apps/quicklaunch/metadata.json
@@ -6,11 +6,12 @@
"description": "Tap or swipe left/right/up/down on your clock face to launch up to five apps of your choice. Configurations can be accessed through Settings->Apps.",
"type": "bootloader",
"tags": "tools, system",
+ "readme": "README.md",
"supports": ["BANGLEJS2"],
"storage": [
- {"name":"quicklaunch.settings.js","url":"settings.js"},
- {"name":"quicklaunch.boot.js","url":"boot.js"},
- {"name":"quicklaunch.app.js","url":"app.js"}
+ {"name": "quicklaunch.settings.js", "url": "settings.js"},
+ {"name": "quicklaunch.boot.js", "url": "boot.js"},
+ {"name": "quicklaunch.app.js", "url": "app.js"}
],
- "data": [{"name":"quicklaunch.json"}]
+ "data": [{"name": "quicklaunch.json"}]
}
diff --git a/apps/runplus/ChangeLog b/apps/runplus/ChangeLog
index 0e19b5559..d920a3eca 100644
--- a/apps/runplus/ChangeLog
+++ b/apps/runplus/ChangeLog
@@ -17,3 +17,6 @@
Keep run state between runs (allowing you to exit and restart the app)
0.16: Don't clear zone 2b indicator segment when updating HRM reading.
Write to correct settings file, fixing settings not working.
+0.17: Fix typo in variable name preventing starting a run.
+0.18: Tweak HRM min/max defaults. Extend min/max intervals in settings. Fix
+ another typo.
diff --git a/apps/runplus/app.js b/apps/runplus/app.js
index 9d7010e6c..7cb5d4381 100644
--- a/apps/runplus/app.js
+++ b/apps/runplus/app.js
@@ -44,8 +44,8 @@ let settings = Object.assign({
},
},
HRM: {
- min: 65,
- max: 170,
+ min: 55,
+ max: 185,
},
}, require("Storage").readJSON("runplus.json", 1) || {});
let statIDs = [settings.B1,settings.B2,settings.B3,settings.B4,settings.B5,settings.B6].filter(s=>s!=="");
@@ -124,7 +124,7 @@ lc.push({ type:"h", filly:1, c:[
// Now calculate the layout
let layout = new Layout( {
type:"v", c: lc
-},{lazy:true, btns:[{ label:"---", cb: ()=>{if (karvonnenActive) {stopKarvonnenUI();run();} onStartStop();}, id:"button"}]});
+},{lazy:true, btns:[{ label:"---", cb: (()=>{if (karvonenActive) {stopKarvonenUI();run();} onStartStop();}), id:"button"}]});
delete lc;
setStatus(exs.state.active);
layout.render();
diff --git a/apps/runplus/metadata.json b/apps/runplus/metadata.json
index 017a52ac3..c605c438d 100644
--- a/apps/runplus/metadata.json
+++ b/apps/runplus/metadata.json
@@ -1,7 +1,7 @@
{
"id": "runplus",
"name": "Run+",
- "version": "0.16",
+ "version": "0.18",
"description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen",
diff --git a/apps/runplus/settings.js b/apps/runplus/settings.js
index cd72022be..539391a27 100644
--- a/apps/runplus/settings.js
+++ b/apps/runplus/settings.js
@@ -32,8 +32,8 @@
},
},
HRM: {
- min: 65,
- max: 165,
+ min: 55,
+ max: 185,
},
}, storage.readJSON(SETTINGS_FILE, 1) || {});
function saveSettings() {
@@ -145,7 +145,7 @@
},
}
hrmMenu[/*LANG*/"max"] = {
- min: 120, max: 190,
+ min: 101, max: 220,
value: settings.HRM.max,
format: v => v,
onchange: v => {
diff --git a/apps/sched/ChangeLog b/apps/sched/ChangeLog
index 634250d48..92b04fb32 100644
--- a/apps/sched/ChangeLog
+++ b/apps/sched/ChangeLog
@@ -22,3 +22,4 @@
0.19: Update clock_info to refresh periodically on active alarms/timers
0.20: Alarm dismiss and snooze events
0.21: Fix crash in clock_info
+0.22: Dated event repeat option
diff --git a/apps/sched/README.md b/apps/sched/README.md
index c874b5577..2fb201cee 100644
--- a/apps/sched/README.md
+++ b/apps/sched/README.md
@@ -45,7 +45,9 @@ Alarms are stored in an array in `sched.json`, and take the form:
// eg (new Date()).toISOString().substr(0,10)
msg : "Eat food", // message to display.
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day! (No change from 0 on timers)
- rp : true, // repeat the alarm every day?
+ rp : true, // repeat the alarm every day? If date is given, pass an object instead of a boolean,
+ // e.g. repeat every 2 months: { interval: "month", num: 2 }.
+ // Supported intervals: day, week, month, year
vibrate : "...", // OPTIONAL pattern of '.', '-' and ' ' to use for when buzzing out this alarm (defaults to '..' if not set)
hidden : false, // OPTIONAL if false, the widget should not show an icon for this alarm
as : false, // auto snooze
diff --git a/apps/sched/metadata.json b/apps/sched/metadata.json
index 42c3aaa12..1a4a64994 100644
--- a/apps/sched/metadata.json
+++ b/apps/sched/metadata.json
@@ -1,7 +1,7 @@
{
"id": "sched",
"name": "Scheduler",
- "version": "0.21",
+ "version": "0.22",
"description": "Scheduling library for alarms and timers",
"icon": "app.png",
"type": "scheduler",
diff --git a/apps/sched/sched.js b/apps/sched/sched.js
index f2f2644f9..ea4c43443 100644
--- a/apps/sched/sched.js
+++ b/apps/sched/sched.js
@@ -42,7 +42,9 @@ function showAlarm(alarm) {
if (del) {
alarms.splice(alarmIndex, 1);
} else {
- if (!alarm.timer) {
+ if (alarm.date && alarm.rp) {
+ setNextRepeatDate(alarm);
+ } else if (!alarm.timer) {
alarm.last = new Date().getDate();
}
if (alarm.ot !== undefined) {
@@ -78,6 +80,36 @@ function showAlarm(alarm) {
});
}
+ function setNextRepeatDate(alarm) {
+ let date = new Date(alarm.date);
+ let rp = alarm.rp;
+ switch(rp.interval) {
+ case true:
+ date.setDate(date.getDate() + 1);
+ break;
+ case "day":
+ date.setDate(date.getDate() + rp.num);
+ break;
+ case "week":
+ date.setDate(date.getDate() + (rp.num * 7));
+ break;
+ case "month":
+ if (!alarm.od) alarm.od = date.getDate();
+ date = new Date(date.getFullYear(), date.getMonth() + rp.num, alarm.od);
+ if (date.getDate() != alarm.od) date.setDate(0);
+ break;
+ case "year":
+ if (!alarm.od) alarm.od = date.getDate();
+ date = new Date(date.getFullYear() + rp.num, date.getMonth(), alarm.od);
+ if (date.getDate() != alarm.od) date.setDate(0);
+ break;
+ default:
+ console.log(`sched: unknown repeat '${JSON.stringify(rp)}'`);
+ break;
+ }
+ alarm.date = date.toLocalISOString().slice(0,10);
+ }
+
if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1)
return;
diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog
index 8f14d0419..971162691 100644
--- a/apps/setting/ChangeLog
+++ b/apps/setting/ChangeLog
@@ -64,3 +64,4 @@
of 'Select Clock'
0.57: Settings.log = 0,1,2,3 for off,display,log,both
0.58: On/Off settings items now use checkboxes
+0.59: Preserve BLE whitelist even when disabled
diff --git a/apps/setting/metadata.json b/apps/setting/metadata.json
index ab029f1e5..e62479604 100644
--- a/apps/setting/metadata.json
+++ b/apps/setting/metadata.json
@@ -1,7 +1,7 @@
{
"id": "setting",
"name": "Settings",
- "version": "0.58",
+ "version": "0.59",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",
diff --git a/apps/setting/settings.js b/apps/setting/settings.js
index db3e476b7..5d2a5f7c6 100644
--- a/apps/setting/settings.js
+++ b/apps/setting/settings.js
@@ -191,7 +191,14 @@ function showBLEMenu() {
onchange: () => setTimeout(showPasskeyMenu) // graphical_menu redraws after the call
},
/*LANG*/'Whitelist': {
- value: settings.whitelist?(settings.whitelist.length+/*LANG*/" devs"):/*LANG*/"off",
+ value:
+ (
+ settings.whitelist_disabled ? /*LANG*/"off" : /*LANG*/"on"
+ ) + (
+ settings.whitelist
+ ? " (" + settings.whitelist.length + ")"
+ : ""
+ ),
onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call
}
});
@@ -341,12 +348,21 @@ function showPasskeyMenu() {
function showWhitelistMenu() {
var menu = {
"< Back" : ()=>showBLEMenu(),
- /*LANG*/"Disable" : () => {
- settings.whitelist = undefined;
+ };
+ if (settings.whitelist_disabled) {
+ menu[/*LANG*/"Enable"] = () => {
+ delete settings.whitelist_disabled;
updateSettings();
showBLEMenu();
- }
- };
+ };
+ } else {
+ menu[/*LANG*/"Disable"] = () => {
+ settings.whitelist_disabled = true;
+ updateSettings();
+ showBLEMenu();
+ };
+ }
+
if (settings.whitelist) settings.whitelist.forEach(function(d){
menu[d.substr(0,17)] = function() {
E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => {
@@ -366,6 +382,7 @@ function showWhitelistMenu() {
NRF.removeAllListeners('connect');
NRF.on('connect', function(addr) {
if (!settings.whitelist) settings.whitelist=[];
+ delete settings.whitelist_disabled;
settings.whitelist.push(addr);
updateSettings();
NRF.removeAllListeners('connect');
diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog
index 0010a58fd..50c033600 100644
--- a/apps/weather/ChangeLog
+++ b/apps/weather/ChangeLog
@@ -21,3 +21,4 @@
0.22: Automatic translation of strings, some left untranslated.
0.23: Update clock_info to avoid a redraw
0.24: Redraw clock_info on update and provide color field for condition
+0.25: Added monochrome parameter to drawIcon in lib
diff --git a/apps/weather/clkinfo.js b/apps/weather/clkinfo.js
index ef3b7d139..4e526b977 100644
--- a/apps/weather/clkinfo.js
+++ b/apps/weather/clkinfo.js
@@ -22,7 +22,7 @@
function weatherIcon(code) {
var ovr = Graphics.createArrayBuffer(24,24,1,{msb:true});
- weatherLib.drawIcon({code:code},12,12,12,ovr);
+ weatherLib.drawIcon({code:code},12,12,12,ovr,true);
var img = ovr.asImage();
img.transparent = 0;
return img;
diff --git a/apps/weather/lib.js b/apps/weather/lib.js
index 14ca77ec6..af6aa4d7e 100644
--- a/apps/weather/lib.js
+++ b/apps/weather/lib.js
@@ -155,14 +155,11 @@ exports.getColor = function(code) {
* @param y Top
* @param r Icon Size
* @param ovr Graphics instance (or undefined for g)
+ * @param monochrome If true, produce a monochromatic icon
*/
-exports.drawIcon = function(cond, x, y, r, ovr) {
+exports.drawIcon = function(cond, x, y, r, ovr, monochrome) {
var palette;
- var monochrome=1;
- if(!ovr) {
- ovr = g;
- monochrome=0;
- }
+ if(!ovr) ovr = g;
palette = getPalette(monochrome, ovr);
diff --git a/apps/weather/metadata.json b/apps/weather/metadata.json
index bcb2fe109..55c2973b0 100644
--- a/apps/weather/metadata.json
+++ b/apps/weather/metadata.json
@@ -1,7 +1,7 @@
{
"id": "weather",
"name": "Weather",
- "version": "0.24",
+ "version": "0.25",
"description": "Show Gadgetbridge weather report",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
diff --git a/apps/weatherClock/ChangeLog b/apps/weatherClock/ChangeLog
index a6a12c297..f31e15729 100644
--- a/apps/weatherClock/ChangeLog
+++ b/apps/weatherClock/ChangeLog
@@ -1,5 +1,6 @@
0.01: New App!
0.02: Minor layout format tweak so it uses less memory and draws ok on Bangle.js 1 (#1012)
0.03: Minor layout extra spaces.
-0.04: Layout now compatible with Bangle.js 2
-0.05: Use weather condition code for icon selection
+0.04: Layout now compatible with Bangle.js 2.
+0.05: Use weather condition code for icon selection.
+0.06: WeatherClock icons now reflect weather conditions better. Add settings menu to hide elements and to use weather icons of Weather app. Images placed into functions for performance.
\ No newline at end of file
diff --git a/apps/weatherClock/README.md b/apps/weatherClock/README.md
index f1f146440..a7f44f7f7 100644
--- a/apps/weatherClock/README.md
+++ b/apps/weatherClock/README.md
@@ -1,12 +1,14 @@
# Weather Clock
-A clock which displays the current weather conditions. Temperature, wind speed, and an icon indicating the weather conditions are displayed.
+A clock which displays the current weather conditions. Time, day of week, date, temperature, wind speed, and an icon indicating the weather condition are displayed.
+
+As of Weather Clock v0.06 the date, day of week, temperature, weather icon and/or wind speed can be hidden in Settings. The icons can be changed to those of the Weather app.
Standard widgets are displayed.
## Requirements
-**This clock requires Gadgetbridge and the weather app in order to get weather data!**
+**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/weatherClock/app.js b/apps/weatherClock/app.js
index 91d0ab36f..4896a9f49 100644
--- a/apps/weatherClock/app.js
+++ b/apps/weatherClock/app.js
@@ -1,22 +1,36 @@
const Layout = require("Layout");
-const storage = require('Storage');
+const storage = require("Storage");
const locale = require("locale");
+const SETTINGS_FILE = "weatherClock.json";
+let s;
+const w = require("weather");
-// weather icons from https://icons8.com/icon/set/weather/color
-var sunIcon = require("heatshrink").decompress(atob("mEwwhC/AH4AbhvQC6vd7ouVC4IwUCwIwUFwQwQCYgAHDZQXc9wACC6QWDDAgXN7wXF9oXPCwowDC5guGGAYXMCw4wCC5RGJJAZGTJBiNISIylQVJrLCC5owGF65fXR7AwBC5jvhC7JIILxapDFxAXOGAy9KC4owGBAQXODAgHDC54AHC8T0FAAQSOGg4qPGA4WUGAIuVC7AA/AH4AEA="));
-
-var partSunIcon = require("heatshrink").decompress(atob("mEwwhC/AH4AY6AWVhvdC6vd7owUFwIABFiYAFGR4Xa93u9oXTCwIYDC6HeC4fuC56MBC4ySOIwpIQXYQXHmYABRpwXECwQYKF5HjC4kwL5gQCAYYwO7wqFAAowK7wWKJBgXLJBPd6YX/AAoVMAAM/Cw0DC5yRHCx5JGFyAwGCyIwFC/4XyR4inXa64wRFwowQCw4A/AH4AkA"));
-
-var cloudIcon = require("heatshrink").decompress(atob("mEwwhC/AH4A/AH4AtgczmYWWDCgWDmcwIKAuEGBoSGGCAWKC7BIKIxYX6CpgABn4tUSJIWPJIwuQGAwWRGAoX/C+SPEU67XXGCIuFGCAWHAH4A/AH4A/ADg="));
-
-var snowIcon = require("heatshrink").decompress(atob("mEwwhC/AH4AhxGAC9YUBC4QZRhAVBAIWIC6QAEI6IYEI5cIBgwWOC64NCKohHPNox3RBgqnQEo7XPHpKONR5AXYAH4ASLa4XWXILiBC6r5LDBgWWDBRrKC5hsCEacIHawvMCIwvQC5QvQFAROEfZ5ADLJ4YGCywvVI7CPGC9IA/AH4AF"));
-
-var rainIcon = require("heatshrink").decompress(atob("mEwwhC/AH4AFgczmYWWDCgWDmcwIKAuEGBoSGGCAWKC7BIKIxYX6CpgABn4tUSJIWPJIwuQGAwWRGAoX/C+SPEU67XXGCIuFGCAWHAGeIBJEIwAVJhGIC5AJBC5QMJEJQMEC44JBC6QSCC54FHLxgNBBgYSEDgKpPMhQXneSwuUAH4A/AA4="));
-
-var stormIcon = require("heatshrink").decompress(atob("mEwwhC/AFEzmcwCyoYUgYXDmYuVGAY0OFwocHC6pNLCxYXYJBQXuCxhhJRpgYKCyBKFFyIXFCyJIFC/4XaO66nU3eza6k7C4IWFGBwXBCwwwO3ewC5AZMC6RaCIxZiI3e7AYYwRCQIIBC4QwPIQIpDC5owDhYREIxgAEFIouNC4orDFyBGBGAcLC6BaFhYWRLSRIFISQXcCyqhRAH4Az"));
-
+// Weather icons from https://icons8.com/icon/set/weather/color
+function getSun() {
+ return require("heatshrink").decompress(atob("mEwwhC/AH4AbhvQC6vd7ouVC4IwUCwIwUFwQwQCYgAHDZQXc9wACC6QWDDAgXN7wXF9oXPCwowDC5guGGAYXMCw4wCC5RGJJAZGTJBiNISIylQVJrLCC5owGF65fXR7AwBC5jvhC7JIILxapDFxAXOGAy9KC4owGBAQXODAgHDC54AHC8T0FAAQSOGg4qPGA4WUGAIuVC7AA/AH4AEA="));
+}
+function getPartSun() {
+ return require("heatshrink").decompress(atob("mEwwhC/AH4AY6AWVhvdC6vd7owUFwIABFiYAFGR4Xa93u9oXTCwIYDC6HeC4fuC56MBC4ySOIwpIQXYQXHmYABRpwXECwQYKF5HjC4kwL5gQCAYYwO7wqFAAowK7wWKJBgXLJBPd6YX/AAoVMAAM/Cw0DC5yRHCx5JGFyAwGCyIwFC/4XyR4inXa64wRFwowQCw4A/AH4AkA"));
+}
+function getCloud() {
+ return require("heatshrink").decompress(atob("mEwwhC/AH4A/AH4AtgczmYWWDCgWDmcwIKAuEGBoSGGCAWKC7BIKIxYX6CpgABn4tUSJIWPJIwuQGAwWRGAoX/C+SPEU67XXGCIuFGCAWHAH4A/AH4A/ADg="));
+}
+function getSnow() {
+ return require("heatshrink").decompress(atob("mEwwhC/AH4AhxGAC9YUBC4QZRhAVBAIWIC6QAEI6IYEI5cIBgwWOC64NCKohHPNox3RBgqnQEo7XPHpKONR5AXYAH4ASLa4XWXILiBC6r5LDBgWWDBRrKC5hsCEacIHawvMCIwvQC5QvQFAROEfZ5ADLJ4YGCywvVI7CPGC9IA/AH4AF"));
+}
+function getRain() {
+ return require("heatshrink").decompress(atob("mEwwhC/AH4AFgczmYWWDCgWDmcwIKAuEGBoSGGCAWKC7BIKIxYX6CpgABn4tUSJIWPJIwuQGAwWRGAoX/C+SPEU67XXGCIuFGCAWHAGeIBJEIwAVJhGIC5AJBC5QMJEJQMEC44JBC6QSCC54FHLxgNBBgYSEDgKpPMhQXneSwuUAH4A/AA4="));
+}
+function getStorm() {
+ return require("heatshrink").decompress(atob("mEwwhC/AFEzmcwCyoYUgYXDmYuVGAY0OFwocHC6pNLCxYXYJBQXuCxhhJRpgYKCyBKFFyIXFCyJIFC/4XaO66nU3eza6k7C4IWFGBwXBCwwwO3ewC5AZMC6RaCIxZiI3e7AYYwRCQIIBC4QwPIQIpDC5owDhYREIxgAEFIouNC4orDFyBGBGAcLC6BaFhYWRLSRIFISQXcCyqhRAH4Az"));
+}
// err icon - https://icons8.com/icons/set/error
-var errIcon = require("heatshrink").decompress(atob("mEwwkBiIA/AH4AZUAIWUiAXBWqgXXdIYuVGCgXBgICCIyYXCJCQTDC6QrEMCQSEJCQRFC6ApGJCCiDDQSpQFAYXEJBqNGJCA/EC4ZIOEwgXFJBgNEAhKlNAgxIKBgoXEJBjsLC5TsIeRycMBhRrMMBKzQEozjOBxAgHGww+IA6wfSH4hnIC47OMSJqlRIJAXCACIXaGoQARPwwuTAH4A/ABw"));
+function getErr() {
+ return require("heatshrink").decompress(atob("mEwwkBiIA/AH4AZUAIWUiAXBWqgXXdIYuVGCgXBgICCIyYXCJCQTDC6QrEMCQSEJCQRFC6ApGJCCiDDQSpQFAYXEJBqNGJCA/EC4ZIOEwgXFJBgNEAhKlNAgxIKBgoXEJBjsLC5TsIeRycMBhRrMMBKzQEozjOBxAgHGww+IA6wfSH4hnIC47OMSJqlRIJAXCACIXaGoQARPwwuTAH4A/ABw"));
+}
+function getDummy() {
+ return require("heatshrink").decompress(atob("gMBwMAwA"));
+}
/**
Choose weather icon to display based on condition.
@@ -25,32 +39,30 @@ sent from gadget bridge.
*/
function chooseIcon(condition) {
condition = condition.toLowerCase();
- if (condition.includes("thunderstorm")) return stormIcon;
+ if (condition.includes("thunderstorm")||
+ condition.includes("squalls")||
+ condition.includes("tornado")) return getStorm;
if (condition.includes("freezing")||condition.includes("snow")||
condition.includes("sleet")) {
- return snowIcon;
+ return getSnow;
}
if (condition.includes("drizzle")||
- condition.includes("shower")) {
- return rainIcon;
+ condition.includes("shower")||
+ condition.includes("rain")) return getRain;
+ if (condition.includes("clear")) return getSun;
+ if (condition.includes("clouds")) return getCloud;
+ if (condition.includes("few clouds")||
+ condition.includes("scattered clouds")||
+ condition.includes("mist")||
+ condition.includes("smoke")||
+ condition.includes("haze")||
+ condition.includes("sand")||
+ condition.includes("dust")||
+ condition.includes("fog")||
+ condition.includes("ash")) {
+ return getPartSun;
}
- if (condition.includes("rain")) return rainIcon;
- if (condition.includes("clear")) return sunIcon;
- if (condition.includes("few clouds")) return partSunIcon;
- if (condition.includes("scattered clouds")) return cloudIcon;
- if (condition.includes("clouds")) return cloudIcon;
- 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 cloudIcon;
- }
- return cloudIcon;
+ return getCloud;
}
/*
@@ -60,55 +72,29 @@ function chooseIcon(condition) {
function chooseIconByCode(code) {
const codeGroup = Math.round(code / 100);
switch (codeGroup) {
- case 2: return stormIcon;
- case 3: return rainIcon;
- case 5: return rainIcon;
- case 6: return snowIcon;
- case 7: return cloudIcon;
+ case 2: return getStorm;
+ case 3: return getRain;
+ case 5:
+ switch (code) {
+ case 511: return getSnow;
+ default: return getRain;
+ }
+ case 6: return getSnow;
+ case 7: return getPartSun;
case 8:
switch (code) {
- case 800: return sunIcon;
- case 801: return partSunIcon;
- default: return cloudIcon;
+ case 800: return getSun;
+ case 804: return getCloud;
+ default: return getPartSun;
}
- default: return cloudIcon;
+ default: return getCloud;
}
}
-/**
-Get weather stored in json file by weather app.
-*/
-function getWeather() {
- let jsonWeather = storage.readJSON('weather.json');
- return jsonWeather;
-}
-
-var clockLayout = new Layout( {
- type:"v", c: [
- {type:"txt", font:"35%", halign: 0, fillx:1, pad: 8, label:"00:00", id:"time" },
- {type: "h", fillx: 1, c: [
- {type:"txt", font:"10%", label:"THU", id:"dow" },
- {type:"txt", font:"10%", label:"01/01/1970", id:"date" }
- ]
- },
- {type: "h", valign : 1, fillx:1, c: [
- {type: "img", filly: 1, id: "weatherIcon", src: sunIcon},
- {type: "v", fillx:1, c: [
- {type: "h", c: [
- {type: "txt", font: "10%", id: "temp", label: "000 °C"},
- ]},
- {type: "h", c: [
- {type: "txt", font: "10%", id: "wind", label: "00 km/h"},
- ]}
- ]
- },
- ]}]
-});
-
-// timeout used to update every minute
+// Timeout used to update every minute
var drawTimeout;
-// schedule a draw for the next minute
+// Schedule a draw for the next minute
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
@@ -119,37 +105,85 @@ function queueDraw() {
function draw() {
var date = new Date();
- clockLayout.time.label = locale.time(date, 1);
- clockLayout.date.label = locale.date(date, 1).toUpperCase();
- clockLayout.dow.label = locale.dow(date, 1).toUpperCase() + " ";
- var weatherJson = getWeather();
- if(weatherJson && weatherJson.weather){
- var currentWeather = weatherJson.weather;
- const temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/);
- clockLayout.temp.label = temp[1] + " " + temp[2];
- const code = currentWeather.code || -1;
+ cLayout.time.label = locale.time(date, 1);
+ cLayout.dow.label = s.day ? locale.dow(date, 1).toUpperCase() + " " : "";
+ cLayout.date.label = s.date ? locale.date(date, 1).toUpperCase() : "";
+ let curr = w.get(); // Get weather from weather app.
+ if(curr){
+ const temp = locale.temp(curr.temp-273.15).match(/^(\D*\d*)(.*)$/);
+ cLayout.temp.label = temp[1] + " " + temp[2];
+ const code = curr.code || -1;
if (code > 0) {
- clockLayout.weatherIcon.src = chooseIconByCode(code);
+ let showIconC = s.src ? wDrawIcon(curr.code) : chooseIconByCode(curr.code);
+ cLayout.wIcon.src = s.icon ? showIconC : getDummy;
} else {
- clockLayout.weatherIcon.src = chooseIcon(currentWeather.txt);
+ let showIconT = s.src ? wDrawIcon(curr.txt) : chooseIcon(curr.txt);
+ cLayout.wIcon.src = s.icon ? showIconT : getDummy;
}
- const wind = locale.speed(currentWeather.wind).match(/^(\D*\d*)(.*)$/);
- clockLayout.wind.label = wind[1] + " " + wind[2] + " " + (currentWeather.wrose||'').toUpperCase();
+ const wind = locale.speed(curr.wind).match(/^(\D*\d*)(.*)$/);
+ cLayout.wind.label = wind[1] + " " + wind[2] + " " + (curr.wrose||"").toUpperCase();
}
else{
- clockLayout.temp.label = "Err";
- clockLayout.wind.label = "No Data";
- clockLayout.weatherIcon.src = errIcon;
+ cLayout.temp.label = "Err";
+ cLayout.wind.label = "No Data";
+ cLayout.wIcon.src = s.icon ? getErr : getDummy;
}
- clockLayout.clear();
- clockLayout.render();
- // queue draw in one minute
+ cLayout.clear();
+ cLayout.render();
+ // Queue draw in one minute
queueDraw();
}
+// Load settings from file
+s = storage.readJSON(SETTINGS_FILE,1)||{};
+s.src = s.src === undefined ? false : s.src;
+s.icon = s.icon === undefined ? true : s.icon;
+s.day = s.day === undefined ? true : s.day;
+s.date = s.date === undefined ? true : s.date;
+s.wind = s.wind === undefined ? true : s.wind;
+
+function wDrawIcon(code) {
+ var ovr = Graphics.createArrayBuffer(50,50,8,{msb:true});
+ if (typeof code == "number") w.drawIcon({code:code},24,24,24,ovr);
+ if (typeof code == "string") w.drawIcon({txt:code},24,24,24,ovr);
+ var img = ovr.asImage();
+ img.transparent = 0;
+ return img;
+}
+
+let srcIcons = s.src ? wDrawIcon(800) : getSun;
+let srcWeather = s.icon ? srcIcons : getDummy;
+let fontTemp = s.wind ? "10%" : "20%";
+let fontWind = s.wind ? "10%" : "0%";
+let labelDay = s.day ? "THU" : "";
+let labelDate = s.date ? "01/01/1970" : "";
+var cLayout = new Layout( {
+ type:"v", c: [
+ {type:"txt", font:"35%", halign: 0, fillx:1, pad: 8, label:"00:00", id:"time" },
+ {type: "h", fillx: 1, c: [
+ {type: "h", c: [
+ {type:"txt", font:"10%", label:labelDay, id:"dow" },
+ {type:"txt", font:"10%", label:labelDate, id:"date" }
+ ]},
+ ]
+ },
+ {type: "h", valign : 1, fillx:1, c: [
+ {type: "img", filly: 1, pad: 8, id: "wIcon", src: srcWeather},
+ {type: "v", fillx:1, c: [
+ {type: "h", c: [
+ {type: "txt", font: fontTemp, id: "temp", label: "000 °C"},
+ ]},
+ {type: "h", c: [
+ {type: "txt", font: fontWind, id: "wind", label: "00 km/h"},
+ ]}
+ ]
+ },
+ ]}]
+});
+
g.clear();
Bangle.setUI("clock"); // Show launcher when middle button pressed
Bangle.loadWidgets();
Bangle.drawWidgets();
-clockLayout.render();
+cLayout.render();
draw();
diff --git a/apps/weatherClock/metadata.json b/apps/weatherClock/metadata.json
index cf8bd899e..270591c74 100644
--- a/apps/weatherClock/metadata.json
+++ b/apps/weatherClock/metadata.json
@@ -1,9 +1,11 @@
{
"id": "weatherClock",
"name": "Weather Clock",
- "version": "0.05",
+ "shortName": "Weather Clock",
+ "version": "0.06",
"description": "A clock which displays current weather conditions (requires Gadgetbridge and Weather apps).",
"icon": "app.png",
+ "dependencies": {"weather":"app"},
"screenshots": [{"url":"screens/screen1.png"}],
"type": "clock",
"tags": "clock, weather",
@@ -12,6 +14,8 @@
"readme": "README.md",
"storage": [
{"name":"weatherClock.app.js","url":"app.js"},
- {"name":"weatherClock.img","url":"app-icon.js","evaluate":true}
- ]
+ {"name":"weatherClock.img","url":"app-icon.js","evaluate":true},
+ {"name":"weatherClock.settings.js","url":"settings.js"}
+ ],
+ "data": [{"name":"weatherClock.json"}]
}
diff --git a/apps/weatherClock/settings.js b/apps/weatherClock/settings.js
new file mode 100644
index 000000000..0aa7330c1
--- /dev/null
+++ b/apps/weatherClock/settings.js
@@ -0,0 +1,58 @@
+(function(back) {
+ const SETTINGS_FILE = "weatherClock.json";
+
+ // Load settings file
+ const storage = require('Storage');
+ let settings = storage.readJSON(SETTINGS_FILE, 1) || {};
+ let s = {};
+ s.date = (settings.date === undefined ? true : settings.date);
+ s.day = (settings.day === undefined ? true : settings.day);
+ s.icon = (settings.icon === undefined ? true : settings.icon);
+ s.wind = (settings.wind === undefined ? true : settings.wind);
+ s.src = (settings.src === undefined ? false : settings.src);
+
+ function save() {
+ settings = s
+ storage.write(SETTINGS_FILE, settings)
+ }
+
+ E.showMenu({
+ '': { 'title': 'Weather Clock' },
+ '< Back': back,
+ 'Show date': {
+ value: !!s.date,
+ onchange: v => {
+ s.date = v;
+ save();
+ },
+ },
+ 'Show day Of Week': {
+ value: !!s.day,
+ onchange: v => {
+ s.day = v;
+ save();
+ },
+ },
+ 'Show weather Icon': {
+ value: !!s.icon,
+ onchange: v => {
+ s.icon = v;
+ save();
+ },
+ },
+ 'Show wind Speed': {
+ value: !!s.wind,
+ onchange: v => {
+ s.wind = v;
+ save();
+ },
+ },
+ 'Use weather app icons': {
+ value: !!s.src,
+ onchange: v => {
+ s.src = v;
+ save();
+ },
+ }
+ });
+});
diff --git a/apps/widChargingStatus/widget.ts b/apps/widChargingStatus/widget.ts
index 02f3cd8d4..a161d5408 100644
--- a/apps/widChargingStatus/widget.ts
+++ b/apps/widChargingStatus/widget.ts
@@ -6,11 +6,11 @@
);
const iconWidth = 18;
- function draw(this: { x: number; y: number }) {
+ function draw(this: { x?: number; y?: number }) {
g.reset();
if (Bangle.isCharging()) {
g.setColor('#FD0');
- g.drawImage(icon, this.x + 1, this.y + 1, {
+ g.drawImage(icon, this.x! + 1, this.y! + 1, {
scale: 0.6875,
});
}
diff --git a/apps/widalarmeta/ChangeLog b/apps/widalarmeta/ChangeLog
index 4bcf6ec69..2b74766c8 100644
--- a/apps/widalarmeta/ChangeLog
+++ b/apps/widalarmeta/ChangeLog
@@ -8,3 +8,4 @@
0.05: Convert Yes/No On/Off in settings to checkboxes
0.06: Remember next alarm to reduce calculation amount
Redraw only every hour when no alarm in next 24h
+0.07: Fix when no alarms are present
diff --git a/apps/widalarmeta/metadata.json b/apps/widalarmeta/metadata.json
index a3d2e8adb..6b3d8978b 100644
--- a/apps/widalarmeta/metadata.json
+++ b/apps/widalarmeta/metadata.json
@@ -2,7 +2,7 @@
"id": "widalarmeta",
"name": "Alarm & Timer ETA",
"shortName": "Alarm ETA",
- "version": "0.06",
+ "version": "0.07",
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
"icon": "widget.png",
"type": "widget",
diff --git a/apps/widalarmeta/widget.js b/apps/widalarmeta/widget.js
index 750ae5d98..0104eb3b1 100644
--- a/apps/widalarmeta/widget.js
+++ b/apps/widalarmeta/widget.js
@@ -9,13 +9,15 @@
function getNextAlarm(date) {
const alarms = (require("Storage").readJSON("sched.json",1) || []).filter(alarm => alarm.on && alarm.hidden !== true);
WIDGETS["widalarmeta"].numActiveAlarms = alarms.length;
- const times = alarms.map(alarm => require("sched").getTimeToAlarm(alarm, date) || Number.POSITIVE_INFINITY);
- const eta = times.length > 0 ? Math.min.apply(null, times) : 0;
- if (eta !== Number.POSITIVE_INFINITY) {
- const idx = times.indexOf(eta);
- const alarm = alarms[idx];
- delete alarm.msg; delete alarm.id; delete alarm.data; // free some memory
- return alarm;
+ if (alarms.length > 0) {
+ const times = alarms.map(alarm => require("sched").getTimeToAlarm(alarm, date) || Number.POSITIVE_INFINITY);
+ const eta = Math.min.apply(null, times);
+ if (eta !== Number.POSITIVE_INFINITY) {
+ const idx = times.indexOf(eta);
+ const alarm = alarms[idx];
+ delete alarm.msg; delete alarm.id; delete alarm.data; // free some memory
+ return alarm;
+ }
}
} // getNextAlarm
diff --git a/apps/widbtstates/ChangeLog b/apps/widbtstates/ChangeLog
new file mode 100644
index 000000000..5560f00bc
--- /dev/null
+++ b/apps/widbtstates/ChangeLog
@@ -0,0 +1 @@
+0.01: New App!
diff --git a/apps/widbtstates/metadata.json b/apps/widbtstates/metadata.json
new file mode 100644
index 000000000..ada530e1b
--- /dev/null
+++ b/apps/widbtstates/metadata.json
@@ -0,0 +1,14 @@
+{
+ "id": "widbtstates",
+ "name": "Bluetooth States",
+ "version": "0.01",
+ "description": "If active, shows a white bluetooth icon, if connected, a blue one (nothing if sleeping)",
+ "icon": "widget.png",
+ "type": "widget",
+ "tags": "widget,bluetooth,clkinfo",
+ "provides_widgets" : ["bluetooth"],
+ "supports": ["BANGLEJS","BANGLEJS2"],
+ "storage": [
+ {"name":"widbtstates.wid.js","url":"widget.js"}
+ ]
+}
diff --git a/apps/widbtstates/widget.js b/apps/widbtstates/widget.js
new file mode 100644
index 000000000..1d32e1bc4
--- /dev/null
+++ b/apps/widbtstates/widget.js
@@ -0,0 +1,53 @@
+"use strict";
+(function () {
+ "ram";
+ var _a;
+ var state = (function () {
+ var status = NRF.getSecurityStatus();
+ if (status.connected)
+ return 2;
+ if (status.advertising)
+ return 1;
+ return 0;
+ })();
+ var width = function () { return state > 0 ? 15 : 0; };
+ var update = function (newState) {
+ state = newState;
+ WIDGETS["bluetooth"].width = width();
+ setTimeout(Bangle.drawWidgets, 50);
+ };
+ var colours = (_a = {},
+ _a[1] = {
+ false: "#fff",
+ true: "#fff",
+ },
+ _a[2] = {
+ false: "#0ff",
+ true: "#00f",
+ },
+ _a);
+ WIDGETS["bluetooth"] = {
+ area: "tl",
+ sortorder: -1,
+ draw: function () {
+ if (state == 0)
+ return;
+ g.reset();
+ g.setColor(colours[state]["".concat(g.theme.dark)]);
+ g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="), this.x + 2, this.y + 2);
+ },
+ width: width(),
+ };
+ NRF.on("connect", update.bind(null, 2));
+ NRF.on("disconnect", update.bind(null, 1));
+ var origWake = NRF.wake;
+ var origSleep = NRF.sleep;
+ NRF.wake = function () {
+ update(1);
+ return origWake.apply(this, arguments);
+ };
+ NRF.sleep = function () {
+ update(0);
+ return origSleep.apply(this, arguments);
+ };
+})();
diff --git a/apps/widbtstates/widget.png b/apps/widbtstates/widget.png
new file mode 100644
index 000000000..1a884a62c
Binary files /dev/null and b/apps/widbtstates/widget.png differ
diff --git a/apps/widbtstates/widget.ts b/apps/widbtstates/widget.ts
new file mode 100644
index 000000000..8f02c1b8c
--- /dev/null
+++ b/apps/widbtstates/widget.ts
@@ -0,0 +1,77 @@
+(() => {
+ "ram";
+
+ const enum State {
+ Asleep,
+ Active,
+ Connected
+ }
+
+ let state: State = (() => {
+ const status = NRF.getSecurityStatus();
+
+ if (status.connected) return State.Connected;
+ if (status.advertising) return State.Active;
+ return State.Asleep;
+ })();
+
+ const width = () => state > State.Asleep ? 15 : 0;
+
+ const update = (newState: State) => {
+ state = newState;
+ WIDGETS["bluetooth"]!.width = width();
+ setTimeout(Bangle.drawWidgets, 50); // no need for .bind()
+ };
+
+ type DarkTheme = `${boolean}`;
+ const colours: {
+ [key in State.Active | State.Connected]: {
+ [key in DarkTheme]: ColorResolvable
+ }
+ } = {
+ [State.Active]: {
+ false: "#fff",
+ true: "#fff",
+ },
+ [State.Connected]: {
+ false: "#0ff",
+ true: "#00f",
+ },
+ };
+
+ WIDGETS["bluetooth"] = {
+ area: "tl",
+ sortorder: -1,
+ draw: function() {
+ if (state == State.Asleep)
+ return;
+
+ g.reset();
+
+ g.setColor(colours[state][`${g.theme.dark}`]);
+
+ g.drawImage(
+ atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),
+ this.x! + 2,
+ this.y! + 2
+ );
+ },
+ width: width(),
+ };
+
+ NRF.on("connect", update.bind(null, State.Connected));
+ NRF.on("disconnect", update.bind(null, State.Active));
+
+ const origWake = NRF.wake;
+ const origSleep = NRF.sleep;
+
+ NRF.wake = function() {
+ update(State.Active);
+ return origWake.apply(this, arguments);
+ };
+
+ NRF.sleep = function() {
+ update(State.Asleep);
+ return origSleep.apply(this, arguments);
+ };
+})();
diff --git a/apps/widlockunlock/ChangeLog b/apps/widlockunlock/ChangeLog
index b4d1ae593..b5efcaa86 100644
--- a/apps/widlockunlock/ChangeLog
+++ b/apps/widlockunlock/ChangeLog
@@ -1 +1,2 @@
0.01: First commit
+0.02: Add tap-to-lock functionality
diff --git a/apps/widlockunlock/metadata.json b/apps/widlockunlock/metadata.json
index d701279b9..cc4fa76cd 100644
--- a/apps/widlockunlock/metadata.json
+++ b/apps/widlockunlock/metadata.json
@@ -1,8 +1,8 @@
{
"id": "widlockunlock",
"name": "Lock/Unlock Widget",
- "version": "0.01",
- "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked, or an unlock icon otherwise",
+ "version": "0.02",
+ "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked, or an unlock icon otherwise. Tap to lock the lcd",
"icon": "widget.png",
"type": "widget",
"tags": "widget,lock",
diff --git a/apps/widlockunlock/widget.js b/apps/widlockunlock/widget.js
index 0716a9edf..cfbbc87a3 100644
--- a/apps/widlockunlock/widget.js
+++ b/apps/widlockunlock/widget.js
@@ -1,5 +1,28 @@
-Bangle.on("lockunlock", function() {
- Bangle.drawWidgets();
+Bangle.on("lock", () => Bangle.drawWidgets());
+
+Bangle.on('touch', (_btn, xy) => {
+ const oversize = 5;
+
+ const w = WIDGETS.lockunlock;
+
+ const x = xy.x;
+ const y = xy.y;
+
+ if(w.x - oversize <= x && x < w.x + 14 + oversize
+ && w.y - oversize <= y && y < w.y + 24 + oversize)
+ {
+ Bangle.setLocked(true);
+
+ const backlightTimeout = Bangle.getOptions().backlightTimeout; // ms
+
+ // seems to be a race/if we don't give the firmware enough time,
+ // it won't timeout the backlight and we'll restore it in our setTimeout below
+ Bangle.setOptions({ backlightTimeout: 100 });
+
+ setTimeout(() => {
+ Bangle.setOptions({ backlightTimeout });
+ }, 300);
+ }
});
WIDGETS["lockunlock"]={area:"tl",sortorder:10,width:14,draw:function(w) {
g.reset().drawImage(atob(Bangle.isLocked() ? "DBGBAAAA8DnDDCBCBP////////n/n/n//////z/A" : "DBGBAAAA8BnDDCBABP///8A8A8Y8Y8Y8A8A//z/A"), w.x+1, w.y+3);
diff --git a/bin/lib/apploader.js b/bin/lib/apploader.js
index 329ece7b7..c1aaa5389 100644
--- a/bin/lib/apploader.js
+++ b/bin/lib/apploader.js
@@ -26,8 +26,10 @@ var device = { id : DEVICEID, appsInstalled : [] };
// call with {DEVICEID:"BANGLEJS/BANGLEJS2"}
exports.init = function(options) {
- if (options.DEVICEID)
+ if (options.DEVICEID) {
DEVICEID = options.DEVICEID;
+ device.id = options.DEVICEID;
+ }
// Load app metadata
var dirs = require("fs").readdirSync(APPSDIR, {withFileTypes: true});
dirs.forEach(dir => {
diff --git a/core b/core
index 0d02ff376..2bb1e55d3 160000
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 0d02ff3763783d166ff84906af038420736aabfc
+Subproject commit 2bb1e55d32d640312fe08cf54de1cd9c498cd31e
diff --git a/typescript/types/main.d.ts b/typescript/types/main.d.ts
index a48ce7565..944d55326 100644
--- a/typescript/types/main.d.ts
+++ b/typescript/types/main.d.ts
@@ -83,7 +83,10 @@ type WidgetArea = "tl" | "tr" | "bl" | "br";
type Widget = {
area: WidgetArea;
width: number;
- draw: (this: { x: number; y: number }) => void;
+ sortorder?: number;
+ draw: (this: Widget, w: Widget) => void;
+ x?: number;
+ y?: number;
};
declare const WIDGETS: { [key: string]: Widget };
@@ -180,6 +183,23 @@ type NRFFilters = {
manufacturerData?: object;
};
+type NRFSecurityStatus = {
+ advertising: boolean,
+} & (
+ {
+ connected: true,
+ encrypted: boolean,
+ mitm_protected: boolean,
+ bonded: boolean,
+ connected_addr?: string,
+ } | {
+ connected: false,
+ encrypted: false,
+ mitm_protected: false,
+ bonded: false,
+ }
+);
+
type ImageObject = {
width: number;
height: number;
@@ -718,7 +738,7 @@ declare class NRF {
* @returns {any} An object
* @url http://www.espruino.com/Reference#l_NRF_getSecurityStatus
*/
- static getSecurityStatus(): any;
+ static getSecurityStatus(): NRFSecurityStatus;
/**
* @returns {any} An object
@@ -1895,6 +1915,7 @@ declare class NRF {
* encrypted // Communication on this link is encrypted.
* mitm_protected // The encrypted communication is also protected against man-in-the-middle attacks.
* bonded // The peer is bonded with us
+ * advertising // Are we currently advertising?
* connected_addr // If connected=true, the MAC address of the currently connected device
* }
* ```
@@ -1903,7 +1924,7 @@ declare class NRF {
* @returns {any} An object
* @url http://www.espruino.com/Reference#l_NRF_getSecurityStatus
*/
- static getSecurityStatus(): any;
+ static getSecurityStatus(): NRFSecurityStatus;
/**
*
@@ -4550,7 +4571,7 @@ declare class BluetoothRemoteGATTServer {
* @returns {any} An object
* @url http://www.espruino.com/Reference#l_BluetoothRemoteGATTServer_getSecurityStatus
*/
- getSecurityStatus(): any;
+ getSecurityStatus(): NRFSecurityStatus;
/**
* See `NRF.connect` for usage examples.