Merge branch 'espruino:master' into iosWeatherUpdates
commit
2d8b1e8218
|
|
@ -16,3 +16,4 @@
|
||||||
0.15: Fix error when displaying a category with only one clockinfo (fix #3728)
|
0.15: Fix error when displaying a category with only one clockinfo (fix #3728)
|
||||||
0.16: Add BLE clkinfo entry
|
0.16: Add BLE clkinfo entry
|
||||||
0.17: Fix BLE icon alignment and border on some clocks
|
0.17: Fix BLE icon alignment and border on some clocks
|
||||||
|
0.18: Tweak BLE icon to add gap and ensure middle of B isn't filled
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,8 @@ exports.load = function() {
|
||||||
get: function() {
|
get: function() {
|
||||||
return {
|
return {
|
||||||
text: this.isOn() ? "On" : "Off",
|
text: this.isOn() ? "On" : "Off",
|
||||||
img: atob("GBiBAAAAAAAAAAAYAAAcAAAWAAATAAARgAMRgAGTAADWAAB8AAA4AAA4AAB8AADWAAGTAAMRgAARgAATAAAWAAAcAAAYAAAAAAAAAA==")
|
img: atob("GBiBAAAAAAAAAAAYAAAcAAAWAAATAAARgAMRgAGTAADGAAB8AAA4AAA4AAB8AADGAAGTAAMRgAARgAATAAAWAAAcAAAYAAAAAAAAAA==")
|
||||||
|
// small gaps added to BLE icon to ensure middle of B isn't filled
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
run: function() {
|
run: function() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "clock_info",
|
{ "id": "clock_info",
|
||||||
"name": "Clock Info Module",
|
"name": "Clock Info Module",
|
||||||
"shortName": "Clock Info",
|
"shortName": "Clock Info",
|
||||||
"version":"0.17",
|
"version":"0.18",
|
||||||
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,7 @@
|
||||||
0.30: Minor code improvements
|
0.30: Minor code improvements
|
||||||
0.31: Add support for new health format (storing more data)
|
0.31: Add support for new health format (storing more data)
|
||||||
Added graphs for Temperature and Battery
|
Added graphs for Temperature and Battery
|
||||||
0.32: If getting HRM every 3/10 minutes, don't turn it on if the Bangle is charging or hasn't moved and is face down/up
|
0.32: If getting HRM every 3/10 minutes, don't turn it on if the Bangle is charging or hasn't moved and is face down/up
|
||||||
|
0.33: Ensure readAllRecordsSince always includes the current day
|
||||||
|
Speed improvements (put temporary functions in RAM where possible)
|
||||||
|
0.34: Fix readFullDatabase (was skipping first month of data)
|
||||||
|
|
@ -41,23 +41,23 @@ exports.getDecoder = function(fileContents) {
|
||||||
return {
|
return {
|
||||||
r : 10, // record length
|
r : 10, // record length
|
||||||
clr : "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
|
clr : "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
|
||||||
decode : h => { var v = {
|
decode : h => { "ram"; var d = h.charCodeAt.bind(h), v = {
|
||||||
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
|
steps : (d(0)<<8) | d(1),
|
||||||
bpmMin : h.charCodeAt(2),
|
bpmMin : d(2),
|
||||||
bpmMax : h.charCodeAt(3),
|
bpmMax : d(3),
|
||||||
movement : h.charCodeAt(4)*8,
|
movement : d(4)*8,
|
||||||
battery : h.charCodeAt(5)&127,
|
battery : d(5)&127,
|
||||||
isCharging : !!(h.charCodeAt(5)&128),
|
isCharging : !!(d(5)&128),
|
||||||
temperature : h.charCodeAt(6)/2, // signed?
|
temperature : d(6)/2, // signed?
|
||||||
altitude : ((h.charCodeAt(7)&31)<<8)|h.charCodeAt(8), // signed?
|
altitude : ((d(7)&31)<<8)|d(8), // signed?
|
||||||
activity : exports.ACTIVITY[h.charCodeAt(7)>>5]
|
activity : exports.ACTIVITY[d(7)>>5]
|
||||||
};
|
};
|
||||||
if (v.temperature>80) v.temperature-=128;
|
if (v.temperature>80) v.temperature-=128;
|
||||||
v.bpm = (v.bpmMin+v.bpmMax)/2;
|
v.bpm = (v.bpmMin+v.bpmMax)/2;
|
||||||
if (v.altitude > 7500) v.altitude-=8192;
|
if (v.altitude > 7500) v.altitude-=8192;
|
||||||
return v;
|
return v;
|
||||||
},
|
},
|
||||||
encode : health => {var alt=health.altitude&8191;return String.fromCharCode(
|
encode : health => { "ram"; var alt=health.altitude&8191;return String.fromCharCode(
|
||||||
health.steps>>8,health.steps&255, // 16 bit steps
|
health.steps>>8,health.steps&255, // 16 bit steps
|
||||||
health.bpmMin || health.bpm, // 8 bit bpm
|
health.bpmMin || health.bpm, // 8 bit bpm
|
||||||
health.bpmMax || health.bpm, // 8 bit bpm
|
health.bpmMax || health.bpm, // 8 bit bpm
|
||||||
|
|
@ -66,23 +66,24 @@ exports.getDecoder = function(fileContents) {
|
||||||
0|Math.round(health.temperature*2),
|
0|Math.round(health.temperature*2),
|
||||||
(alt>>8)|(Math.max(0,exports.ACTIVITY.indexOf(health.activity))<<5),alt&255,
|
(alt>>8)|(Math.max(0,exports.ACTIVITY.indexOf(health.activity))<<5),alt&255,
|
||||||
0 // tbd
|
0 // tbd
|
||||||
)}
|
);}
|
||||||
};
|
};
|
||||||
} else { // HEALTH1
|
} else { // HEALTH1
|
||||||
return {
|
return {
|
||||||
r : 4, // record length
|
r : 4, // record length
|
||||||
clr : "\xFF\xFF\xFF\xFF",
|
clr : "\xFF\xFF\xFF\xFF",
|
||||||
decode : h => ({
|
decode : h => { "ram"; return {
|
||||||
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
|
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
|
||||||
bpm : h.charCodeAt(2),
|
bpm : h.charCodeAt(2),
|
||||||
bpmMin : h.charCodeAt(2),
|
bpmMin : h.charCodeAt(2),
|
||||||
bpmMax : h.charCodeAt(2),
|
bpmMax : h.charCodeAt(2),
|
||||||
movement : h.charCodeAt(3)*8
|
movement : h.charCodeAt(3)*8
|
||||||
}),
|
};},
|
||||||
encode : health => String.fromCharCode(
|
encode : health => { "ram"; return String.fromCharCode(
|
||||||
health.steps>>8,health.steps&255, // 16 bit steps
|
health.steps>>8,health.steps&255, // 16 bit steps
|
||||||
health.bpm, // 8 bit bpm
|
health.bpm, // 8 bit bpm
|
||||||
Math.min(health.movement, 255))
|
Math.min(health.movement, 255));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -111,12 +112,10 @@ exports.readAllRecords = function(d, cb) {
|
||||||
// Read the entire database. There is no guarantee that the months are read in order.
|
// Read the entire database. There is no guarantee that the months are read in order.
|
||||||
exports.readFullDatabase = function(cb) {
|
exports.readFullDatabase = function(cb) {
|
||||||
require("Storage").list(/health-[0-9]+-[0-9]+.raw/).forEach(val => {
|
require("Storage").list(/health-[0-9]+-[0-9]+.raw/).forEach(val => {
|
||||||
console.log(val);
|
|
||||||
var parts = val.split('-');
|
var parts = val.split('-');
|
||||||
var y = parseInt(parts[1],10);
|
var y = parseInt(parts[1],10);
|
||||||
var mo = parseInt(parts[2].replace('.raw', ''),10);
|
var mo = parseInt(parts[2].replace('.raw', ''),10) - 1;
|
||||||
|
exports.readAllRecords(new Date(y, mo, 1), (r) => {"ram";
|
||||||
exports.readAllRecords(new Date(y, mo, 1), (r) => {
|
|
||||||
r.date = new Date(y, mo, r.day, r.hr, r.min);
|
r.date = new Date(y, mo, r.day, r.hr, r.min);
|
||||||
cb(r);
|
cb(r);
|
||||||
});
|
});
|
||||||
|
|
@ -127,9 +126,9 @@ exports.readFullDatabase = function(cb) {
|
||||||
// There may be some records for the day of the timestamp previous to the timestamp
|
// There may be some records for the day of the timestamp previous to the timestamp
|
||||||
exports.readAllRecordsSince = function(d, cb) {
|
exports.readAllRecordsSince = function(d, cb) {
|
||||||
var currentDate = new Date().getTime();
|
var currentDate = new Date().getTime();
|
||||||
var di = d;
|
var di = new Date(d.toISOString().substr(0,10)); // copy date (ignore time)
|
||||||
while (di.getTime() <= currentDate) {
|
while (di.getTime() <= currentDate) {
|
||||||
exports.readDay(di, (r) => {
|
exports.readDay(di, (r) => {"ram";
|
||||||
r.date = new Date(di.getFullYear(), di.getMonth(), di.getDate(), r.hr, r.min);
|
r.date = new Date(di.getFullYear(), di.getMonth(), di.getDate(), r.hr, r.min);
|
||||||
cb(r);
|
cb(r);
|
||||||
});
|
});
|
||||||
|
|
@ -149,7 +148,7 @@ exports.readDailySummaries = function(d, cb) {
|
||||||
if (h!=inf.clr) cb(Object.assign(inf.decode(h), {day:day+1}));
|
if (h!=inf.clr) cb(Object.assign(inf.decode(h), {day:day+1}));
|
||||||
idx += DB_RECORDS_PER_DAY*inf.r;
|
idx += DB_RECORDS_PER_DAY*inf.r;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Read all records from the given day
|
// Read all records from the given day
|
||||||
exports.readDay = function(d, cb) {
|
exports.readDay = function(d, cb) {
|
||||||
|
|
@ -167,4 +166,4 @@ exports.readDay = function(d, cb) {
|
||||||
idx += inf.r;
|
idx += inf.r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "health",
|
"id": "health",
|
||||||
"name": "Health Tracking",
|
"name": "Health Tracking",
|
||||||
"shortName": "Health",
|
"shortName": "Health",
|
||||||
"version": "0.32",
|
"version": "0.34",
|
||||||
"description": "Logs health data and provides an app to view it",
|
"description": "Logs health data and provides an app to view it",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,system,health",
|
"tags": "tool,system,health",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
Measurements from the build in PPG-Sensor (Photoplethysmograph) is sensitive to motion and can be corrupted with Motion Artifacts (MA). This module allows to remove these.
|
Measurements from the build in PPG-Sensor (Photoplethysmograph) is sensitive to motion and can be corrupted with Motion Artifacts (MA). This module allows to remove these.
|
||||||
|
|
||||||
|
**WARNING:** On Bangle.js 2 this has been found to make heart rate readings [substantially less accurate in some cases](https://github.com/orgs/espruino/discussions/7738#discussioncomment-13594093) (the HRM already has built in motion artefact removal).
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
* **MA removal**
|
* **MA removal**
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"shortName":"HRM MA removal",
|
"shortName":"HRM MA removal",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.02",
|
"version":"0.02",
|
||||||
"description": "Removes Motion Artifacts in Bangle.js's heart rate sensor data.",
|
"description": "Removes Motion Artifacts in Bangle.js's heart rate sensor data. **WARNING:** On Bangle.js 2 this has been found to make heart rate readings substantially less accurate in some cases.",
|
||||||
"type": "bootloader",
|
"type": "bootloader",
|
||||||
"tags": "health",
|
"tags": "health",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Japanese Walking Timer
|
||||||
|
|
||||||
|
A simple timer designed to help you manage your walking intervals, whether you're in a relaxed mode or an intense workout!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
- The timer starts with a default total duration and interval duration, which can be adjusted in the settings.
|
||||||
|
- Tap the screen to pause or resume the timer.
|
||||||
|
- The timer will switch modes between "Relax" and "Intense" at the end of each interval.
|
||||||
|
- The display shows the current time, the remaining interval time, and the total time left.
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
[Fabian Köll] ([Koell](https://github.com/Koell))
|
||||||
|
|
||||||
|
|
||||||
|
## Icon
|
||||||
|
|
||||||
|
[Icon](https://www.koreanwikiproject.com/wiki/images/2/2f/%E8%A1%8C.png)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4cA///A4IDBvvv11zw0xlljjnnJ3USoARP0uICJ+hnOACJ8mkARO9Mn0AGDhP2FQ8FhM9L4nyyc4CI0OpJZBgVN//lkmSsARGnlMPoMH2mSpMkzPQCAsBoViAgMC/WTt2T2giGhUTiBWDm3SU5FQ7yNOgeHum7Ypu+3sB5rFMgP3tEB5MxBg2X//+yAFBOIKhBngcFn8pkmTO4ShFAAUT+cSSQOSpgKDlihCPoN/mIOBCIVvUIsBk//zWStOz////u27QRCheTzEOtVJnV+6070BgGj2a4EL5V39MAgkm2ARGvGbNwMkOgUHknwCAsC43DvAIEg8mGo0Um+yCI0nkARF0O8nQjHCIsFh1gCJ08WwM6rARLgftNAMzCIsDI4te4gDBuYRM/pxCCJoADCI6PHdINDCI0kYo8BqYRHYowRByZ9GCJEDCLXACLVQAoUL+mXCJBrBiARD7clCJNzBIl8pIRIgEuwBGExMmUI4qH9MnYo4AH3MxCB0Ai/oCJ4AY"))
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
// === Utility Functions ===
|
||||||
|
function formatTime(seconds) {
|
||||||
|
let mins = Math.floor(seconds / 60);
|
||||||
|
let secs = (seconds % 60).toString().padStart(2, '0');
|
||||||
|
return `${mins}:${secs}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimeStr() {
|
||||||
|
let d = new Date();
|
||||||
|
return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCachedLeftTime() {
|
||||||
|
cachedLeftTime = "Left: " + formatTime(state.remainingTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Constants ===
|
||||||
|
const FILE = "jwalk.json";
|
||||||
|
const DEFAULTS = {
|
||||||
|
totalDuration: 30,
|
||||||
|
intervalDuration: 3,
|
||||||
|
startMode: 0,
|
||||||
|
modeBuzzerDuration: 1000,
|
||||||
|
finishBuzzerDuration: 1500,
|
||||||
|
showClock: 1,
|
||||||
|
updateWhileLocked: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// === Settings and State ===
|
||||||
|
let settings = require("Storage").readJSON(FILE, 1) || DEFAULTS;
|
||||||
|
|
||||||
|
let state = {
|
||||||
|
remainingTotal: settings.totalDuration * 60,
|
||||||
|
intervalDuration: settings.intervalDuration * 60,
|
||||||
|
remainingInterval: 0,
|
||||||
|
intervalEnd: 0,
|
||||||
|
paused: false,
|
||||||
|
currentMode: settings.startMode === 1 ? "Intense" : "Relax",
|
||||||
|
finished: false,
|
||||||
|
forceDraw: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cachedLeftTime = "";
|
||||||
|
let lastMinuteStr = getTimeStr();
|
||||||
|
let drawTimerInterval;
|
||||||
|
|
||||||
|
// === UI Rendering ===
|
||||||
|
function drawUI() {
|
||||||
|
let y = Bangle.appRect.y + 8;
|
||||||
|
g.reset().setBgColor(g.theme.bg).clearRect(Bangle.appRect);
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
|
||||||
|
let displayInterval = state.paused
|
||||||
|
? state.remainingInterval
|
||||||
|
: Math.max(0, Math.floor((state.intervalEnd - Date.now()) / 1000));
|
||||||
|
|
||||||
|
g.setFont("Vector", 40);
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
g.drawString(formatTime(displayInterval), g.getWidth() / 2, y + 70);
|
||||||
|
|
||||||
|
let cy = y + 100;
|
||||||
|
if (state.paused) {
|
||||||
|
g.setFont("Vector", 15);
|
||||||
|
g.drawString("PAUSED", g.getWidth() / 2, cy);
|
||||||
|
} else {
|
||||||
|
let cx = g.getWidth() / 2;
|
||||||
|
g.setColor(g.theme.accent || g.theme.fg2 || g.theme.fg);
|
||||||
|
if (state.currentMode === "Relax") {
|
||||||
|
g.fillCircle(cx, cy, 5);
|
||||||
|
} else {
|
||||||
|
g.fillPoly([
|
||||||
|
cx, cy - 6,
|
||||||
|
cx - 6, cy + 6,
|
||||||
|
cx + 6, cy + 6
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setFont("6x8", 2);
|
||||||
|
g.setFontAlign(0, -1);
|
||||||
|
g.drawString(state.currentMode, g.getWidth() / 2, y + 15);
|
||||||
|
g.drawString(cachedLeftTime, g.getWidth() / 2, cy + 15);
|
||||||
|
|
||||||
|
if (settings.showClock) {
|
||||||
|
g.setFontAlign(1, 0);
|
||||||
|
g.drawString(lastMinuteStr, g.getWidth() - 4, y);
|
||||||
|
}
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Workout Logic ===
|
||||||
|
function toggleMode() {
|
||||||
|
state.currentMode = state.currentMode === "Relax" ? "Intense" : "Relax";
|
||||||
|
Bangle.buzz(settings.modeBuzzerDuration);
|
||||||
|
state.forceDraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startNextInterval() {
|
||||||
|
if (state.remainingTotal <= 0) {
|
||||||
|
finishWorkout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.remainingInterval = Math.min(state.intervalDuration, state.remainingTotal);
|
||||||
|
state.remainingTotal -= state.remainingInterval;
|
||||||
|
updateCachedLeftTime();
|
||||||
|
state.intervalEnd = Date.now() + state.remainingInterval * 1000;
|
||||||
|
state.forceDraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePause() {
|
||||||
|
if (state.finished) return;
|
||||||
|
|
||||||
|
if (!state.paused) {
|
||||||
|
state.remainingInterval = Math.max(0, Math.floor((state.intervalEnd - Date.now()) / 1000));
|
||||||
|
state.paused = true;
|
||||||
|
} else {
|
||||||
|
state.intervalEnd = Date.now() + state.remainingInterval * 1000;
|
||||||
|
state.paused = false;
|
||||||
|
}
|
||||||
|
drawUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishWorkout() {
|
||||||
|
clearInterval(drawTimerInterval);
|
||||||
|
Bangle.buzz(settings.finishBuzzerDuration);
|
||||||
|
state.finished = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
g.clear();
|
||||||
|
g.setFont("Vector", 30);
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
g.drawString("Well done!", g.getWidth() / 2, g.getHeight() / 2);
|
||||||
|
g.flip();
|
||||||
|
|
||||||
|
const exitHandler = () => {
|
||||||
|
Bangle.removeListener("touch", exitHandler);
|
||||||
|
Bangle.removeListener("btn1", exitHandler);
|
||||||
|
load(); // Exit app
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.on("touch", exitHandler);
|
||||||
|
setWatch(exitHandler, BTN1, { repeat: false });
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Timer Tick ===
|
||||||
|
function tick() {
|
||||||
|
if (state.finished) return;
|
||||||
|
|
||||||
|
const currentMinuteStr = getTimeStr();
|
||||||
|
if (currentMinuteStr !== lastMinuteStr) {
|
||||||
|
lastMinuteStr = currentMinuteStr;
|
||||||
|
state.forceDraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.paused && (state.intervalEnd - Date.now()) / 1000 <= 0) {
|
||||||
|
toggleMode();
|
||||||
|
startNextInterval();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.forceDraw || settings.updateWhileLocked || !Bangle.isLocked()) {
|
||||||
|
drawUI();
|
||||||
|
state.forceDraw = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Initialization ===
|
||||||
|
Bangle.on("touch", togglePause);
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
updateCachedLeftTime();
|
||||||
|
startNextInterval();
|
||||||
|
drawUI();
|
||||||
|
drawTimerInterval = setInterval(tick, 1000);
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"id": "jwalk",
|
||||||
|
"name": "Japanese Walking",
|
||||||
|
"shortName": "J-Walk",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Alternating walk timer: 3 min Relax / 3 min Intense for a set time. Tap to pause/resume. Start mode, interval and total time configurable via Settings.",
|
||||||
|
"tags": "walk,timer,fitness",
|
||||||
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"data": [
|
||||||
|
{ "name": "jwalk.json" }
|
||||||
|
],
|
||||||
|
"storage": [
|
||||||
|
{ "name": "jwalk.app.js", "url": "app.js" },
|
||||||
|
{ "name": "jwalk.settings.js", "url": "settings.js" },
|
||||||
|
{ "name": "jwalk.img", "url": "app-icon.js", "evaluate": true }
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
|
|
@ -0,0 +1,65 @@
|
||||||
|
(function (back) {
|
||||||
|
const FILE = "jwalk.json";
|
||||||
|
const DEFAULTS = {
|
||||||
|
totalDuration: 30,
|
||||||
|
intervalDuration: 3,
|
||||||
|
startMode: 0,
|
||||||
|
modeBuzzerDuration: 1000,
|
||||||
|
finishBuzzerDuration: 1500,
|
||||||
|
showClock: 1,
|
||||||
|
updateWhileLocked: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let settings = require("Storage").readJSON(FILE, 1) || DEFAULTS;
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
require("Storage").writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSettingsMenu() {
|
||||||
|
E.showMenu({
|
||||||
|
'': { title: 'Japanese Walking' },
|
||||||
|
'< Back': back,
|
||||||
|
'Total Time (min)': {
|
||||||
|
value: settings.totalDuration,
|
||||||
|
min: 10, max: 60, step: 1,
|
||||||
|
onchange: v => { settings.totalDuration = v; saveSettings(); }
|
||||||
|
},
|
||||||
|
'Interval (min)': {
|
||||||
|
value: settings.intervalDuration,
|
||||||
|
min: 1, max: 10, step: 1,
|
||||||
|
onchange: v => { settings.intervalDuration = v; saveSettings(); }
|
||||||
|
},
|
||||||
|
'Start Mode': {
|
||||||
|
value: settings.startMode,
|
||||||
|
min: 0, max: 1,
|
||||||
|
format: v => v ? "Intense" : "Relax",
|
||||||
|
onchange: v => { settings.startMode = v; saveSettings(); }
|
||||||
|
},
|
||||||
|
'Display Clock': {
|
||||||
|
value: settings.showClock,
|
||||||
|
min: 0, max: 1,
|
||||||
|
format: v => v ? "Show" : "Hide" ,
|
||||||
|
onchange: v => { settings.showClock = v; saveSettings(); }
|
||||||
|
},
|
||||||
|
'Update UI While Locked': {
|
||||||
|
value: settings.updateWhileLocked,
|
||||||
|
min: 0, max: 1,
|
||||||
|
format: v => v ? "Always" : "On Change",
|
||||||
|
onchange: v => { settings.updateWhileLocked = v; saveSettings(); }
|
||||||
|
},
|
||||||
|
'Mode Buzz (ms)': {
|
||||||
|
value: settings.modeBuzzerDuration,
|
||||||
|
min: 0, max: 2000, step: 50,
|
||||||
|
onchange: v => { settings.modeBuzzerDuration = v; saveSettings(); }
|
||||||
|
},
|
||||||
|
'Finish Buzz (ms)': {
|
||||||
|
value: settings.finishBuzzerDuration,
|
||||||
|
min: 0, max: 5000, step: 100,
|
||||||
|
onchange: v => { settings.finishBuzzerDuration = v; saveSettings(); }
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showSettingsMenu();
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
0.01: App created w/ clockInfos, bold font, date and time.
|
||||||
|
0.02: Added text truncation for long ClockInfo strings.
|
||||||
|
0.03: Added small inline Clock Info next to date
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Modern Clock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A beautifully simple, modern clock with three Clock Infos, and a clean UI. Fast-Loads.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Has 3 Clock Infos, that are individually changeable.
|
||||||
|
* Has an inline Clock Info, next to the date, for quick, glanceable data.
|
||||||
|
* Low battery consumption.
|
||||||
|
* Uses locale for time and date.
|
||||||
|
* Bold time font, for quicker readability.
|
||||||
|
* Uses rounded rectangles and a bold font for a simple, clean, modern look.
|
||||||
|
* Has Fast Loading, for quicker access to launcher.
|
||||||
|
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
RKBoss6
|
||||||
|
|
||||||
|
Github: https://github.com/RKBoss6
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4kA///00x3u+pPK8+xxXCxHCn+/xO6tO6o0ZjFt+sjw0xudkhHCMf4A/AH4A/ADECkUiuUnBAe7AAm85gAK5YWChYRMAA2wC4Xq1QAQ13gC4cRAA3RBA8RC4/dCYfdAoIADjQXJjvRCYMdAIIFBjuqBAMaD4Ws4AvG6NB6NNGQMdilEokdAQNJjseC40d6lU7FdC4VImc2ptGmc1poXFjoYBC4PZqgXCouEouNo1E+sajxfELAIXIpoXBAYQXHF4U/+2EC4YVBzoXBpIXBL4oYB6lTmwXBSQNIC4lFO4LXIRIJHBUwIXCptIC4NKI5AqB7GU6OIC4ITBR4IXGvYvEC4IvBC4NDn/zxtDskzU4IXGa4JhBwjzC7MztAvBxE4jscF5EROoJfCAoJRCEgMa1wXFFwQYCAYQbCBgQDBC4aPFGAYGFAga/F1Xd1QCD1vRAoXdjUaAoWsC4e85nB5gCD5kc1kRBAXBiMcAoJHCgW7AAu8AYYdCAggWBAAMiCwvLB4YAF5YWDAAMMCBAAG0AXKNIRrChfsBZAXGBQoABC5QA/AH4A/AFgA=="))
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"id": "modclock",
|
||||||
|
"name": "Modern Clock",
|
||||||
|
"shortName":"Modern Clk",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"version":"0.03",
|
||||||
|
"description": "A modern, simple clock, with three ClockInfos and Fast Loading",
|
||||||
|
"type":"clock",
|
||||||
|
"tags": "clock,clkinfo",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"screenshots" : [ { "url":"Scr1.png" },
|
||||||
|
{ "url":"Scr2.png" } ],
|
||||||
|
"dependencies" : { "clock_info":"module"},
|
||||||
|
"allow_emulator":true,
|
||||||
|
"readme":"README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"modclock.app.js","url":"app.js"},
|
||||||
|
{"name":"modclock.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -8,11 +8,20 @@ This is Bangle.js's main settings menu:
|
||||||
* **Alerts** - Set how Bangle.js alerts you (including Quiet mode)
|
* **Alerts** - Set how Bangle.js alerts you (including Quiet mode)
|
||||||
* **Utils** - Utilities, including resetting settings
|
* **Utils** - Utilities, including resetting settings
|
||||||
|
|
||||||
|
**New Users:** these are some settings you'll probably want to change right away:
|
||||||
|
|
||||||
|
* **Calibrate LCD** Make sure that the LCD touchscreen responds to touches where you expect them to
|
||||||
|
* **Locale** Set whether you want 12 hour time, and what day of the week the week starts on.
|
||||||
|
|
||||||
See below for options under each heading:
|
See below for options under each heading:
|
||||||
|
|
||||||
|
## Apps - App-specific settings
|
||||||
|
|
||||||
|
This is where you adjust settings for an individual app. (eg. Health app: Adjust how often heart rate tracking should fire.)
|
||||||
|
|
||||||
## System - System settings
|
## System - System settings
|
||||||
|
|
||||||
* **Theme** Adjust the colour scheme
|
* **Theme** Adjust the colour scheme. Choose between light mode, dark mode, or a custom theme. To adjust themes in more detail you can also use the [Theme Switcher App](https://banglejs.com/apps/?id=themesetter)
|
||||||
* **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below.
|
* **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below.
|
||||||
* **Locale** set time zone, the time format (12/24h, for supported clocks) and the first day of the week
|
* **Locale** set time zone, the time format (12/24h, for supported clocks) and the first day of the week
|
||||||
* **Clock** if you have more than one clock face, select the default one
|
* **Clock** if you have more than one clock face, select the default one
|
||||||
|
|
@ -44,9 +53,9 @@ See below for options under each heading:
|
||||||
* **Rotation** allows you to rotate (or mirror) what's displayed on the screen, eg. for left-handed wearers (needs 2v16 or 2v15 cutting edge firmware to work reliably)
|
* **Rotation** allows you to rotate (or mirror) what's displayed on the screen, eg. for left-handed wearers (needs 2v16 or 2v15 cutting edge firmware to work reliably)
|
||||||
* **Wake on X** should the given activity wake up the Bangle.js LCD?
|
* **Wake on X** should the given activity wake up the Bangle.js LCD?
|
||||||
* On Bangle.js 2 when locked the touchscreen is turned off to save power. Because of this,
|
* On Bangle.js 2 when locked the touchscreen is turned off to save power. Because of this,
|
||||||
`Wake on Touch` actually uses the accelerometer, and you need to actually tap the display to wake Bangle.js (we recently renamed the menu item to `Wake on Tap`).
|
`Wake on Tap` actually uses the accelerometer, and you need to actually tap the display to wake Bangle.js.
|
||||||
* **Twist X** these options adjust the sensitivity of `Wake on Twist` to ensure Bangle.js wakes up with just the right amount of wrist movement.
|
* **Twist X** these options adjust the sensitivity of `Wake on Twist` to ensure Bangle.js wakes up with just the right amount of wrist movement.
|
||||||
* **Calibrate** on Bangle.js 2, pop up a screen allowing you to calibrate the touchscreen (calibration only works on 2v16 or 2v15 cutting edge builds)
|
* **Calibrate** on Bangle.js 2, pop up a screen allowing you to calibrate the touchscreen, ensuring your touches are mapped to the right place on the screen. (Highly reccomended for new users!)
|
||||||
|
|
||||||
## Locale
|
## Locale
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,4 @@
|
||||||
0.17: Minor code improvements
|
0.17: Minor code improvements
|
||||||
0.18: Add back as a function to prevent translation making it a menu entry
|
0.18: Add back as a function to prevent translation making it a menu entry
|
||||||
0.19: Write sleep state into health event's .activity field
|
0.19: Write sleep state into health event's .activity field
|
||||||
|
0.20: Increase default sleep thresholds, tweak settings to allow higher ones to be chosen
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ global.sleeplog = {
|
||||||
// threshold settings
|
// threshold settings
|
||||||
maxAwake: 36E5, // [ms] maximal awake time to count for consecutive sleep
|
maxAwake: 36E5, // [ms] maximal awake time to count for consecutive sleep
|
||||||
minConsec: 18E5, // [ms] minimal time to count for consecutive sleep
|
minConsec: 18E5, // [ms] minimal time to count for consecutive sleep
|
||||||
deepTh: 100, // threshold for deep sleep
|
deepTh: 150, // threshold for deep sleep
|
||||||
lightTh: 200, // threshold for light sleep
|
lightTh: 300, // threshold for light sleep
|
||||||
wearTemp: 19.5, // temperature threshold to count as worn
|
wearTemp: 19.5, // temperature threshold to count as worn
|
||||||
}, require("Storage").readJSON("sleeplog.json", true) || {})
|
}, require("Storage").readJSON("sleeplog.json", true) || {})
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id":"sleeplog",
|
"id":"sleeplog",
|
||||||
"name":"Sleep Log",
|
"name":"Sleep Log",
|
||||||
"shortName": "SleepLog",
|
"shortName": "SleepLog",
|
||||||
"version": "0.19",
|
"version": "0.20",
|
||||||
"description": "Log and view your sleeping habits. This app uses built in movement calculations. View data from Bangle, or from the web app.",
|
"description": "Log and view your sleeping habits. This app uses built in movement calculations. View data from Bangle, or from the web app.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@
|
||||||
// threshold settings
|
// threshold settings
|
||||||
maxAwake: 36E5, // [ms] maximal awake time to count for consecutive sleep
|
maxAwake: 36E5, // [ms] maximal awake time to count for consecutive sleep
|
||||||
minConsec: 18E5, // [ms] minimal time to count for consecutive sleep
|
minConsec: 18E5, // [ms] minimal time to count for consecutive sleep
|
||||||
deepTh: 100, // threshold for deep sleep
|
deepTh: 150, // threshold for deep sleep
|
||||||
lightTh: 200, // threshold for light sleep
|
lightTh: 300, // threshold for light sleep
|
||||||
wearTemp: 19.5, // temperature threshold to count as worn
|
wearTemp: 19.5, // temperature threshold to count as worn
|
||||||
// app settings
|
// app settings
|
||||||
breakToD: 12, // [h] time of day when to start/end graphs
|
breakToD: 12, // [h] time of day when to start/end graphs
|
||||||
|
|
@ -324,9 +324,9 @@
|
||||||
},
|
},
|
||||||
/*LANG*/"Deep Sleep": {
|
/*LANG*/"Deep Sleep": {
|
||||||
value: settings.deepTh,
|
value: settings.deepTh,
|
||||||
step: 1,
|
step: 10,
|
||||||
min: 30,
|
min: 30,
|
||||||
max: 200,
|
max: 500,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
noList: true,
|
noList: true,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
|
|
@ -338,7 +338,7 @@
|
||||||
value: settings.lightTh,
|
value: settings.lightTh,
|
||||||
step: 10,
|
step: 10,
|
||||||
min: 100,
|
min: 100,
|
||||||
max: 400,
|
max: 800,
|
||||||
wrap: true,
|
wrap: true,
|
||||||
noList: true,
|
noList: true,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,5 @@
|
||||||
0.24: Redraw clock_info on update and provide color field for condition
|
0.24: Redraw clock_info on update and provide color field for condition
|
||||||
0.25: Added monochrome parameter to drawIcon in lib
|
0.25: Added monochrome parameter to drawIcon in lib
|
||||||
0.26: Expose update function (for use by iOS integration)
|
0.26: Expose update function (for use by iOS integration)
|
||||||
0.27: Add UV index display
|
0.27: Add UV index display
|
||||||
|
0.28: Fix UV positioning, hide when 0
|
||||||
|
|
@ -7,8 +7,11 @@ It also adds a ClockInfo list to Bangle.js.
|
||||||
You can view the full report through the app:
|
You can view the full report through the app:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## iOS Setup
|
## iOS Setup
|
||||||
Use the iOS shortcut [here](https://www.icloud.com/shortcuts/dbf7159200d945179e0938c15e64f102). The shortcut uses Apple Weather for weather updates, and sends a notification, which is read by Bangle.js. To push weather every hour, or interval, you will need to create a shortcut automation for every time you want to push the weather.
|
|
||||||
|
Use the iOS shortcut [here](https://www.icloud.com/shortcuts/73be0ce1076446f3bdc45a5707de5c4d). The shortcut uses Apple Weather for weather updates, and sends a notification, which is read by Bangle.js. To push weather every hour, or interval, you will need to create a shortcut automation for every time you want to push the weather.
|
||||||
|
|
||||||
## Android Setup
|
## Android Setup
|
||||||
|
|
||||||
1. Install [Gadgetbridge for Android](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/) on your phone.
|
1. Install [Gadgetbridge for Android](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/) on your phone.
|
||||||
|
|
@ -51,8 +54,8 @@ When you first load QuickWeather, it will take you through the setup process. Yo
|
||||||
|
|
||||||
**Note:** at one time, the Weather Notification app also worked with Gadgetbridge. However, many users are reporting it's no longer seeing the OpenWeatherMap API key as valid. The app has not received any updates since August of 2020, and may be unmaintained.
|
**Note:** at one time, the Weather Notification app also worked with Gadgetbridge. However, many users are reporting it's no longer seeing the OpenWeatherMap API key as valid. The app has not received any updates since August of 2020, and may be unmaintained.
|
||||||
|
|
||||||
|
|
||||||
## Clock Infos
|
## Clock Infos
|
||||||
|
|
||||||
Tap on any clockInfo when focused to directly open the weather app.
|
Tap on any clockInfo when focused to directly open the weather app.
|
||||||
Adds:
|
Adds:
|
||||||
* Condition ClockInfo with condition icon
|
* Condition ClockInfo with condition icon
|
||||||
|
|
@ -60,6 +63,7 @@ Adds:
|
||||||
* Wind speed ClockInfo.
|
* Wind speed ClockInfo.
|
||||||
* Chance of rain ClockInfo.
|
* Chance of rain ClockInfo.
|
||||||
* Temperature ClockInfo without condition icon.
|
* Temperature ClockInfo without condition icon.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
* Expiration timespan can be set after which the local weather data is considered as invalid
|
* Expiration timespan can be set after which the local weather data is considered as invalid
|
||||||
|
|
|
||||||
|
|
@ -9,38 +9,9 @@ Bangle.loadWidgets();
|
||||||
var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
|
var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
|
||||||
{filly: 1},
|
{filly: 1},
|
||||||
{type: "h", filly: 0, c: [
|
{type: "h", filly: 0, c: [
|
||||||
{type: "v", width: g.getWidth()/2, c: [ // Vertical container for icon + UV
|
{type: "v", width: g.getWidth()/2, c: [ // Vertical container for icon
|
||||||
{type: "custom", fillx: 1, height: g.getHeight()/2 - 30, valign: -1, txt: "unknown", id: "icon",
|
{type: "custom", fillx: 1, height: g.getHeight()/2 - 30, valign: -1, txt: "unknown", id: "icon",
|
||||||
render: l => weather.drawIcon(l, l.x+l.w/2, l.y+l.h/2, l.w/2-10)},
|
render: l => weather.drawIcon(l, l.x+l.w/2, l.y+l.h/2, l.w/2-5)},
|
||||||
{type: "custom", fillx: 1, height: 20, id: "uvDisplay",
|
|
||||||
render: l => {
|
|
||||||
if (!current || current.uv === undefined) return;
|
|
||||||
const uv = Math.min(parseInt(current.uv), 11); // Cap at 11
|
|
||||||
|
|
||||||
// UV color thresholds: [max_value, color] based on WHO standards
|
|
||||||
const colors = [[2,"#0F0"], [5,"#FF0"], [7,"#F80"], [10,"#F00"], [11,"#F0F"]];
|
|
||||||
const color = colors.find(c => uv <= c[0])[1];
|
|
||||||
|
|
||||||
// Setup and measure label
|
|
||||||
g.setFont("6x8").setFontAlign(-1, 0);
|
|
||||||
const label = "UV: ";
|
|
||||||
const labelW = g.stringWidth(label);
|
|
||||||
|
|
||||||
// Calculate centered position (4px block + 1px spacing) * blocks - last spacing
|
|
||||||
const totalW = labelW + uv * 5 - (uv > 0 ? 1 : 0);
|
|
||||||
const x = l.x + (l.w - totalW) / 2;
|
|
||||||
const y = l.y + l.h;
|
|
||||||
|
|
||||||
// Draw label
|
|
||||||
g.setColor(g.theme.fg).drawString(label, x, y);
|
|
||||||
|
|
||||||
// Draw UV blocks
|
|
||||||
g.setColor(color);
|
|
||||||
for (let i = 0; i < uv; i++) {
|
|
||||||
g.fillRect(x + labelW + i * 5, y - 3, x + labelW + i * 5 + 3, y + 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]},
|
]},
|
||||||
{type: "v", fillx: 1, c: [
|
{type: "v", fillx: 1, c: [
|
||||||
{type: "h", pad: 2, c: [
|
{type: "h", pad: 2, c: [
|
||||||
|
|
@ -50,12 +21,43 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
|
||||||
{filly: 1},
|
{filly: 1},
|
||||||
{type: "txt", font: "6x8", pad: 2, halign: 1, label: /*LANG*/"Humidity"},
|
{type: "txt", font: "6x8", pad: 2, halign: 1, label: /*LANG*/"Humidity"},
|
||||||
{type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"},
|
{type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"},
|
||||||
{filly: 1},
|
{type: "txt", font: "6x8", pad: [2, 2, 2, 2], halign: -1, label: /*LANG*/"Wind"},
|
||||||
{type: "txt", font: "6x8", pad: 2, halign: -1, label: /*LANG*/"Wind"},
|
{type: "h", pad: [0, 2, 2, 2], halign: -1, c: [
|
||||||
{type: "h", halign: -1, c: [
|
|
||||||
{type: "txt", font: "9%", pad: 2, id: "wind", label: "00"},
|
{type: "txt", font: "9%", pad: 2, id: "wind", label: "00"},
|
||||||
{type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"},
|
{type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"},
|
||||||
]},
|
]},
|
||||||
|
{type: "custom", fillx: 1, height: 15, id: "uvDisplay",
|
||||||
|
render: l => {
|
||||||
|
if (!current || current.uv === undefined || current.uv === 0) return;
|
||||||
|
const uv = Math.min(parseInt(current.uv), 11); // Cap at 11
|
||||||
|
|
||||||
|
// UV color thresholds: [max_value, color] based on WHO standards
|
||||||
|
const colors = [[2,"#0F0"], [5,"#FF0"], [7,"#F80"], [10,"#F00"], [11,"#F0F"]];
|
||||||
|
const color = colors.find(c => uv <= c[0])[1];
|
||||||
|
const blockH = 8, blockW = 3;
|
||||||
|
|
||||||
|
// Draw UV title and blocks on same line
|
||||||
|
g.setFont("6x8").setFontAlign(-1, 0);
|
||||||
|
const label = "UV";
|
||||||
|
const labelW = g.stringWidth(label);
|
||||||
|
|
||||||
|
const x = l.x + 2;
|
||||||
|
const y = l.y + l.h / 2;
|
||||||
|
|
||||||
|
// Draw title
|
||||||
|
g.setColor(g.theme.fg).drawString(label, x, y);
|
||||||
|
|
||||||
|
// Draw UV blocks after title
|
||||||
|
g.setColor(color);
|
||||||
|
for (let i = 0; i < uv; i++) {
|
||||||
|
const blockX = x + labelW + 4 + i * (blockW + 2);
|
||||||
|
g.fillRect(blockX, y - blockH/2, blockX + blockW, y + blockW/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset graphics state to prevent interference
|
||||||
|
g.reset();
|
||||||
|
}
|
||||||
|
},
|
||||||
]},
|
]},
|
||||||
]},
|
]},
|
||||||
{filly: 1},
|
{filly: 1},
|
||||||
|
|
@ -91,6 +93,7 @@ function draw() {
|
||||||
layout.loc.label = current.loc;
|
layout.loc.label = current.loc;
|
||||||
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; // How to autotranslate this and similar?
|
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; // How to autotranslate this and similar?
|
||||||
layout.update();
|
layout.update();
|
||||||
|
layout.forgetLazyState();
|
||||||
layout.render();
|
layout.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "weather",
|
"id": "weather",
|
||||||
"name": "Weather",
|
"name": "Weather",
|
||||||
"version": "0.27",
|
"version": "0.28",
|
||||||
"description": "Show Gadgetbridge/iOS weather report",
|
"description": "Show Gadgetbridge/iOS weather report",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,4 @@
|
||||||
0.09: Match draw() API e.g. to allow wid_edit to alter this widget
|
0.09: Match draw() API e.g. to allow wid_edit to alter this widget
|
||||||
0.10: Change 4x5 font to 6x8, teletext is now default font
|
0.10: Change 4x5 font to 6x8, teletext is now default font
|
||||||
0.11: Bugfix: handle changes in alarms (e.g. done without a load, such as via fastload)
|
0.11: Bugfix: handle changes in alarms (e.g. done without a load, such as via fastload)
|
||||||
|
0.12: Redraw when screen turns on or watch is unlocked
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "widalarmeta",
|
"id": "widalarmeta",
|
||||||
"name": "Alarm & Timer ETA",
|
"name": "Alarm & Timer ETA",
|
||||||
"shortName": "Alarm ETA",
|
"shortName": "Alarm ETA",
|
||||||
"version": "0.11",
|
"version": "0.12",
|
||||||
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
|
"description": "A widget that displays the time to the next Alarm or Timer in hours and minutes, maximum 24h (configurable).",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
|
|
|
||||||
|
|
@ -133,5 +133,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
Bangle.on("alarmReload", () => WIDGETS["widalarmeta"].reload());
|
Bangle.on("alarmReload", () => WIDGETS["widalarmeta"].reload());
|
||||||
|
Bangle.on("lock", () => WIDGETS["widalarmeta"].draw(WIDGETS["widalarmeta"]))
|
||||||
|
Bangle.on("lcdPower", () => WIDGETS["widalarmeta"].draw(WIDGETS["widalarmeta"]))
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
2
core
2
core
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0916756932699d626555171ce8e0a2989b151c89
|
Subproject commit f17383aeed940690db16f58dcfec5a91b7890391
|
||||||
Loading…
Reference in New Issue