Merge branch 'master' of https://github.com/dapgo/BangleApps
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Actually upload correct code
|
||||
0.03: Display sea-level pressure, too, and allow calibration
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
- <a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
||||
|
||||
## Creator
|
||||
[David Peer](https://github.com/peerdavid)
|
||||
|
||||
## Contributors
|
||||
thyttan
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA=="))
|
||||
|
|
@ -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
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -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();
|
||||
},
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@
|
|||
value: actions.indexOf(settings.DRAGDOWN),
|
||||
format: v => actions[v],
|
||||
onchange: v => {
|
||||
settings.DRGDOWN = actions[v];
|
||||
settings.DRAGDOWN = actions[v];
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App based on dragboard, but with a U shaped drag area
|
||||
|
|
@ -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
|
||||
|
After Width: | Height: | Size: 9.0 KiB |
|
|
@ -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<MIDPADDING-2 ? LEFT : RIGHT, event.b == 0, (event.y-topStart)/((R.y2 - topStart)/vLength));
|
||||
}
|
||||
// drag on top rectangle for number or punctuation
|
||||
else if ((event.y < ( (R.y2) - 12 )) && (event.y > ( (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();
|
||||
});
|
||||
};
|
||||
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;i<binary.length;i+=CHUNKSIZE) {
|
||||
var l = binary.length-i;
|
||||
if (l>CHUNKSIZE) l=CHUNKSIZE;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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()}
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
@ -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"} ]
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
0.01: Initial fork from messages_light
|
||||
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.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 2.8 KiB |
|
|
@ -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();};
|
||||
exports.stopBuzz = function() { return require_real("messages").stopBuzz();};
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
eval(require("Storage").read("messages.settings.js"));
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
atob("MDABAAAAA8AAAAAAB+AAf//+DDAAwAACGBgAwAADMAwAwAADYAYAgAAAwYMAgAABg+GAgAADBiDAgDwGBiBggH4MAmAwgMMYA8AYgIEwAAAMgIFg8AeGgIHBkAyDgMNBGAjDgH5BGAjDgDxh8A+DgAAwYAMGgAAYAYAMgAAMA+AYwAAGBiAwwAADBiBgwAADgmDAf//+w8Gwf///4AM8wAADMAYEzwADGA3En4ABDBvkmYABBjIkmYABI+IkH4ABPAPkjwABHAHEgDwBAHAEAH4BAPgEgGYBAIgEgGYBAIgEgH4BAPgEgDwBAHAEgADxHAHEgAH5PgPkgAGZIgIkgAGZIgIkgAH5PgPkwADzHAHEwAADAAAEcAAPwAAcP//8///w")
|
||||
|
|
@ -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});
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"}]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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"}],
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"}]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
|
|
@ -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);
|
||||
};
|
||||
})();
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -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);
|
||||
};
|
||||
})();
|
||||
|
|
@ -1 +1,2 @@
|
|||
0.01: First commit
|
||||
0.02: Add tap-to-lock functionality
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||