dapgo 2023-03-08 11:00:10 +01:00
commit 5a515be932
103 changed files with 1995 additions and 416 deletions

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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",

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Actually upload correct code
0.03: Display sea-level pressure, too, and allow calibration

View File

@ -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;
});

View File

@ -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",

View File

@ -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

View File

@ -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.

View File

@ -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",

36
apps/bwclklite/ChangeLog Normal file
View File

@ -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.

30
apps/bwclklite/README.md Normal file
View File

@ -0,0 +1,30 @@
# BW Clock Lite
This is a fork of a very minimalistic clock.
![](screenshot.png)
## 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:
![](screenshot_3.png)
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

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA=="))

331
apps/bwclklite/app.js Normal file
View File

@ -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

BIN
apps/bwclklite/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -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"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -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();
},
}
});
})

View File

@ -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

View File

@ -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",

View File

@ -93,7 +93,7 @@
value: actions.indexOf(settings.DRAGDOWN),
format: v => actions[v],
onchange: v => {
settings.DRGDOWN = actions[v];
settings.DRAGDOWN = actions[v];
writeSettings();
}
},

View File

@ -0,0 +1 @@
0.01: New App based on dragboard, but with a U shaped drag area

View File

@ -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

BIN
apps/draguboard/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

156
apps/draguboard/lib.js Normal file
View File

@ -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();
});
};

View File

@ -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"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -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);
});

View File

@ -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

View File

@ -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 += `(&#9888; 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;

View File

@ -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",

View File

@ -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

View File

@ -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();
}
}

View File

@ -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",

View File

@ -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

View File

@ -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.

View File

@ -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;
};

View File

@ -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));

View File

@ -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);
}
}

View File

@ -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()}

View File

@ -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);
});

View File

@ -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"} ]

View File

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -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();};

View File

@ -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"
}
]
}

View File

@ -1 +0,0 @@
eval(require("Storage").read("messages.settings.js"));

32
apps/multidice/ChangeLog Normal file
View File

@ -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

20
apps/multidice/README.md Normal file
View File

@ -0,0 +1,20 @@
# multiple dice roller
roll anywhere from 1-8 dice at the same time.
## Usage
![startup.png](startup.png)
On the menu screen: tap on the dice to change what variant is selected, & shake/or press BTN to roll the dice
![single_rolled.png](single_rolled.png)
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
![many_selected.png](many_selected.png)
![many_rolled.png](many_rolled.png)
## Controls
App uses touchscreen to cycle through different dice, and accelerometer/BTN to roll them

View File

@ -0,0 +1 @@
atob("MDABAAAAA8AAAAAAB+AAf//+DDAAwAACGBgAwAADMAwAwAADYAYAgAAAwYMAgAABg+GAgAADBiDAgDwGBiBggH4MAmAwgMMYA8AYgIEwAAAMgIFg8AeGgIHBkAyDgMNBGAjDgH5BGAjDgDxh8A+DgAAwYAMGgAAYAYAMgAAMA+AYwAAGBiAwwAADBiBgwAADgmDAf//+w8Gwf///4AM8wAADMAYEzwADGA3En4ABDBvkmYABBjIkmYABI+IkH4ABPAPkjwABHAHEgDwBAHAEAH4BAPgEgGYBAIgEgGYBAIgEgH4BAPgEgDwBAHAEgADxHAHEgAH5PgPkgAGZIgIkgAGZIgIkgAH5PgPkwADzHAHEwAADAAAEcAAPwAAcP//8///w")

185
apps/multidice/app.js Normal file
View File

@ -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});

BIN
apps/multidice/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -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}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
apps/multidice/startup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -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

View File

@ -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.",

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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",

View File

@ -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",

View File

@ -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"}]
}

View File

@ -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.

View File

@ -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();

View File

@ -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",

View File

@ -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 => {

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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;

View File

@ -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

View File

@ -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",

View File

@ -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');

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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"}],

View File

@ -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.

View File

@ -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.

View File

@ -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();

View File

@ -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"}]
}

View File

@ -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();
},
}
});
});

View File

@ -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,
});
}

View File

@ -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

View File

@ -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",

View File

@ -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

View File

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

View File

@ -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"}
]
}

View File

@ -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);
};
})();

BIN
apps/widbtstates/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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);
};
})();

View File

@ -1 +1,2 @@
0.01: First commit
0.02: Add tap-to-lock functionality

View File

@ -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",

View File

@ -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);

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