Merge branch 'espruino:master' into Weather-Feels-Like-Updates
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New app! (settings, boot.js).
|
||||
0.02: Fix settings defaulting brightness to 0
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# BackLite
|
||||
### This app needs the latest settings app update (v 0.80), to ensure that setting the brightness to `0` does not default to `1`.
|
||||
|
||||
BackLite is an app which greatly conserves battery life by only turning the backlight on when you long press the button from a locked state.
|
||||
|
||||
Modern watches have a dedicated button to turn the backlight on, so as not to waste battery in an already light environment. This app recreates that functionality for the Bangle.js, which only has one button.
|
||||
|
||||
#### Warning: This app overwrites the LCD brightness setting in `Bangle.js LCD settings`. If it is changed, the app will basically lose functionality. It auto-fixes itself every boot, so if you change the brightness, just reboot :)
|
||||
# Usage
|
||||
When you unlock with a press of the button, or any other way you unlock the watch, the backlight will not turn on, as most of the time you are able to read it, due to the transreflective display on the Bangle.js 2.
|
||||
|
||||
If you press and hold the button to unlock the watch (for around half a second), the backlight will turn on for 5 seconds - just enough to see what you need to see. After that, it will turn off again.
|
||||
|
||||
Some apps like `Light Switch Widget` will prevent this app from working properly.
|
||||
# Settings
|
||||
`Brightness` - The LCD brightness when unlocked with a long press.
|
||||
# Creator
|
||||
RKBoss6
|
||||
|
||||
TODO: Add a setting for long press time, or light duration
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
let getSettings = function(){
|
||||
return Object.assign({
|
||||
// default values
|
||||
brightness: 0.3,
|
||||
|
||||
}, require('Storage').readJSON("BackLite.settings.json", true) || {});
|
||||
};
|
||||
|
||||
|
||||
//Set LCD to zero every reboot
|
||||
let s = require("Storage").readJSON("setting.json", 1) || {};
|
||||
s.brightness = 0;
|
||||
require("Storage").writeJSON("setting.json", s);
|
||||
//remove large settings object from memory
|
||||
delete s;
|
||||
const longPressTime=400; //(ms)
|
||||
|
||||
Bangle.on('lock', function(isLocked) {
|
||||
Bangle.setLCDBrightness(0);
|
||||
|
||||
if (!isLocked) {
|
||||
// Just unlocked — give a short delay and check if BTN1 is still pressed
|
||||
setTimeout(() => {
|
||||
if (digitalRead(BTN1)) {
|
||||
//set brightness until. locked.
|
||||
Bangle.setLCDBrightness(getSettings().brightness);
|
||||
} else {
|
||||
Bangle.setLCDBrightness(0);
|
||||
}
|
||||
}, longPressTime); // Slight delay to allow unlock to settle
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"id": "backlite",
|
||||
"name": "BackLite",
|
||||
"version": "0.02",
|
||||
"description": "Conserves battery life by turning the backlight on only on a long press of the button from a locked state. **Requires the latest settings update (v0.80)**",
|
||||
"icon": "icon.png",
|
||||
"type": "bootloader",
|
||||
"tags": "system",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"backlite.boot.js","url":"boot.js"},
|
||||
{"name":"backlite.settings.js","url":"settings.js"}
|
||||
|
||||
],
|
||||
"data": [{"name":"BackLite.settings.json"}]
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
(function(back) {
|
||||
var FILE = "BackLite.settings.json";
|
||||
|
||||
var settings = require("Storage").readJSON(FILE, 1) || {};
|
||||
|
||||
if (!isFinite(settings.brightness)) settings.brightness = 0.3;
|
||||
|
||||
function writeSettings() {
|
||||
require("Storage").writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
E.showMenu({
|
||||
"" : { "title" : "BackLite" },
|
||||
"< Back": back, // fallback if run standalone
|
||||
"Brightness": {
|
||||
value: settings.brightness,
|
||||
min: 0.1, max: 1,
|
||||
step: 0.1,
|
||||
onchange: v => {
|
||||
settings.brightness = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
0.01: It works somehow, early version for testers and feedback :)
|
||||
0.02: Changed almost all code with Frederic version of Pong and adjusted to be a BrickBreaker!, still Alpha
|
||||
0.03: Rewrote the whole thing to have less code and better graphics, now it works.
|
||||
0.04: Rewrote part of the code to coupe with the flickering and added better logic to handle the graphics.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# BrickBreaker
|
||||
|
||||
A simple BreakOut clone for the Banglejs
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||

|
||||
|
||||
Buttons 1 and 3 to move the BrickBreaker!
|
||||
|
||||
Button 2 to pause and start the game again.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This game was created to learn JS and how to interact with Banglejs, meaning that it may not be perfect :).
|
||||
|
||||
Built with love with base on the tutorial: 2D breakout game using pure JavaScript
|
||||
https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript
|
||||
|
||||
Started on 2020 but rewrote all in 2025 and this is the version I got without having issues with Memory Exhaustion.
|
||||
|
||||
And yes, for Bangle 1, old school!
|
||||
|
||||
## Creator
|
||||
|
||||
Israel Ochoa <isuraeru at gmail.com>
|
||||
|
|
@ -0,0 +1 @@
|
|||
atob("MDCCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAAAAAAAAAVQAAAAAAAAAAAAAFWUAAAAAAAAAAAABVuUAAAAAAAAAAAAVa/lAAAAAAAAAAABWv/lQAAAAAAAAAAVb/+VAAAAAAAAAABW//lUAAAAAAAAAABX/6VAAAAAAAAAAAAW/lUAAAAAAAAAAAAFpVAAAAAAAAAAAAAFVQAAAAAAAAAAAAABUAAAFVVVVQVVQAAAQAAAFVVVVQVVQAAAAAAAFv//5QW5QAAAAAAAG///9QW9QAAAAAAAG///9QW9QAAAAAAAFqqqpQWpQAAAAAAAFVVVVQVVQAAAAAAAFVVVVQVVQAAAAAAAAAAAAAAAAAAAABVVVVQFVVVVQAAAAFVVVVUVVVVVQAAAAFqqqqUWqqqpQAAAAFv//+UW///9QAAAAFv//+UW///9QAAAAFv//+UWv//5QAAAAFVVVVUVVVVVQAAAABVVVVUFVVVVQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVVVVUFVVVVQVVQABaqqqUFqqqpQWpQABv//+UF///9QW5QABv//+UG///9QW5QABv//+UG///9QW5QABaqqqUFqqqpQWlQABVVVVUFVVVVQVVQAAVVVVABVVVVABUAAAAAAAAAAAAAAAAAA==")
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
|
||||
(function () {
|
||||
var BALL_RADIUS = 3;
|
||||
var PADDLE_WIDTH = 26;
|
||||
var PADDLE_HEIGHT = 6;
|
||||
var BRICK_ROWS = 2;
|
||||
var BRICK_HEIGHT = 8;
|
||||
var BRICK_PADDING = 4;
|
||||
var BRICK_OFFSET_TOP = 60;
|
||||
var BRICK_OFFSET_LEFT = 2;
|
||||
var SPEED_MULTIPLIER = 1.1;
|
||||
var PADDLE_SPEED = 12;
|
||||
|
||||
var ball, paddle, interval;
|
||||
var bricks = [];
|
||||
var BRICK_WIDTH, BRICK_COLS;
|
||||
var score = 0;
|
||||
var level = 1;
|
||||
var highScore = 0;
|
||||
var paused = false;
|
||||
var gameOver = false;
|
||||
var lives = 3;
|
||||
var paddleMove = 0;
|
||||
|
||||
var storage = require("Storage");
|
||||
|
||||
function loadHighScore() {
|
||||
var saved = storage.readJSON("breakout_highscore.json", 1);
|
||||
highScore = saved && saved.highScore ? saved.highScore : 0;
|
||||
}
|
||||
|
||||
function saveHighScore() {
|
||||
if (score > highScore) {
|
||||
highScore = score;
|
||||
storage.writeJSON("breakout_highscore.json", { highScore });
|
||||
}
|
||||
}
|
||||
|
||||
function initBricks() {
|
||||
bricks = [];
|
||||
for (var i = 0; i < BRICK_ROWS * BRICK_COLS; i++) {
|
||||
bricks.push(1);
|
||||
}
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
var screenWidth = g.getWidth();
|
||||
BRICK_COLS = Math.min(5, Math.floor((screenWidth - BRICK_OFFSET_LEFT + BRICK_PADDING) / (15 + BRICK_PADDING)));
|
||||
BRICK_WIDTH = Math.floor((screenWidth - BRICK_OFFSET_LEFT - (BRICK_COLS - 1) * BRICK_PADDING) / BRICK_COLS);
|
||||
|
||||
ball = {
|
||||
x: screenWidth / 2,
|
||||
y: g.getHeight() - 40,
|
||||
dx: (Math.random() > 0.5 ? 1 : -1) * 3,
|
||||
dy: -3,
|
||||
radius: BALL_RADIUS,
|
||||
prevPos: null
|
||||
};
|
||||
|
||||
paddle = {
|
||||
x: screenWidth / 2 - PADDLE_WIDTH / 2,
|
||||
y: g.getHeight() - 20,
|
||||
width: PADDLE_WIDTH,
|
||||
height: PADDLE_HEIGHT,
|
||||
prevPos: null
|
||||
};
|
||||
lives = 3;
|
||||
score = 0;
|
||||
level = 1;
|
||||
gameOver = false;
|
||||
paused = false;
|
||||
loadHighScore();
|
||||
initBricks();
|
||||
}
|
||||
|
||||
function drawLives() {
|
||||
var heartSize = 6;
|
||||
var spacing = 2;
|
||||
var startX = g.getWidth() - (lives * (heartSize + spacing)) - 2;
|
||||
var y = 32;
|
||||
g.setColor(1, 0, 0);
|
||||
for (var i = 0; i < lives; i++) {
|
||||
var x = startX + i * (heartSize + spacing);
|
||||
g.fillPoly([x + 3, y, x + 6, y + 3, x + 3, y + 6, x, y + 3], true);
|
||||
}
|
||||
}
|
||||
|
||||
function drawBricks() {
|
||||
g.setColor(0, 1, 0);
|
||||
for (var i = 0; i < bricks.length; i++) {
|
||||
if (bricks[i]) {
|
||||
var c = i % BRICK_COLS;
|
||||
var r = Math.floor(i / BRICK_COLS);
|
||||
var brickX = BRICK_OFFSET_LEFT + c * (BRICK_WIDTH + BRICK_PADDING);
|
||||
var brickY = BRICK_OFFSET_TOP + r * (BRICK_HEIGHT + BRICK_PADDING);
|
||||
g.fillRect(brickX, brickY, brickX + BRICK_WIDTH - 1, brickY + BRICK_HEIGHT - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawBall() {
|
||||
g.setColor(1, 1, 1);
|
||||
g.fillCircle(ball.x, ball.y, ball.radius);
|
||||
g.setColor(0.7, 0.7, 0.7);
|
||||
g.fillCircle(ball.x - 0.5, ball.y - 0.5, ball.radius - 1);
|
||||
}
|
||||
|
||||
function drawPaddle() {
|
||||
g.setColor(0, 1, 1);
|
||||
g.fillRect(paddle.x, paddle.y, paddle.x + paddle.width - 1, paddle.y + paddle.height - 1);
|
||||
}
|
||||
|
||||
function drawHUD() {
|
||||
g.setColor(0, 0, 0).fillRect(0, 0, g.getWidth(), BRICK_OFFSET_TOP - 1);
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFont("6x8", 1);
|
||||
g.setFontAlign(-1, -1);
|
||||
g.drawString("Score: " + score, 2, 22);
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString("High: " + highScore, g.getWidth() / 2, 22);
|
||||
g.setFontAlign(1, -1);
|
||||
g.drawString("Lvl: " + level, g.getWidth() - 2, 22);
|
||||
drawLives();
|
||||
if (paused) {
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString("PAUSED", g.getWidth() / 2, g.getHeight() / 2);
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
if (paddle.prevPos) {
|
||||
g.setColor(0, 0, 0).fillRect(paddle.prevPos.x - 1, paddle.prevPos.y - 1, paddle.prevPos.x + paddle.width + 1, paddle.prevPos.y + paddle.height + 1);
|
||||
}
|
||||
if (ball.prevPos) {
|
||||
g.setColor(0, 0, 0).fillCircle(ball.prevPos.x, ball.prevPos.y, ball.radius + 1);
|
||||
}
|
||||
drawHUD();
|
||||
drawBall();
|
||||
drawPaddle();
|
||||
g.flip();
|
||||
ball.prevPos = { x: ball.x, y: ball.y };
|
||||
paddle.prevPos = { x: paddle.x, y: paddle.y, width: paddle.width, height: paddle.height };
|
||||
}
|
||||
|
||||
function showGameOver() {
|
||||
g.clear();
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(1, 0, 0);
|
||||
g.drawString("GAME OVER", g.getWidth() / 2, g.getHeight() / 2 - 20);
|
||||
g.setFont("6x8", 1);
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawString("Score: " + score, g.getWidth() / 2, g.getHeight() / 2);
|
||||
g.drawString("High: " + highScore, g.getWidth() / 2, g.getHeight() / 2 + 12);
|
||||
g.drawString("BTN2 = Restart", g.getWidth() / 2, g.getHeight() / 2 + 28);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function collisionDetection() {
|
||||
for (var i = 0; i < bricks.length; i++) {
|
||||
if (bricks[i]) {
|
||||
var c = i % BRICK_COLS;
|
||||
var r = Math.floor(i / BRICK_COLS);
|
||||
var brickX = BRICK_OFFSET_LEFT + c * (BRICK_WIDTH + BRICK_PADDING);
|
||||
var brickY = BRICK_OFFSET_TOP + r * (BRICK_HEIGHT + BRICK_PADDING);
|
||||
if (ball.x + ball.radius > brickX && ball.x - ball.radius < brickX + BRICK_WIDTH && ball.y + ball.radius > brickY && ball.y - ball.radius < brickY + BRICK_HEIGHT) {
|
||||
ball.dy = -ball.dy;
|
||||
bricks[i] = 0;
|
||||
score += 10;
|
||||
g.setColor(0, 0, 0).fillRect(brickX, brickY, brickX + BRICK_WIDTH - 1, brickY + BRICK_HEIGHT - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function allBricksCleared() {
|
||||
for (var i = 0; i < bricks.length; i++) {
|
||||
if (bricks[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function resetAfterLifeLost() {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
ball.x = g.getWidth() / 2;
|
||||
ball.y = g.getHeight() - 40;
|
||||
ball.dx = (Math.random() > 0.5 ? 1 : -1) * 3;
|
||||
ball.dy = -3;
|
||||
paddle.x = g.getWidth() / 2 - PADDLE_WIDTH / 2;
|
||||
ball.prevPos = null;
|
||||
paddle.prevPos = null;
|
||||
g.clear();
|
||||
drawBricks();
|
||||
draw();
|
||||
setTimeout(() => { interval = setInterval(update, 50); }, 1000);
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (paused || gameOver) return;
|
||||
if (paddleMove) {
|
||||
paddle.x += paddleMove * PADDLE_SPEED;
|
||||
if (paddle.x < 0) paddle.x = 0;
|
||||
if (paddle.x + paddle.width > g.getWidth()) {
|
||||
paddle.x = g.getWidth() - paddle.width;
|
||||
}
|
||||
}
|
||||
ball.x += ball.dx;
|
||||
ball.y += ball.dy;
|
||||
if (ball.x + ball.radius > g.getWidth() || ball.x - ball.radius < 0) {
|
||||
ball.dx = -ball.dx;
|
||||
}
|
||||
if (ball.y - ball.radius < 0) {
|
||||
ball.dy = -ball.dy;
|
||||
}
|
||||
if (ball.y + ball.radius >= paddle.y && ball.x >= paddle.x && ball.x <= paddle.x + paddle.width) {
|
||||
ball.dy = -ball.dy;
|
||||
ball.y = paddle.y - ball.radius;
|
||||
}
|
||||
if (ball.y + ball.radius > g.getHeight()) {
|
||||
lives--;
|
||||
if (lives > 0) {
|
||||
resetAfterLifeLost();
|
||||
return;
|
||||
} else {
|
||||
clearInterval(interval);
|
||||
interval = undefined;
|
||||
saveHighScore();
|
||||
gameOver = true;
|
||||
setTimeout(showGameOver, 50);
|
||||
return;
|
||||
}
|
||||
}
|
||||
collisionDetection();
|
||||
if (allBricksCleared()) {
|
||||
level++;
|
||||
ball.dx = (Math.random() > 0.5 ? 1 : -1) * Math.abs(ball.dx) * SPEED_MULTIPLIER;
|
||||
ball.dy *= SPEED_MULTIPLIER;
|
||||
initBricks();
|
||||
ball.prevPos = null;
|
||||
paddle.prevPos = null;
|
||||
g.clear();
|
||||
drawBricks();
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
function showGetReady(callback) {
|
||||
g.clear();
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(1, 1, 0);
|
||||
g.drawString("GET READY!", g.getWidth() / 2, g.getHeight() / 2);
|
||||
g.flip();
|
||||
setTimeout(() => {
|
||||
g.clear();
|
||||
drawBricks();
|
||||
draw();
|
||||
callback();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
function startGame() {
|
||||
initGame();
|
||||
showGetReady(() => {
|
||||
interval = setInterval(update, 50);
|
||||
});
|
||||
}
|
||||
|
||||
setWatch(() => {
|
||||
if (gameOver) {
|
||||
startGame();
|
||||
return;
|
||||
}
|
||||
paused = !paused;
|
||||
if (paused) {
|
||||
drawHUD();
|
||||
g.flip();
|
||||
} else {
|
||||
g.clear();
|
||||
drawBricks();
|
||||
draw();
|
||||
}
|
||||
}, BTN2, { repeat: true, edge: "rising" });
|
||||
|
||||
setWatch(() => { paddleMove = -1; }, BTN1, { repeat: true, edge: "rising" });
|
||||
setWatch(() => { paddleMove = 0; }, BTN1, { repeat: true, edge: "falling" });
|
||||
setWatch(() => { paddleMove = 1; }, BTN3, { repeat: true, edge: "rising" });
|
||||
setWatch(() => { paddleMove = 0; }, BTN3, { repeat: true, edge: "falling" });
|
||||
|
||||
startGame();
|
||||
})();
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{ "id": "bbreaker",
|
||||
"name": "BrickBreaker",
|
||||
"shortName":"BrickBreaker",
|
||||
"icon": "bbreaker.png",
|
||||
"version":"0.04",
|
||||
"description": "A simple BreakOut clone for the Banglejs",
|
||||
"tags": "game",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"bbreaker.app.js","url":"app.js"},
|
||||
{"name":"bbreaker.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -5,7 +5,7 @@
|
|||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clkinfo",
|
||||
"tags": "clkinfo",
|
||||
"tags": "clkinfo,clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"clkinfoclk.clkinfo.js","url":"clkinfo.js"}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -0,0 +1,22 @@
|
|||
(function() {
|
||||
let strideLength = (require("Storage").readJSON("myprofile.json",1)||{}).strideLength ?? 0.79,
|
||||
lastSteps = 0;
|
||||
function stepUpdateHandler() { distance.emit("redraw"); }
|
||||
var distance = {
|
||||
name : "Distance",
|
||||
get : () => { let v = (Bangle.getHealthStatus("day").steps - lastSteps)*strideLength; return {
|
||||
text : require("locale").distance(v,1),
|
||||
img : atob("GBiBAAMAAAeAAA/AAA/AAA/gAA/gwAfh4AfD4APD4AOH4AAH4ADj4AHjwAHhwADgAAACAAAHgAAPAAAHAAgCEBgAGD///BgAGAgAEA==")
|
||||
};},
|
||||
run : function() {
|
||||
lastSteps = (lastSteps>=Bangle.getHealthStatus("day").steps) ? 0 : Bangle.getHealthStatus("day").steps;
|
||||
this.emit("redraw");
|
||||
},
|
||||
show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); },
|
||||
hide : function() { Bangle.removeListener("step", stepUpdateHandler); }
|
||||
};
|
||||
return {
|
||||
name: "Bangle",
|
||||
items: [ distance ]
|
||||
};
|
||||
})
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "clkinfodist",
|
||||
"name": "Clockinfo Distance",
|
||||
"version":"0.01",
|
||||
"description": "Uses the 'My Profile' app's Stride Length to calculate distance travelled based on step count. Tap to reset for measuring distances.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clkinfo",
|
||||
"tags": "clkinfo,distance,steps,outdoors,tool",
|
||||
"dependencies": {"myprofile":"app"},
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"clkinfodist.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -16,3 +16,6 @@
|
|||
0.15: Fix error when displaying a category with only one clockinfo (fix #3728)
|
||||
0.16: Add BLE clkinfo entry
|
||||
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
|
||||
0.19: Fix Altitude ClockInfo after BLE added
|
||||
Tapping Altitude now updates the reading
|
||||
|
|
@ -39,22 +39,23 @@ exports.load = function() {
|
|||
var hrm = 0;
|
||||
var alt = "--";
|
||||
// callbacks (needed for easy removal of listeners)
|
||||
function batteryUpdateHandler() { bangleItems[0].emit("redraw"); }
|
||||
function stepUpdateHandler() { bangleItems[1].emit("redraw"); }
|
||||
function batteryUpdateHandler() { bangleItems.find(i=>i.name=="Battery").emit("redraw"); }
|
||||
function stepUpdateHandler() { bangleItems.find(i=>i.name=="Steps").emit("redraw"); }
|
||||
function hrmUpdateHandler(e) {
|
||||
if (e && e.confidence>60) hrm = Math.round(e.bpm);
|
||||
bangleItems[2].emit("redraw");
|
||||
bangleItems.find(i=>i.name=="HRM").emit("redraw");
|
||||
}
|
||||
function altUpdateHandler() {
|
||||
try {
|
||||
Bangle.getPressure().then(data=>{
|
||||
if (!data) return;
|
||||
alt = Math.round(data.altitude) + "m";
|
||||
bangleItems[3].emit("redraw");
|
||||
bangleItems.find(i=>i.name=="Altitude").emit("redraw");
|
||||
});
|
||||
} catch (e) {
|
||||
print("Caught "+e+"\n in function altUpdateHandler in module clock_info");
|
||||
bangleItems[3].emit('redraw');}
|
||||
bangleItems.find(i=>i.name=="Altitude").emit('redraw');
|
||||
}
|
||||
}
|
||||
// actual menu
|
||||
var menu = [{
|
||||
|
|
@ -120,7 +121,6 @@ exports.load = function() {
|
|||
},
|
||||
},
|
||||
{ name: "BLE",
|
||||
hasRange: false,
|
||||
isOn: () => {
|
||||
const s = NRF.getSecurityStatus();
|
||||
return s.advertising || s.connected;
|
||||
|
|
@ -128,7 +128,8 @@ exports.load = function() {
|
|||
get: function() {
|
||||
return {
|
||||
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() {
|
||||
|
|
@ -155,6 +156,7 @@ exports.load = function() {
|
|||
min : 0, max : settings.maxAltitude,
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==")
|
||||
}),
|
||||
run : function() { alt = "--"; this.emit("redraw"); altUpdateHandler(); },
|
||||
show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); },
|
||||
hide : function() { clearInterval(this.interval); delete this.interval; },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "clock_info",
|
||||
"name": "Clock Info Module",
|
||||
"shortName": "Clock Info",
|
||||
"version":"0.17",
|
||||
"version":"0.19",
|
||||
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
||||
"icon": "app.png",
|
||||
"type": "module",
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Fix lint warnings
|
||||
0.05: Fix on not reading counter defaults in Settings
|
||||
0.06: Added ability to display only one counter and fast-scrolling
|
||||
0.07: Add option to keep unlocked
|
||||
|
|
|
|||
|
|
@ -7,12 +7,19 @@ var s = Object.assign({
|
|||
fullscreen:true,
|
||||
buzz: true,
|
||||
colortext: true,
|
||||
keepunlocked: false,
|
||||
}, require('Storage').readJSON("counter2.json", true) || {});
|
||||
|
||||
var sGlob = Object.assign({
|
||||
timeout: 10,
|
||||
}, require('Storage').readJSON("setting.json", true) || {});
|
||||
|
||||
const lockTimeout = s.keepunlocked ? 0 : 1000;
|
||||
if (s.keepunlocked) {
|
||||
Bangle.setOptions({lockTimeout});
|
||||
Bangle.setLocked(false);
|
||||
}
|
||||
|
||||
const f1 = (s.colortext) ? "#f00" : "#fff";
|
||||
const f2 = (s.colortext) ? "#00f" : "#fff";
|
||||
const b1 = (s.colortext) ? g.theme.bg : "#f00";
|
||||
|
|
@ -80,7 +87,7 @@ function updateScreen() {
|
|||
Bangle.on('lock', e => {
|
||||
drag = undefined;
|
||||
var timeOutTimer = sGlob.timeout * 1000;
|
||||
Bangle.setOptions({backlightTimeout: timeOutTimer, lockTimeout: timeOutTimer});
|
||||
Bangle.setOptions({backlightTimeout: timeOutTimer, lockTimeout});
|
||||
if (dragtimeout) clearTimeout(dragtimeout);
|
||||
fastupdateoccurring = false;
|
||||
});
|
||||
|
|
@ -102,7 +109,7 @@ Bangle.on("drag", e => {
|
|||
drag = undefined;
|
||||
if (dragtimeout) {
|
||||
let timeOutTimer = 1000;
|
||||
Bangle.setOptions({backlightTimeout: timeOutTimer, lockTimeout: timeOutTimer});
|
||||
Bangle.setOptions({backlightTimeout: timeOutTimer, lockTimeout});
|
||||
clearTimeout(dragtimeout);
|
||||
}
|
||||
fastupdateoccurring = false;
|
||||
|
|
@ -134,7 +141,7 @@ function resetcounter(which) {
|
|||
fastupdateoccurring = false;
|
||||
if (dragtimeout) {
|
||||
let timeOutTimer = 1000;
|
||||
Bangle.setOptions({backlightTimeout: timeOutTimer, lockTimeout: timeOutTimer});
|
||||
Bangle.setOptions({backlightTimeout: timeOutTimer, lockTimeout});
|
||||
clearTimeout(dragtimeout);
|
||||
}
|
||||
if (which == null) {
|
||||
|
|
@ -152,7 +159,6 @@ function resetcounter(which) {
|
|||
ignoreonce = true;
|
||||
}
|
||||
|
||||
|
||||
updateScreen();
|
||||
|
||||
setWatch(function() {
|
||||
|
|
@ -163,6 +169,6 @@ setWatch(function() {
|
|||
}
|
||||
}
|
||||
var timeOutTimer = sGlob.timeout * 1000;
|
||||
Bangle.setOptions({backlightTimeout: timeOutTimer, lockTimeout: timeOutTimer});
|
||||
Bangle.setOptions({backlightTimeout: timeOutTimer, lockTimeout});
|
||||
load();
|
||||
}, BTN1, {repeat:true, edge:"falling"});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "counter2",
|
||||
"name": "Counter2",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "Dual Counter",
|
||||
"readme":"README.md",
|
||||
"icon": "counter2-icon.png",
|
||||
|
|
|
|||
|
|
@ -70,10 +70,17 @@
|
|||
onchange: v => {
|
||||
settings.buzz = v;
|
||||
writeSettings();
|
||||
},
|
||||
};
|
||||
appMenu['Keep unlocked'] = {
|
||||
value: settings.keepunlocked,
|
||||
onchange: v => {
|
||||
settings.keepunlocked = v;
|
||||
writeSettings();
|
||||
},
|
||||
};
|
||||
E.showMenu(appMenu);
|
||||
}
|
||||
|
||||
showMainMenu();
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
"icon": "icon.png",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"storage": [
|
||||
{"name":"drained.boot.js","url":"boot.js"},
|
||||
{"name":"drained.app.js","url":"app.js"},
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: initial import
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Elite clock 
|
||||
|
||||
Simple binary clock for leet haxorz.
|
||||
|
||||
Written by: [Pavel Machek](https://github.com/pavelmachek)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgIspiEPgEeAoU/4F/wGAiEAsEA4AFImAHBAolj/gRD4YFEC4UPwEfgAFC4EfF5IpHAp4dC4EQv/A/+AHYJlDnjY/AH4AJ"))
|
||||
|
After Width: | Height: | Size: 9.6 KiB |
|
|
@ -0,0 +1,86 @@
|
|||
// Bangle.js 2 - Binary Leet ClockZ
|
||||
/*
|
||||
|
||||
bangle.js2: create binary 'leet clock' where the time is shown as text
|
||||
"leet clockz" with binary 0 being normal character and binary one
|
||||
being leet translation. Be careful to only update time on minute
|
||||
boundaries.
|
||||
|
||||
So yeah — 1337 c10ckZ = certified leetspeak 😎
|
||||

|
||||
ChatGPT said:
|
||||
Alright, here’s a hardcore hacker variant of elite clock in full-on aggressive leetspeak:
|
||||
|
||||
£|173 ¢|_0¢|<Z
|
||||
|
||||
Breakdown:
|
||||
|
||||
E → £ or 3
|
||||
L → | or £
|
||||
I → 1 or !
|
||||
T → 7 or +
|
||||
C → ¢ or (
|
||||
O → 0
|
||||
K → |< or X
|
||||
|
||||
Other extreme variants:
|
||||
3|173 (|_0(<Z
|
||||
€|!73 ©|0xX
|
||||
3L1+3 (L0XXZ
|
||||
|
||||
*/
|
||||
|
||||
const TEXT = "leet\nclockz";
|
||||
const LEET = {
|
||||
"l": "1",
|
||||
"e": "3",
|
||||
"t": "7",
|
||||
" ": " ",
|
||||
"c": "(",
|
||||
"o": "0",
|
||||
"k": "X",
|
||||
"z": "2"
|
||||
};
|
||||
|
||||
// Convert hour (12h) and minute to binary mask
|
||||
function getBinaryFromTime(d) {
|
||||
let h = d.getHours() % 12;
|
||||
if (h === 0) h = 12; // 12-hour format: 0 becomes 12
|
||||
const m = d.getMinutes();
|
||||
const bin = ((h << 7) | m).toString(2).padStart(11, '0');
|
||||
return bin;
|
||||
}
|
||||
|
||||
// Map normal characters to leet if binary mask says so
|
||||
function getDisplayText(binMask) {
|
||||
return TEXT.split("").map((ch, i) =>
|
||||
binMask[i] === '1' ? (LEET[ch] || ch) : ch
|
||||
).join("");
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.reset().clear();
|
||||
const now = new Date();
|
||||
const bin = getBinaryFromTime(now);
|
||||
const txt = getDisplayText(bin);
|
||||
|
||||
const w = 0;
|
||||
g.setFont("Vector", 47).setFontAlign(0,0);
|
||||
|
||||
g.drawString(txt, (g.getWidth() - w) / 2, (g.getHeight() - 0) / 2);
|
||||
}
|
||||
|
||||
function scheduleNextDraw() {
|
||||
const now = new Date();
|
||||
const msToNextMin = 60000 - (now.getSeconds() * 1000 + now.getMilliseconds());
|
||||
setTimeout(() => {
|
||||
draw();
|
||||
scheduleNextDraw();
|
||||
}, msToNextMin);
|
||||
}
|
||||
|
||||
// Init
|
||||
draw();
|
||||
scheduleNextDraw();
|
||||
//Bangle.loadWidgets();
|
||||
//Bangle.drawWidgets();
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "eliteclock",
|
||||
"name": "Elite clock",
|
||||
"version": "0.01",
|
||||
"description": "Simple binary clock for leet haxorz",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"storage": [
|
||||
{"name":"eliteclock.app.js","url":"eliteclock.app.js"},
|
||||
{"name":"eliteclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -34,4 +34,11 @@
|
|||
0.30: Minor code improvements
|
||||
0.31: Add support for new health format (storing more data)
|
||||
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)
|
||||
0.35: Update boot/lib.min.js
|
||||
0.36: Fix Distance graphs that used '1*' to remove the suffix
|
||||
0.37: Reduce movement limit for HRM off from 400 to 100
|
||||
Fix daily summary for movement (was not scaling down correctly)
|
||||
|
|
@ -32,7 +32,7 @@ function menuStepCount() {
|
|||
}
|
||||
|
||||
function menuDistance() {
|
||||
const distMult = 1*require("locale").distance(myprofile.strideLength, 2); // hackish: this removes the distance suffix, e.g. 'm'
|
||||
const distMult = parseFloat(require("locale").distance(myprofile.strideLength, 2)); // this removes the distance suffix, e.g. 'm'
|
||||
E.showMenu({
|
||||
"": { title:/*LANG*/"Distance" },
|
||||
/*LANG*/"< Back": () => menuStepCount(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
function startMeasurement() {
|
||||
// if is charging, or hardly moved and face up/down, don't start HRM
|
||||
if (Bangle.isCharging() ||
|
||||
(Bangle.getHealthStatus("last").movement<400 && Math.abs(Bangle.getAccel().z)>0.99)) return;
|
||||
(Bangle.getHealthStatus("last").movement<100 && Math.abs(Bangle.getAccel().z)>0.99)) return;
|
||||
// otherwise turn HRM on
|
||||
Bangle.setHRMPower(1, "health");
|
||||
setTimeout(() => {
|
||||
|
|
@ -81,12 +81,6 @@ Bangle.on("health", health => {
|
|||
require("Storage").write(fn, "HEALTH2\0", 0, DB_HEADER_LEN + DB_RECORDS_PER_MONTH*inf.r); // header (and allocate full new file)
|
||||
}
|
||||
var recordPos = DB_HEADER_LEN+(rec*inf.r);
|
||||
|
||||
// scale down reported movement value in order to fit it within a
|
||||
// uint8 DB field
|
||||
health = Object.assign({}, health);
|
||||
health.movement /= 8;
|
||||
|
||||
require("Storage").write(fn, inf.encode(health), recordPos);
|
||||
if (rec%DB_RECORDS_PER_DAY != DB_RECORDS_PER_DAY-2) return;
|
||||
// we're at the end of the day. Read in all of the data for the day and sum it up
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
(function(){var a=0|(require("Storage").readJSON("health.json",1)||{}).hrm;if(1==a||2==a){function c(){Bangle.setHRMPower(1,"health");setTimeout(()=>Bangle.setHRMPower(0,"health"),6E4*a);if(1==a){function b(){Bangle.setHRMPower(1,"health");setTimeout(()=>{Bangle.setHRMPower(0,"health")},6E4)}setTimeout(b,2E5);setTimeout(b,4E5)}}Bangle.on("health",c);Bangle.on("HRM",b=>{90<b.confidence&&1>Math.abs(Bangle.getHealthStatus().bpm-b.bpm)&&Bangle.setHRMPower(0,"health")});90<Bangle.getHealthStatus().bpmConfidence||
|
||||
c()}else Bangle.setHRMPower(!!a,"health")})();Bangle.on("health",a=>{(Bangle.getPressure?Bangle.getPressure():Promise.resolve({})).then(c=>{Object.assign(a,c);c=new Date(Date.now()-59E4);if(a&&0<a.steps){var b=require("Storage").readJSON("health.json",1)||{},d=Bangle.getHealthStatus("day").steps;b.stepGoalNotification&&0<b.stepGoal&&d>=b.stepGoal&&(d=(new Date(Date.now())).toISOString().split("T")[0],!b.stepGoalNotificationDate||b.stepGoalNotificationDate<d)&&(Bangle.buzz(200,.5),require("notify").show({title:b.stepGoal+
|
||||
" steps",body:"You reached your step goal!",icon:atob("DAyBABmD6BaBMAsA8BCBCBCBCA8AAA==")}),b.stepGoalNotificationDate=d,require("Storage").writeJSON("health.json",b))}var g=function(f){return 145*(f.getDate()-1)+6*f.getHours()+(0|6*f.getMinutes()/60)}(c);c=function(f){return"health-"+f.getFullYear()+"-"+(f.getMonth()+1)+".raw"}(c);d=require("Storage").read(c);if(void 0!==d){b=require("health").getDecoder(d);var e=d.substr(8+g*b.r,b.r);if(e!=b.clr){print("HEALTH ERR: Already written!");return}}else b=
|
||||
require("health").getDecoder("HEALTH2"),require("Storage").write(c,"HEALTH2\x00",0,8+4495*b.r);var h=8+g*b.r;a=Object.assign({},a);a.movement/=8;require("Storage").write(c,b.encode(a),h);if(143==g%145)if(g=h+b.r,d.substr(g,b.r)!=b.clr)print("HEALTH ERR: Daily summary already written!");else{a={steps:0,bpm:0,movement:0,movCnt:0,bpmCnt:0};for(var k=0;144>k;k++)e=d.substr(h,b.r),e!=b.clr&&(e=b.decode(e),a.steps+=e.steps,a.bpm+=e.bpm,a.movement+=e.movement,a.movCnt++,e.bpm&&a.bpmCnt++),h-=b.r;a.bpmCnt&&
|
||||
(a.bpm/=a.bpmCnt);a.movCnt&&(a.movement/=a.movCnt);require("Storage").write(c,b.encode(a),g)}})})
|
||||
{let a=0|(require("Storage").readJSON("health.json",1)||{}).hrm;if(1==a||2==a){let d=function(b){function c(){Bangle.isCharging()||100>Bangle.getHealthStatus("last").movement&&.99<Math.abs(Bangle.getAccel().z)||(Bangle.setHRMPower(1,"health"),setTimeout(()=>{Bangle.setHRMPower(0,"health")},6E4*a))}c();1==a&&(setTimeout(c,2E5),setTimeout(c,4E5))};Bangle.on("health",d);Bangle.on("HRM",b=>{90<b.confidence&&1>Math.abs(Bangle.getHealthStatus().bpm-b.bpm)&&Bangle.setHRMPower(0,
|
||||
"health")});90>Bangle.getHealthStatus().bpmConfidence&&d()}else Bangle.setHRMPower(!!a,"health")}Bangle.on("health",a=>{(Bangle.getPressure?Bangle.getPressure():Promise.resolve({})).then(d=>{Object.assign(a,d);d=new Date(Date.now()-59E4);if(a&&0<a.steps){var b=require("Storage").readJSON("health.json",1)||{},c=Bangle.getHealthStatus("day").steps;b.stepGoalNotification&&0<b.stepGoal&&c>=b.stepGoal&&(c=(new Date(Date.now())).toISOString().split("T")[0],!b.stepGoalNotificationDate||b.stepGoalNotificationDate<
|
||||
c)&&(Bangle.buzz(200,.5),require("notify").show({title:b.stepGoal+" steps",body:"You reached your step goal!",icon:atob("DAyBABmD6BaBMAsA8BCBCBCBCA8AAA==")}),b.stepGoalNotificationDate=c,require("Storage").writeJSON("health.json",b))}var g=function(f){return 145*(f.getDate()-1)+6*f.getHours()+(0|6*f.getMinutes()/60)}(d);d=function(f){return"health-"+f.getFullYear()+"-"+(f.getMonth()+1)+".raw"}(d);c=require("Storage").read(d);if(void 0!==c){b=require("health").getDecoder(c);var e=c.substr(8+g*b.r,
|
||||
b.r);if(e!=b.clr){print("HEALTH ERR: Already written!");return}}else b=require("health").getDecoder("HEALTH2"),require("Storage").write(d,"HEALTH2\x00",0,8+4495*b.r);var h=8+g*b.r;require("Storage").write(d,b.encode(a),h);if(143==g%145)if(g=h+b.r,c.substr(g,b.r)!=b.clr)print("HEALTH ERR: Daily summary already written!");else{a={steps:0,bpm:0,movement:0,movCnt:0,bpmCnt:0};for(var k=0;144>k;k++)e=c.substr(h,b.r),e!=b.clr&&(e=b.decode(e),a.steps+=e.steps,a.bpm+=e.bpm,a.movement+=e.movement,a.movCnt++,
|
||||
e.bpm&&a.bpmCnt++),h-=b.r;a.bpmCnt&&(a.bpm/=a.bpmCnt);a.movCnt&&(a.movement/=a.movCnt);require("Storage").write(d,b.encode(a),g)}})})
|
||||
|
|
@ -41,48 +41,49 @@ exports.getDecoder = function(fileContents) {
|
|||
return {
|
||||
r : 10, // record length
|
||||
clr : "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
|
||||
decode : h => { var v = {
|
||||
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
|
||||
bpmMin : h.charCodeAt(2),
|
||||
bpmMax : h.charCodeAt(3),
|
||||
movement : h.charCodeAt(4)*8,
|
||||
battery : h.charCodeAt(5)&127,
|
||||
isCharging : !!(h.charCodeAt(5)&128),
|
||||
temperature : h.charCodeAt(6)/2, // signed?
|
||||
altitude : ((h.charCodeAt(7)&31)<<8)|h.charCodeAt(8), // signed?
|
||||
activity : exports.ACTIVITY[h.charCodeAt(7)>>5]
|
||||
decode : h => { "ram"; var d = h.charCodeAt.bind(h), v = {
|
||||
steps : (d(0)<<8) | d(1),
|
||||
bpmMin : d(2),
|
||||
bpmMax : d(3),
|
||||
movement : d(4)*8,
|
||||
battery : d(5)&127,
|
||||
isCharging : !!(d(5)&128),
|
||||
temperature : d(6)/2, // signed?
|
||||
altitude : ((d(7)&31)<<8)|d(8), // signed?
|
||||
activity : exports.ACTIVITY[d(7)>>5]
|
||||
};
|
||||
if (v.temperature>80) v.temperature-=128;
|
||||
v.bpm = (v.bpmMin+v.bpmMax)/2;
|
||||
if (v.altitude > 7500) v.altitude-=8192;
|
||||
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.bpmMin || health.bpm, // 8 bit bpm
|
||||
health.bpmMax || health.bpm, // 8 bit bpm
|
||||
Math.min(health.movement, 255),
|
||||
Math.min(health.movement >> 3, 255),
|
||||
E.getBattery()|(Bangle.isCharging()&&128),
|
||||
0|Math.round(health.temperature*2),
|
||||
(alt>>8)|(Math.max(0,exports.ACTIVITY.indexOf(health.activity))<<5),alt&255,
|
||||
0 // tbd
|
||||
)}
|
||||
);}
|
||||
};
|
||||
} else { // HEALTH1
|
||||
return {
|
||||
r : 4, // record length
|
||||
clr : "\xFF\xFF\xFF\xFF",
|
||||
decode : h => ({
|
||||
decode : h => { "ram"; return {
|
||||
steps : (h.charCodeAt(0)<<8) | h.charCodeAt(1),
|
||||
bpm : h.charCodeAt(2),
|
||||
bpmMin : h.charCodeAt(2),
|
||||
bpmMax : h.charCodeAt(2),
|
||||
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.bpm, // 8 bit bpm
|
||||
Math.min(health.movement, 255))
|
||||
Math.min(health.movement >> 3, 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.
|
||||
exports.readFullDatabase = function(cb) {
|
||||
require("Storage").list(/health-[0-9]+-[0-9]+.raw/).forEach(val => {
|
||||
console.log(val);
|
||||
var parts = val.split('-');
|
||||
var y = parseInt(parts[1],10);
|
||||
var mo = parseInt(parts[2].replace('.raw', ''),10);
|
||||
|
||||
exports.readAllRecords(new Date(y, mo, 1), (r) => {
|
||||
var mo = parseInt(parts[2].replace('.raw', ''),10) - 1;
|
||||
exports.readAllRecords(new Date(y, mo, 1), (r) => {"ram";
|
||||
r.date = new Date(y, mo, r.day, r.hr, r.min);
|
||||
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
|
||||
exports.readAllRecordsSince = function(d, cb) {
|
||||
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) {
|
||||
exports.readDay(di, (r) => {
|
||||
exports.readDay(di, (r) => {"ram";
|
||||
r.date = new Date(di.getFullYear(), di.getMonth(), di.getDate(), r.hr, r.min);
|
||||
cb(r);
|
||||
});
|
||||
|
|
@ -149,7 +148,7 @@ exports.readDailySummaries = function(d, cb) {
|
|||
if (h!=inf.clr) cb(Object.assign(inf.decode(h), {day:day+1}));
|
||||
idx += DB_RECORDS_PER_DAY*inf.r;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Read all records from the given day
|
||||
exports.readDay = function(d, cb) {
|
||||
|
|
@ -167,4 +166,4 @@ exports.readDay = function(d, cb) {
|
|||
idx += inf.r;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
function k(b){return"health-"+b.getFullYear()+"-"+(b.getMonth()+1)+".raw"}function l(b){return 145*(b.getDate()-1)+6*b.getHours()+(0|6*b.getMinutes()/60)}exports.ACTIVITY="UNKNOWN NOT_WORN WALKING EXERCISE LIGHT_SLEEP DEEP_SLEEP".split(" ");exports.getDecoder=function(b){return"HEALTH2"==b.substr(0,7)?{r:10,clr:"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff",decode:a=>{a={steps:a.charCodeAt(0)<<8|a.charCodeAt(1),bpmMin:a.charCodeAt(2),bpmMax:a.charCodeAt(3),
|
||||
movement:8*a.charCodeAt(4),battery:a.charCodeAt(5)&127,isCharging:!!(a.charCodeAt(5)&128),temperature:a.charCodeAt(6)/2,altitude:(a.charCodeAt(7)&31)<<8|a.charCodeAt(8),activity:exports.ACTIVITY[a.charCodeAt(7)>>5]};80<a.temperature&&(a.temperature-=128);a.bpm=(a.bpmMin+a.bpmMax)/2;7500<a.altitude&&(a.altitude-=8192);return a},encode:a=>{var c=a.altitude&8191;return String.fromCharCode(a.steps>>8,a.steps&255,a.bpmMin||a.bpm,a.bpmMax||a.bpm,Math.min(a.movement,255),E.getBattery()|(Bangle.isCharging()&&
|
||||
128),0|Math.round(2*a.temperature),c>>8|Math.max(0,exports.ACTIVITY.indexOf(a.activity))<<5,c&255,0)}}:{r:4,clr:"\xff\xff\xff\xff",decode:a=>({steps:a.charCodeAt(0)<<8|a.charCodeAt(1),bpm:a.charCodeAt(2),bpmMin:a.charCodeAt(2),bpmMax:a.charCodeAt(2),movement:8*a.charCodeAt(3)}),encode:a=>String.fromCharCode(a.steps>>8,a.steps&255,a.bpm,Math.min(a.movement,255))}};exports.readAllRecords=function(b,a){b=k(b);b=require("Storage").read(b);if(void 0!==b)for(var c=exports.getDecoder(b),d={},e=8,
|
||||
f=0;31>f;f++){d.day=f+1;for(var g=0;24>g;g++){d.hr=g;for(var h=0;6>h;h++){d.min=10*h;var m=b.substr(e,c.r);m!=c.clr&&a(Object.assign(c.decode(m),d));e+=c.r}}e+=c.r}};exports.readFullDatabase=function(b){require("Storage").list(/health-[0-9]+-[0-9]+.raw/).forEach(a=>{console.log(a);a=a.split("-");var c=parseInt(a[1],10),d=parseInt(a[2].replace(".raw",""),10);exports.readAllRecords(new Date(c,d,1),e=>{e.date=new Date(c,d,e.day,e.hr,e.min);b(e)})})};exports.readAllRecordsSince=function(b,a){for(var c=
|
||||
(new Date).getTime();b.getTime()<=c;)exports.readDay(b,d=>{d.date=new Date(b.getFullYear(),b.getMonth(),b.getDate(),d.hr,d.min);a(d)}),b.setDate(b.getDate()+1)};exports.readDailySummaries=function(b,a){l(b);b=k(b);b=require("Storage").read(b);if(void 0!==b)for(var c=exports.getDecoder(b),d=8+144*c.r,e=0;31>e;e++){var f=b.substr(d,c.r);f!=c.clr&&a(Object.assign(c.decode(f),{day:e+1}));d+=145*c.r}};exports.readDay=function(b,a){l(b);var c=k(b);c=require("Storage").read(c);if(void 0!==c){var d=exports.getDecoder(c),
|
||||
e={};b=8+145*d.r*(b.getDate()-1);for(var f=0;24>f;f++){e.hr=f;for(var g=0;6>g;g++){e.min=10*g;var h=c.substr(b,d.r);h!=d.clr&&a(Object.assign(d.decode(h),e));b+=d.r}}}}
|
||||
function k(b){return"health-"+b.getFullYear()+"-"+(b.getMonth()+1)+".raw"}function l(b){return 145*(b.getDate()-1)+6*b.getHours()+(0|6*b.getMinutes()/60)}exports.ACTIVITY="UNKNOWN NOT_WORN WALKING EXERCISE LIGHT_SLEEP DEEP_SLEEP".split(" ");exports.getDecoder=function(b){return"HEALTH2"==b.substr(0,7)?{r:10,clr:"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff",decode:a=>{"ram";a=a.charCodeAt.bind(a);a={steps:a(0)<<8|a(1),bpmMin:a(2),bpmMax:a(3),movement:8*a(4),
|
||||
battery:a(5)&127,isCharging:!!(a(5)&128),temperature:a(6)/2,altitude:(a(7)&31)<<8|a(8),activity:exports.ACTIVITY[a(7)>>5]};80<a.temperature&&(a.temperature-=128);a.bpm=(a.bpmMin+a.bpmMax)/2;7500<a.altitude&&(a.altitude-=8192);return a},encode:a=>{"ram";var c=a.altitude&8191;return String.fromCharCode(a.steps>>8,a.steps&255,a.bpmMin||a.bpm,a.bpmMax||a.bpm,Math.min(a.movement>>3,255),E.getBattery()|(Bangle.isCharging()&&128),0|Math.round(2*a.temperature),c>>8|Math.max(0,exports.ACTIVITY.indexOf(a.activity))<<
|
||||
5,c&255,0)}}:{r:4,clr:"\xff\xff\xff\xff",decode:a=>{"ram";return{steps:a.charCodeAt(0)<<8|a.charCodeAt(1),bpm:a.charCodeAt(2),bpmMin:a.charCodeAt(2),bpmMax:a.charCodeAt(2),movement:8*a.charCodeAt(3)}},encode:a=>{"ram";return String.fromCharCode(a.steps>>8,a.steps&255,a.bpm,Math.min(a.movement>>3,255))}}};exports.readAllRecords=function(b,a){b=k(b);b=require("Storage").read(b);if(void 0!==b)for(var c=exports.getDecoder(b),d={},e=8,f=0;31>f;f++){d.day=f+1;for(var g=0;24>g;g++){d.hr=g;for(var h=
|
||||
0;6>h;h++){d.min=10*h;var m=b.substr(e,c.r);m!=c.clr&&a(Object.assign(c.decode(m),d));e+=c.r}}e+=c.r}};exports.readFullDatabase=function(b){require("Storage").list(/health-[0-9]+-[0-9]+.raw/).forEach(a=>{a=a.split("-");var c=parseInt(a[1],10),d=parseInt(a[2].replace(".raw",""),10)-1;exports.readAllRecords(new Date(c,d,1),e=>{"ram";e.date=new Date(c,d,e.day,e.hr,e.min);b(e)})})};exports.readAllRecordsSince=function(b,a){for(var c=(new Date).getTime(),d=new Date(b.toISOString().substr(0,10));d.getTime()<=
|
||||
c;)exports.readDay(d,e=>{"ram";e.date=new Date(d.getFullYear(),d.getMonth(),d.getDate(),e.hr,e.min);a(e)}),d.setDate(d.getDate()+1)};exports.readDailySummaries=function(b,a){l(b);b=k(b);b=require("Storage").read(b);if(void 0!==b)for(var c=exports.getDecoder(b),d=8+144*c.r,e=0;31>e;e++){var f=b.substr(d,c.r);f!=c.clr&&a(Object.assign(c.decode(f),{day:e+1}));d+=145*c.r}};exports.readDay=function(b,a){l(b);var c=k(b);c=require("Storage").read(c);if(void 0!==c){var d=exports.getDecoder(c),e={};b=8+145*
|
||||
d.r*(b.getDate()-1);for(var f=0;24>f;f++){e.hr=f;for(var g=0;6>g;g++){e.min=10*g;var h=c.substr(b,d.r);h!=d.clr&&a(Object.assign(d.decode(h),e));b+=d.r}}}}
|
||||
|
|
@ -2,9 +2,10 @@
|
|||
"id": "health",
|
||||
"name": "Health Tracking",
|
||||
"shortName": "Health",
|
||||
"version": "0.32",
|
||||
"version": "0.37",
|
||||
"description": "Logs health data and provides an app to view it",
|
||||
"icon": "app.png",
|
||||
"screenshots" : [ { "url":"screenshot.png" } ],
|
||||
"tags": "tool,system,health",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 5.0 KiB |
|
|
@ -4,6 +4,7 @@
|
|||
"version": "0.13",
|
||||
"description": "Measure your heart rate and see live sensor data",
|
||||
"icon": "heartrate.png",
|
||||
"screenshots" : [ { "url":"screenshot.png" } ],
|
||||
"tags": "health",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 5.4 KiB |
|
|
@ -16,5 +16,4 @@ It is also possible to load existing icon into editor, using
|
|||
"load_icon("");" command. At the end of iconbits.app.js file there are
|
||||
more utility functions.
|
||||
|
||||
|
||||
|
||||
Create 48x48 icon in gimp.
|
||||
|
After Width: | Height: | Size: 9.6 KiB |
|
|
@ -17,4 +17,5 @@
|
|||
0.16: Always request Current Time service from iOS
|
||||
0.17: Default to passing full UTF8 strings into messages app (which can now process them with an international font)
|
||||
0.18: Fix UTF8 conversion (check for `font` library, not `fonts`)
|
||||
0.19: Convert numeric weather values to int from BangleDumpWeather shortcut
|
||||
0.19: Convert numeric weather values to int from BangleDumpWeather shortcut
|
||||
0.20: Add feels-like temperature data field to weather parsing from BangleDumpWeather shortcut.
|
||||
|
|
|
|||
|
|
@ -180,6 +180,7 @@ E.on('notify',msg=>{
|
|||
let weatherEvent = {
|
||||
t: "weather",
|
||||
temp: d.temp,
|
||||
feels: d.feels,
|
||||
hi: d.hi,
|
||||
lo: d.lo,
|
||||
hum: d.hum,
|
||||
|
|
@ -192,7 +193,7 @@ E.on('notify',msg=>{
|
|||
loc: d.loc
|
||||
}
|
||||
// Convert string fields to numbers for iOS weather shortcut
|
||||
const numFields = ['code', 'wdir', 'temp', 'hi', 'lo', 'hum', 'wind', 'uv', 'rain'];
|
||||
const numFields = ['code', 'wdir', 'temp','feels', 'hi', 'lo', 'hum', 'wind', 'uv', 'rain'];
|
||||
numFields.forEach(field => {
|
||||
if (weatherEvent[field] != null) weatherEvent[field] = +weatherEvent[field];
|
||||
});
|
||||
|
|
@ -345,4 +346,4 @@ E.emit("ANCS", {
|
|||
} else {
|
||||
Bangle.ancsConvertUTF8 = E.asUTF8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "ios",
|
||||
"name": "iOS Integration",
|
||||
"version": "0.19",
|
||||
"description": "Display notifications/music/etc from iOS devices",
|
||||
"version": "0.20",
|
||||
"description": "Display/pull notifications, music, weather, and agenda from iOS devices",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,ios,apple,messages,notifications",
|
||||
"dependencies": {"messages":"module"},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
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 }
|
||||
]
|
||||
}
|
||||
|
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();
|
||||
})
|
||||
|
|
@ -117,3 +117,4 @@
|
|||
Remove workaround for 2v10 (>3 years ago) - assume everyone is on never firmware now
|
||||
0.86: Default to showing message scroller (with title, bigger icon)
|
||||
0.87: Make choosing of font size more repeatable
|
||||
0.88: Adjust padding calculation so messages are spaced out properly even when using international fonts
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
// a message
|
||||
require("messages").pushMessage({"t":"add","id":1575479849,"src":"WhatsApp","title":"My Friend","body":"Hey! How's everything going?",reply:1,negative:1})
|
||||
require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1})
|
||||
require("messages").pushMessage({"t":"add","id":1575479850,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1})
|
||||
require("messages").pushMessage({"t":"add","id":23232,"src":"Skype","title":"Mr. Bobby McBobFace","body":"Boopedy-boop",positive:1,negative:1})
|
||||
require("messages").pushMessage({"t":"add","id":23233,"src":"Skype","title":"Thyttan test","body":"Nummerplåtsbelysning trodo",positive:1,negative:1})
|
||||
require("messages").pushMessage({"t":"add","id":23234,"src":"Skype","title":"Thyttan test 2","body":"Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo",positive:1,negative:1})
|
||||
|
|
@ -391,8 +391,6 @@ function showMessage(msgid, persist) {
|
|||
}
|
||||
}
|
||||
lines = g.setFont(bodyFont).wrapString(body, w);
|
||||
if (lines.length<3)
|
||||
lines.unshift(""); // if less lines, pad them out a bit at the top!
|
||||
}
|
||||
let negHandler,posHandler,rowLeftDraw,rowRightDraw;
|
||||
if (msg.negative) {
|
||||
|
|
@ -432,12 +430,14 @@ function showMessage(msgid, persist) {
|
|||
}
|
||||
let fontHeight = g.setFont(bodyFont).getFontHeight();
|
||||
let lineHeight = (fontHeight>25)?fontHeight:25;
|
||||
if (title.includes("\n")) lineHeight=25; // ensure enough room for 2 lines of title in header
|
||||
if (title.includes("\n") && lineHeight<25) lineHeight=25; // ensure enough room for 2 lines of title in header
|
||||
let linesPerRow = 2;
|
||||
if (fontHeight<17) {
|
||||
lineHeight = 16;
|
||||
linesPerRow = 3;
|
||||
}
|
||||
if ((lines.length+4.5)*lineHeight < Bangle.appRect.h)
|
||||
lines.unshift(""); // if less lines, pad them out a bit at the top!
|
||||
let rowHeight = lineHeight*linesPerRow;
|
||||
let textLineOffset = -(linesPerRow + ((rowLeftDraw||rowRightDraw)?1:0));
|
||||
let msgIcon = require("messageicons").getImage(msg);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "messagegui",
|
||||
"name": "Message UI",
|
||||
"shortName": "Messages",
|
||||
"version": "0.87",
|
||||
"version": "0.88",
|
||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
0.01: App Created w/ clockInfos, bold font, date and time.
|
||||
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.04: Fix date not clearing
|
||||
|
|
|
|||
|
|
@ -2,18 +2,20 @@
|
|||
|
||||
|
||||
|
||||
A beautifully simple, modern clock with two Clock Infos, and a clean UI. Fast-Loads.
|
||||
A beautifully simple, modern clock with three Clock Infos, and a clean UI. Fast-Loads.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Has 2 Clock Infos, that are individually changeable.
|
||||
* 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.
|
||||
|
||||
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
|
@ -9,7 +9,21 @@ Graphics.prototype.setFontBold = function(scale) {
|
|||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
||||
let drawTimeout;
|
||||
let showInlineClkInfo=true;
|
||||
let calcStrLength=function(str,maxLength){
|
||||
|
||||
//too long
|
||||
|
||||
// Example maximum length
|
||||
var truncatedText = str;
|
||||
|
||||
if (str.length > maxLength) {
|
||||
truncatedText = str.substring(0, maxLength - 3) + "...";
|
||||
}
|
||||
|
||||
return truncatedText;
|
||||
};
|
||||
|
||||
//ROUNDED RECT FUNCTION
|
||||
let bRoundedRectangle= function(x1,y1,x2,y2,r) {
|
||||
var f = 1 - r;
|
||||
|
|
@ -48,7 +62,6 @@ let bRoundedRectangle= function(x1,y1,x2,y2,r) {
|
|||
};
|
||||
|
||||
|
||||
|
||||
//CLOCK INFO
|
||||
let clockInfoItems = require("clock_info").load();
|
||||
|
||||
|
|
@ -58,7 +71,7 @@ let clockInfoItems = require("clock_info").load();
|
|||
|
||||
let clockInfoMenuLeft = require("clock_info").addInteractive(clockInfoItems, {
|
||||
// Add the dimensions we're rendering to here - these are used to detect taps on the clock info area
|
||||
x : 10, y: 100, w: 72, h:70,
|
||||
x : 7, y: 100, w: 76, h:70,
|
||||
// You can add other information here you want to be passed into 'options' in 'draw'
|
||||
// This function draws the info
|
||||
draw : (itm, info, options) => {
|
||||
|
|
@ -70,7 +83,7 @@ let clockInfoMenuLeft = require("clock_info").addInteractive(clockInfoItems, {
|
|||
// indicate focus - we're using a border, but you could change color?
|
||||
if (options.focus){
|
||||
// show if focused
|
||||
g.setColor(0,15,255);
|
||||
g.setColor(0,255,15);
|
||||
bRoundedRectangle(options.x,options.y,options.x+options.w,options.y+options.h,8);
|
||||
}else{
|
||||
g.setColor(g.theme.fg);
|
||||
|
|
@ -82,7 +95,7 @@ let clockInfoMenuLeft = require("clock_info").addInteractive(clockInfoItems, {
|
|||
if (info.img){
|
||||
g.drawImage(info.img, midx-12,midy-21);
|
||||
}// draw the image
|
||||
g.setFont("Vector",16).setFontAlign(0,1).drawString(info.text, midx,midy+23); // draw the text
|
||||
g.setFont("Vector",16).setFontAlign(0,1).drawString(calcStrLength(info.text,8), midx,midy+23); // draw the text
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -91,7 +104,7 @@ let clockInfoMenuLeft = require("clock_info").addInteractive(clockInfoItems, {
|
|||
//CLOCK INFO RIGHT DIMENSIONS: 97,113, w:66, h: 55
|
||||
let clockInfoMenuRight = require("clock_info").addInteractive(clockInfoItems, {
|
||||
// Add the dimensions we're rendering to here - these are used to detect taps on the clock info area
|
||||
x : 94, y: 100, w: 72, h:70,
|
||||
x : 91, y: 100, w: 76, h:70,
|
||||
// You can add other information here you want to be passed into 'options' in 'draw'
|
||||
// This function draws the info
|
||||
draw : (itm, info, options) => {
|
||||
|
|
@ -103,7 +116,7 @@ let clockInfoMenuRight = require("clock_info").addInteractive(clockInfoItems, {
|
|||
// indicate focus - we're using a border, but you could change color?
|
||||
if (options.focus){
|
||||
// show if focused
|
||||
g.setColor(0,15,255);
|
||||
g.setColor(0,255,15);
|
||||
bRoundedRectangle(options.x,options.y,options.x+options.w,options.y+options.h,8);
|
||||
}else{
|
||||
g.setColor(g.theme.fg);
|
||||
|
|
@ -115,15 +128,42 @@ let clockInfoMenuRight = require("clock_info").addInteractive(clockInfoItems, {
|
|||
if (info.img){
|
||||
g.drawImage(info.img, midx-12,midy-21);
|
||||
}// draw the image
|
||||
g.setFont("Vector",16).setFontAlign(0,1).drawString(info.text, midx,midy+23); // draw the text
|
||||
g.setFont("Vector",16).setFontAlign(0,1).drawString(calcStrLength(info.text,8), midx,midy+23); // draw the text
|
||||
}
|
||||
});
|
||||
let clockInfoMenuInline;
|
||||
|
||||
if(showInlineClkInfo){
|
||||
clockInfoMenuInline = require("clock_info").addInteractive(clockInfoItems, {
|
||||
// Add the dimensions we're rendering to here - these are used to detect taps on the clock info area
|
||||
x : g.getWidth()/2+6, y: 69, w: 95, h:25,
|
||||
// You can add other information here you want to be passed into 'options' in 'draw'
|
||||
// This function draws the info
|
||||
draw : (itm, info, options) => {
|
||||
// itm: the item containing name/hasRange/etc
|
||||
// info: data returned from itm.get() containing text/img/etc
|
||||
// options: options passed into addInteractive
|
||||
// Clear the background
|
||||
g.reset().clearRect(options.x, options.y, options.x+options.w, options.y+options.h);
|
||||
// indicate focus - we're using a border, but you could change color?
|
||||
if (options.focus){
|
||||
// show if focused
|
||||
g.setColor(0,255,15);
|
||||
|
||||
}
|
||||
// we're drawing center-aligned here
|
||||
//var midx = options.x+options.w/2;
|
||||
var midy=options.y+options.h/2;
|
||||
if (info.img){
|
||||
g.drawImage(info.img, options.x+4,midy-7.2,{scale: 0.63});
|
||||
}// draw the image
|
||||
g.setFont("Vector",15).setFontAlign(-1,0).drawString(calcStrLength(info.text,6), options.x+22,midy+1); // draw the text
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// DRAW FACE
|
||||
let draw = function() {
|
||||
|
|
@ -136,7 +176,7 @@ let draw = function() {
|
|||
// draw the current time (4x size 7 segment)
|
||||
|
||||
// align bottom right
|
||||
|
||||
|
||||
g.setFontBold();
|
||||
|
||||
if(meridian!=""){
|
||||
|
|
@ -149,13 +189,13 @@ let draw = function() {
|
|||
var meridianStrWidth=g.stringWidth(meridian);
|
||||
var totalStrWidth=meridianStrWidth+padding+clkStrWidth;
|
||||
var startX = ((g.getWidth() - totalStrWidth) / 2)+6;
|
||||
g.clearRect(0,0,g.getWidth(),90);
|
||||
g.clearRect(0,0,g.getWidth(),64);
|
||||
g.setFontBold();
|
||||
g.setFontAlign(-1,1);
|
||||
g.drawString(clock, startX, Y+2,true);
|
||||
g.drawString(clock, startX, Y-2);
|
||||
g.setFont("Vector",20);
|
||||
g.setFontAlign(-1,1);
|
||||
g.drawString(meridian, startX + clkStrWidth + padding, Y-5, true);
|
||||
g.drawString(meridian, startX + clkStrWidth + padding, Y-9, true);
|
||||
|
||||
}else{
|
||||
|
||||
|
|
@ -165,15 +205,17 @@ let draw = function() {
|
|||
|
||||
}
|
||||
|
||||
|
||||
// draw the date, in a normal font
|
||||
g.setFont("Vector",18);
|
||||
g.setFontAlign(0,0); // align center bottom
|
||||
g.setFont("Vector",16);
|
||||
g.setFontAlign(1,0); // align center bottom
|
||||
// pad the date - this clears the background if the date were to change length
|
||||
var dateStr = require("locale").dow(new Date(), 1)+", "+ require("locale").month(new Date(), true)+" "+new Date().getDate();
|
||||
g.drawString(" "+dateStr+" ", g.getWidth()/2, Y+9, true /*clear background*/);
|
||||
var dateStr = " "+require("locale").dow(new Date(), 1)+", " +new Date().getDate();
|
||||
//print(g.stringHeight(dateStr));
|
||||
g.drawString(" "+dateStr+" ", g.getWidth()/2+6, Y+9, true /*clear background*/);
|
||||
|
||||
Bangle.drawWidgets();
|
||||
g.setColor("#ffffff");
|
||||
|
||||
|
||||
// queue next draw
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
|
|
@ -196,6 +238,7 @@ Bangle.setUI({
|
|||
delete Graphics.prototype.setFontBold;
|
||||
clockInfoMenuRight.remove();
|
||||
clockInfoMenuLeft.remove();
|
||||
if(showInlineClkInfo) clockInfoMenuInline.remove();
|
||||
|
||||
}});
|
||||
|
||||
|
|
@ -203,4 +246,6 @@ g.clear();
|
|||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
draw();
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
"name": "Modern Clock",
|
||||
"shortName":"Modern Clk",
|
||||
"icon": "icon.png",
|
||||
"version":"0.01",
|
||||
"description": "A modern, simple clock, with two Clock Infos and Fast Loading",
|
||||
"version":"0.04",
|
||||
"description": "A modern, simple clock, with three ClockInfos and Fast Loading",
|
||||
"type":"clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"screenshots" : [ { "url":"Screenshot1.png" },
|
||||
{ "url":"Screenshot2.png" } ],
|
||||
"screenshots" : [ { "url":"Scr1.png" },
|
||||
{ "url":"Scr2.png" } ],
|
||||
"dependencies" : { "clock_info":"module"},
|
||||
"allow_emulator":true,
|
||||
"readme":"README.md",
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@
|
|||
0.10: Use charging state on boot for auto calibration
|
||||
Log additional timestamp for trace log
|
||||
0.11: Minor code improvements
|
||||
0.12: Round monotonic percentage, rename to stable percentage/voltage
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require('Storage').readJSON("powermanager.default.json", true) || {},
|
||||
require('Storage').readJSON("powermanager.json", true) || {}
|
||||
);
|
||||
|
||||
|
||||
if (settings.log) {
|
||||
let logFile = require('Storage').open("powermanager.log","a");
|
||||
let def = require('Storage').readJSON("powermanager.def.json", true) || {};
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
let hw = require('Storage').readJSON("powermanager.hw.json", true) || {};
|
||||
if (!hw.start) hw.start = Date.now();
|
||||
if (!hw.power) hw.power = {};
|
||||
|
||||
|
||||
const saveEvery = 1000 * 60 * 5;
|
||||
const TO_WRAP = ["GPS","Compass","Barometer","HRM","LCD"];
|
||||
|
||||
|
|
@ -28,7 +28,9 @@
|
|||
require('Storage').writeJSON("powermanager.hw.json", hw);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
setInterval(save, saveEvery);
|
||||
|
||||
E.on("kill", ()=>{
|
||||
|
|
@ -75,7 +77,7 @@
|
|||
})(Bangle[functionName]);
|
||||
}
|
||||
|
||||
let functions = {};
|
||||
|
||||
let wrapDeferred = ((o,t) => (a) => {
|
||||
if (a == eval || typeof a == "string") {
|
||||
return o.apply(this, arguments);
|
||||
|
|
@ -131,19 +133,25 @@
|
|||
handleCharging(Bangle.isCharging());
|
||||
}
|
||||
|
||||
var savedBatPercent=E.getBattery();
|
||||
if (settings.forceMonoPercentage){
|
||||
var p = (E.getBattery()+E.getBattery()+E.getBattery()+E.getBattery())/4;
|
||||
var op = E.getBattery;
|
||||
var newPercent =Math.round((E.getBattery()+E.getBattery()+E.getBattery()+E.getBattery()+E.getBattery()+E.getBattery())/6);
|
||||
|
||||
E.getBattery = function() {
|
||||
var current = Math.round((op()+op()+op()+op())/4);
|
||||
if (Bangle.isCharging() && current > p) p = current;
|
||||
if (!Bangle.isCharging() && current < p) p = current;
|
||||
return p;
|
||||
};
|
||||
|
||||
if(Bangle.isCharging()){
|
||||
if(newPercent > savedBatPercent)
|
||||
savedBatPercent = newPercent;
|
||||
}else{
|
||||
if(newPercent < savedBatPercent)
|
||||
savedBatPercent = newPercent;
|
||||
}
|
||||
return savedBatPercent;
|
||||
};
|
||||
}
|
||||
|
||||
if (settings.forceMonoVoltage){
|
||||
var v = (NRF.getBattery()+NRF.getBattery()+NRF.getBattery()+NRF.getBattery())/4;
|
||||
var v = (NRF.getBattery()+NRF.getBattery()+NRF.getBattery()+NRF.getBattery()+NRF.getBattery()+NRF.getBattery())/6;
|
||||
var ov = NRF.getBattery;
|
||||
NRF.getBattery = function() {
|
||||
var current = (ov()+ov()+ov()+ov())/4;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
"id": "powermanager",
|
||||
"name": "Power Manager",
|
||||
"shortName": "Power Manager",
|
||||
"version": "0.11",
|
||||
"description": "Allow configuration of warnings and thresholds for battery charging and display.",
|
||||
"version": "0.12",
|
||||
"description": "Allow configuration of warnings for battery charging, stabilization of voltage, stabilization of battery percentage, and battery logging.",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
"tags": "tool",
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@
|
|||
'Widget': function() {
|
||||
E.showMenu(submenu_widget);
|
||||
},
|
||||
'Monotonic percentage': {
|
||||
'Stable Percentage': {
|
||||
value: !!settings.forceMonoPercentage,
|
||||
onchange: v => {
|
||||
writeSettings("forceMonoPercentage", v);
|
||||
}
|
||||
},
|
||||
'Monotonic voltage': {
|
||||
'Stable Voltage': {
|
||||
value: !!settings.forceMonoVoltage,
|
||||
onchange: v => {
|
||||
writeSettings("forceMonoVoltage", v);
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Add settings to configure prompt
|
||||
0.04: Minor code improvements
|
||||
0.05: Comment out unused function in settings.js
|
||||
0.06: Changed to use E.showMessage() instead of g.drawString() to display power off message. Looks better and is more coherent.
|
||||
|
|
|
|||
|
|
@ -19,21 +19,16 @@ if (showPrompt) {
|
|||
setTimeout(load, 100);
|
||||
return;
|
||||
}
|
||||
g.setFont("6x8",2).setFontAlign(0,0);
|
||||
var x = g.getWidth()/2;
|
||||
var y = g.getHeight()/2 + 10;
|
||||
g.drawString("Powering off...", x, y);
|
||||
|
||||
E.showMessage("Powering off...");
|
||||
|
||||
setTimeout(function() {
|
||||
if (Bangle.softOff) Bangle.softOff(); else Bangle.off();
|
||||
}, 1000);
|
||||
});
|
||||
} else {
|
||||
g.setFont("6x8",2).setFontAlign(0,0);
|
||||
var x = g.getWidth()/2;
|
||||
var y = g.getHeight()/2 + 10;
|
||||
g.drawString("Powering off...", x, y);
|
||||
|
||||
E.showMessage("Powering off...");
|
||||
|
||||
setTimeout(function() {
|
||||
if (Bangle.softOff) Bangle.softOff(); else Bangle.off();
|
||||
}, 1000);
|
||||
|
|
@ -41,4 +36,4 @@ if (showPrompt) {
|
|||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "poweroff",
|
||||
"name": "Poweroff",
|
||||
"shortName":"Poweroff",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Simple app to power off your Bangle.js",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,poweroff,shutdown",
|
||||
|
|
|
|||
|
|
@ -16,3 +16,4 @@
|
|||
0.12: Fix bug where settings would behave as if all were set to false
|
||||
0.13: Update to new touch-event handling
|
||||
0.14: Fix bug in handling changes to `Bangle.appRect`
|
||||
0.15: Workaround bug in 2v26/2v27 firmware
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ var _a, _b;
|
|||
var prosettings = (require("Storage").readJSON("promenu.settings.json", true) || {});
|
||||
(_a = prosettings.naturalScroll) !== null && _a !== void 0 ? _a : (prosettings.naturalScroll = false);
|
||||
(_b = prosettings.wrapAround) !== null && _b !== void 0 ? _b : (prosettings.wrapAround = true);
|
||||
E.showMenu = function (items) {
|
||||
E.showMenu = (function (items) {
|
||||
if (items == null) {
|
||||
g.clearRect(Bangle.appRect);
|
||||
return Bangle.setUI();
|
||||
}
|
||||
var RectRnd = function (x1, y1, x2, y2, r) {
|
||||
var pp = [];
|
||||
pp.push.apply(pp, g.quadraticBezier([x2 - r, y1, x2, y1, x2, y1 + r]));
|
||||
|
|
@ -122,7 +126,6 @@ E.showMenu = function (items) {
|
|||
nameScroll_1 = 0;
|
||||
}, 300, name, v, item, idx, x, iy);
|
||||
}
|
||||
g.setColor(g.theme.fg);
|
||||
iy += fontHeight;
|
||||
idx++;
|
||||
};
|
||||
|
|
@ -204,7 +207,10 @@ E.showMenu = function (items) {
|
|||
else
|
||||
l.select(evt);
|
||||
};
|
||||
Bangle.setUI({
|
||||
var touchcb = (function (_button, xy) {
|
||||
cb(void 0, xy);
|
||||
});
|
||||
var uiopts = {
|
||||
mode: "updown",
|
||||
back: back,
|
||||
remove: function () {
|
||||
|
|
@ -212,11 +218,20 @@ E.showMenu = function (items) {
|
|||
if (nameScroller)
|
||||
clearInterval(nameScroller);
|
||||
Bangle.removeListener("swipe", onSwipe);
|
||||
if (setUITouch)
|
||||
Bangle.removeListener("touch", touchcb);
|
||||
(_a = options.remove) === null || _a === void 0 ? void 0 : _a.call(options);
|
||||
},
|
||||
touch: (function (_button, xy) {
|
||||
cb(void 0, xy);
|
||||
}),
|
||||
}, cb);
|
||||
};
|
||||
var setUITouch = process.env.VERSION >= "2v26";
|
||||
if (!setUITouch) {
|
||||
uiopts.touch = touchcb;
|
||||
}
|
||||
Bangle.setUI(uiopts, cb);
|
||||
if (setUITouch) {
|
||||
Bangle.removeListener("touch", Bangle.touchHandler);
|
||||
delete Bangle.touchHandler;
|
||||
Bangle.on("touch", touchcb);
|
||||
}
|
||||
return l;
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,12 @@ const prosettings = (require("Storage").readJSON("promenu.settings.json", true)
|
|||
prosettings.naturalScroll ??= false;
|
||||
prosettings.wrapAround ??= true;
|
||||
|
||||
E.showMenu = (items?: Menu): MenuInstance => {
|
||||
E.showMenu = ((items?: Menu): MenuInstance | void => {
|
||||
if(items == null){
|
||||
g.clearRect(Bangle.appRect);
|
||||
return Bangle.setUI();
|
||||
}
|
||||
|
||||
const RectRnd = (x1: number, y1: number, x2: number, y2: number, r: number) => {
|
||||
const pp = [];
|
||||
pp.push(...g.quadraticBezier([x2 - r, y1, x2, y1, x2, y1 + r]));
|
||||
|
|
@ -167,7 +172,6 @@ E.showMenu = (items?: Menu): MenuInstance => {
|
|||
}, 300, name, v, item, idx, x, iy);
|
||||
}
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
iy += fontHeight;
|
||||
idx++;
|
||||
}
|
||||
|
|
@ -252,21 +256,47 @@ E.showMenu = (items?: Menu): MenuInstance => {
|
|||
else l.select(evt);
|
||||
};
|
||||
|
||||
Bangle.setUI({
|
||||
mode: "updown",
|
||||
back,
|
||||
remove: () => {
|
||||
if (nameScroller) clearInterval(nameScroller);
|
||||
Bangle.removeListener("swipe", onSwipe);
|
||||
options.remove?.();
|
||||
},
|
||||
touch: ((_button, xy) => {
|
||||
const touchcb = ((_button, xy) => {
|
||||
// since we've specified options.touch,
|
||||
// we need to pass through all taps since the default
|
||||
// touchHandler isn't installed in setUI
|
||||
cb(void 0, xy);
|
||||
}) satisfies TouchCallback,
|
||||
} as SetUIArg<"updown">, cb);
|
||||
}) satisfies TouchCallback;
|
||||
|
||||
const uiopts = {
|
||||
mode: "updown",
|
||||
back: back as () => void,
|
||||
remove: () => {
|
||||
if (nameScroller) clearInterval(nameScroller);
|
||||
Bangle.removeListener("swipe", onSwipe);
|
||||
if(setUITouch)
|
||||
Bangle.removeListener("touch", touchcb);
|
||||
options.remove?.();
|
||||
},
|
||||
} satisfies SetUIArg<"updown">;
|
||||
|
||||
// does setUI install its own touch handler?
|
||||
const setUITouch = process.env.VERSION >= "2v26";
|
||||
if (!setUITouch) {
|
||||
// old firmware, we can use its touch handler - no need for workaround
|
||||
(uiopts as any).touch = touchcb;
|
||||
}
|
||||
|
||||
Bangle.setUI(uiopts, cb);
|
||||
|
||||
if(setUITouch){
|
||||
// new firmware, remove setUI's touch handler and use just our own to
|
||||
// avoid `cb` drawing the menu (as part of setUI's touch handler)
|
||||
// followed by us drawing the menu (as part of our touch handler)
|
||||
//
|
||||
// work around details:
|
||||
// - https://github.com/espruino/Espruino/issues/2648
|
||||
// - https://github.com/orgs/espruino/discussions/7697#discussioncomment-13782299
|
||||
Bangle.removeListener("touch", (Bangle as any).touchHandler);
|
||||
delete (Bangle as any).touchHandler;
|
||||
|
||||
Bangle.on("touch", touchcb);
|
||||
}
|
||||
|
||||
return l;
|
||||
};
|
||||
}) as typeof E.showMenu;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "promenu",
|
||||
"name": "Pro Menu",
|
||||
"version": "0.14",
|
||||
"version": "0.15",
|
||||
"description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.",
|
||||
"icon": "icon.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
|||
|
|
@ -88,3 +88,6 @@ of 'Select Clock'
|
|||
0.77: Save altitude calibration when user exits via reset
|
||||
0.78: Fix menu scroll restore on BangleJS1
|
||||
0.79: Ensure that tapping on pressure/altitude doesn't cause a menu to display temporarily
|
||||
0.80: Add option to set LCD brightness to 0, keep default brightness at 1.
|
||||
0.81: Ensure 'Privacy' (address randomnisation) is only added for Bangle.js 2,
|
||||
and add warnings in documentation that it can cause connection issues
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ This is Bangle.js's main settings menu:
|
|||
* **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:
|
||||
|
||||
|
||||
## 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.)
|
||||
|
|
@ -45,6 +45,8 @@ This is where you adjust settings for an individual app. (eg. Health app: Adjust
|
|||
* **Passkey** allows you to set a passkey that is required to connect and pair to Bangle.js.
|
||||
* **Whitelist** allows you to specify only specific devices that you will let connect to your Bangle.js. Simply choose the menu item, then `Add Device`, and then connect to Bangle.js with the device you want to add. If you are already connected you will have to disconnect first. Changes will take effect when you exit the `Settings` app.
|
||||
* **NOTE:** iOS devices and newer Android devices often implement Address Randomisation and change their Bluetooth address every so often. If you device's address changes, you will be unable to connect until you update the whitelist again.
|
||||
* **Privacy** - (Bangle.js 2 only) enables BLE privacy mode (see [NRF.setSecurity](https://www.espruino.com/Reference#l_NRF_setSecurity)). This randomises the Bangle's MAC address and can also
|
||||
remove advertising of its name. **This can cause connection issues with apps that expect to keep a permanent connection like iOS/Gadgetbridge**
|
||||
|
||||
## LCD
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.79",
|
||||
"version": "0.81",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
|
@ -191,7 +192,7 @@ function BLEMenu() {
|
|||
var hidN = [/*LANG*/"Off", /*LANG*/"Kbrd & Media", /*LANG*/"Kbrd", /*LANG*/"Kbrd & Mouse", /*LANG*/"Joystick"];
|
||||
var privacy = [/*LANG*/"Off", /*LANG*/"Show name", /*LANG*/"Hide name"];
|
||||
|
||||
return {
|
||||
var menu = {
|
||||
'': { 'title': /*LANG*/'Bluetooth' },
|
||||
'< Back': ()=>popMenu(mainMenu()),
|
||||
/*LANG*/'Make Connectable': ()=>makeConnectable(),
|
||||
|
|
@ -209,7 +210,32 @@ function BLEMenu() {
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Privacy': {
|
||||
/*LANG*/'HID': {
|
||||
value: Math.max(0,0 | hidV.indexOf(settings.HID)),
|
||||
min: 0, max: hidN.length-1,
|
||||
format: v => hidN[v],
|
||||
onchange: v => {
|
||||
settings.HID = hidV[v];
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Passkey': {
|
||||
value: settings.passkey?settings.passkey:/*LANG*/"none",
|
||||
onchange: () => setTimeout(() => pushMenu(passkeyMenu())) // graphical_menu redraws after the call
|
||||
},
|
||||
/*LANG*/'Whitelist': {
|
||||
value:
|
||||
(
|
||||
(settings.whitelist_disabled || !settings.whitelist) ? /*LANG*/"Off" : /*LANG*/"On"
|
||||
) + (
|
||||
settings.whitelist
|
||||
? " (" + settings.whitelist.length + ")"
|
||||
: ""
|
||||
),
|
||||
onchange: () => setTimeout(() => pushMenu(whitelistMenu())) // graphical_menu redraws after the call
|
||||
}
|
||||
};
|
||||
if (BANGLEJS2) menu[/*LANG*/'Privacy'] = {
|
||||
min: 0, max: privacy.length-1,
|
||||
format: v => privacy[v],
|
||||
value: (() => {
|
||||
|
|
@ -234,32 +260,8 @@ function BLEMenu() {
|
|||
}
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'HID': {
|
||||
value: Math.max(0,0 | hidV.indexOf(settings.HID)),
|
||||
min: 0, max: hidN.length-1,
|
||||
format: v => hidN[v],
|
||||
onchange: v => {
|
||||
settings.HID = hidV[v];
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Passkey': {
|
||||
value: settings.passkey?settings.passkey:/*LANG*/"none",
|
||||
onchange: () => setTimeout(() => pushMenu(passkeyMenu())) // graphical_menu redraws after the call
|
||||
},
|
||||
/*LANG*/'Whitelist': {
|
||||
value:
|
||||
(
|
||||
(settings.whitelist_disabled || !settings.whitelist) ? /*LANG*/"off" : /*LANG*/"on"
|
||||
) + (
|
||||
settings.whitelist
|
||||
? " (" + settings.whitelist.length + ")"
|
||||
: ""
|
||||
),
|
||||
onchange: () => setTimeout(() => pushMenu(whitelistMenu())) // graphical_menu redraws after the call
|
||||
}
|
||||
};
|
||||
};
|
||||
return menu;
|
||||
}
|
||||
|
||||
function showThemeMenu(pop) {
|
||||
|
|
@ -474,11 +476,11 @@ function LCDMenu() {
|
|||
Object.assign(lcdMenu, {
|
||||
/*LANG*/'LCD Brightness': {
|
||||
value: settings.brightness,
|
||||
min: 0.1,
|
||||
min : BANGLEJS2 ? 0 : 0.1,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
onchange: v => {
|
||||
settings.brightness = v || 1;
|
||||
settings.brightness = v ?? 1;
|
||||
updateSettings();
|
||||
Bangle.setLCDBrightness(settings.brightness);
|
||||
}
|
||||
|
|
@ -1077,4 +1079,4 @@ function showAltitude() {
|
|||
}
|
||||
|
||||
// Show the main menu
|
||||
pushMenu(mainMenu());
|
||||
pushMenu(mainMenu());
|
||||
|
|
|
|||
|
|
@ -15,3 +15,4 @@
|
|||
0.17: Minor code improvements
|
||||
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.20: Increase default sleep thresholds, tweak settings to allow higher ones to be chosen
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ global.sleeplog = {
|
|||
// threshold settings
|
||||
maxAwake: 36E5, // [ms] maximal awake time to count for consecutive sleep
|
||||
minConsec: 18E5, // [ms] minimal time to count for consecutive sleep
|
||||
deepTh: 100, // threshold for deep sleep
|
||||
lightTh: 200, // threshold for light sleep
|
||||
deepTh: 150, // threshold for deep sleep
|
||||
lightTh: 300, // threshold for light sleep
|
||||
wearTemp: 19.5, // temperature threshold to count as worn
|
||||
}, require("Storage").readJSON("sleeplog.json", true) || {})
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id":"sleeplog",
|
||||
"name":"Sleep Log",
|
||||
"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.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
// threshold settings
|
||||
maxAwake: 36E5, // [ms] maximal awake time to count for consecutive sleep
|
||||
minConsec: 18E5, // [ms] minimal time to count for consecutive sleep
|
||||
deepTh: 100, // threshold for deep sleep
|
||||
lightTh: 200, // threshold for light sleep
|
||||
deepTh: 150, // threshold for deep sleep
|
||||
lightTh: 300, // threshold for light sleep
|
||||
wearTemp: 19.5, // temperature threshold to count as worn
|
||||
// app settings
|
||||
breakToD: 12, // [h] time of day when to start/end graphs
|
||||
|
|
@ -324,9 +324,9 @@
|
|||
},
|
||||
/*LANG*/"Deep Sleep": {
|
||||
value: settings.deepTh,
|
||||
step: 1,
|
||||
step: 10,
|
||||
min: 30,
|
||||
max: 200,
|
||||
max: 500,
|
||||
wrap: true,
|
||||
noList: true,
|
||||
onchange: v => {
|
||||
|
|
@ -338,7 +338,7 @@
|
|||
value: settings.lightTh,
|
||||
step: 10,
|
||||
min: 100,
|
||||
max: 400,
|
||||
max: 800,
|
||||
wrap: true,
|
||||
noList: true,
|
||||
onchange: v => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Added pie chart for visualization, tweaked UI.
|
||||
0.03: Fixed bug with total storage pie chart.
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
|
||||
<!-- Toggle Buttons -->
|
||||
<div class="btn-group" style="margin: 1em; display: flex; justify-content: center;">
|
||||
<button id="tableButton" class="btn btn-primary">View Table</button>
|
||||
<button id="pieChartButton" class="btn">View Pie Chart</button>
|
||||
</div>
|
||||
|
||||
<!-- Table View -->
|
||||
<div id="storageTable"></div>
|
||||
|
||||
<!-- Chart View -->
|
||||
<div id="storagePieChart" style="display: none; flex-direction: column; align-items: center;">
|
||||
<div id="piechart" style="width: 100%; max-width: 600px; height: 400px;"></div>
|
||||
<div id="totalStoragePie" style="width: 100%; max-width: 600px; height: 300px;"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let globalApps = [];
|
||||
let storageStats = null;
|
||||
|
||||
function onInit(device) {
|
||||
Util.showModal("Reading Storage...");
|
||||
Puck.eval(`(()=>{
|
||||
let getApps = () => require("Storage").list(/\\.info$/).map(appInfoName => {
|
||||
let appInfo = require("Storage").readJSON(appInfoName,1)||{};
|
||||
var fileSize = 0, dataSize = 0;
|
||||
appInfo.files.split(",").forEach(f => fileSize += require("Storage").read(f).length);
|
||||
var data = (appInfo.data||"").split(";");
|
||||
function wildcardToRegexp(wc) {
|
||||
return new RegExp("^"+wc.replaceAll(".","\\\\.").replaceAll("?",".*")+"$");
|
||||
}
|
||||
if (data[0]) data[0].split(",").forEach(wc => {
|
||||
require("Storage").list(wildcardToRegexp(wc), {sf:false}).forEach(f => {
|
||||
dataSize += require("Storage").read(f).length
|
||||
});
|
||||
});
|
||||
if (data[1]) data[1].split(",").forEach(wc => {
|
||||
require("Storage").list(wildcardToRegexp(wc), {sf:true}).forEach(f => {
|
||||
dataSize += require("Storage").open(f,"r").getLength();
|
||||
});
|
||||
});
|
||||
return [appInfo.id, fileSize, dataSize];
|
||||
});
|
||||
return [getApps(), require(\"Storage\").getStats()]; })()`, function(result) {
|
||||
Util.hideModal();
|
||||
globalApps = result[0].sort((a,b) => (b[1]+b[2]) - (a[1]+a[2]));
|
||||
storageStats = result[1];
|
||||
|
||||
if (globalApps.length === 0) {
|
||||
document.getElementById("storageTable").innerHTML = "<p>No apps found</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
drawTable();
|
||||
});
|
||||
}
|
||||
function roundDecimal(num){
|
||||
return Math.round(num * 10) / 10;
|
||||
}
|
||||
function drawTable() {
|
||||
document.getElementById("storageTable").innerHTML = `
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>App</th>
|
||||
<th>Code (kb)</th>
|
||||
<th>Data (kb)</th>
|
||||
<th>Total (kb)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${globalApps.map(app => `
|
||||
<tr>
|
||||
<td>${app[0]}</td>
|
||||
<td>${(app[1]/1000).toFixed(1)}</td>
|
||||
<td>${(app[2]/1000).toFixed(1)}</td>
|
||||
<td>${((app[1]+app[2])/1000).toFixed(1)}</td>
|
||||
</tr>`).join("")}
|
||||
</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
function drawChart() {
|
||||
if (globalApps.length === 0) return;
|
||||
|
||||
// App-specific chart
|
||||
const chartData = [
|
||||
['App', 'Total Size (KB)']
|
||||
].concat(globalApps.map(app => [app[0], roundDecimal((app[1] + app[2])/1000)]));
|
||||
|
||||
const data = google.visualization.arrayToDataTable(chartData);
|
||||
|
||||
const options = {
|
||||
title: 'App Storage Breakdown (KBs)',
|
||||
chartArea: { width: '90%', height: '80%' },
|
||||
legend: { position: 'bottom' }
|
||||
};
|
||||
|
||||
const chart = new google.visualization.PieChart(document.getElementById('piechart'));
|
||||
chart.draw(data, options);
|
||||
|
||||
// Total storage chart
|
||||
if (storageStats) {
|
||||
const usedKB = roundDecimal(storageStats.fileBytes / 1000);
|
||||
const freeKB = roundDecimal(storageStats.freeBytes / 1000);
|
||||
const trashKB = roundDecimal(storageStats.trashBytes / 1000);
|
||||
const totalData = google.visualization.arrayToDataTable([
|
||||
['Type', 'KB'],
|
||||
['Used', usedKB],
|
||||
['Free', freeKB],
|
||||
['Trash', trashKB],
|
||||
]);
|
||||
|
||||
const totalOptions = {
|
||||
title: 'Total Storage Usage (KBs)',
|
||||
|
||||
chartArea: { width: '90%', height: '80%' },
|
||||
legend: { position: 'bottom' }
|
||||
};
|
||||
|
||||
const totalChart = new google.visualization.PieChart(document.getElementById('totalStoragePie'));
|
||||
totalChart.draw(totalData, totalOptions);
|
||||
}
|
||||
}
|
||||
|
||||
google.charts.load('current', {'packages':['corechart']});
|
||||
|
||||
|
||||
document.getElementById("pieChartButton").addEventListener("click", function () {
|
||||
document.getElementById("storageTable").style.display = "none";
|
||||
document.getElementById("storagePieChart").style.display = "flex";
|
||||
drawChart();
|
||||
this.classList.add("btn-primary");
|
||||
document.getElementById("tableButton").classList.remove("btn-primary");
|
||||
});
|
||||
|
||||
document.getElementById("tableButton").addEventListener("click", function () {
|
||||
document.getElementById("storageTable").style.display = "block";
|
||||
document.getElementById("storagePieChart").style.display = "none";
|
||||
drawTable();
|
||||
this.classList.add("btn-primary");
|
||||
document.getElementById("pieChartButton").classList.remove("btn-primary");
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "storageanalyzer",
|
||||
"name": "Storage Analyzer",
|
||||
"version": "0.03",
|
||||
"description": "Analyzes Bangle.js storage and shows which apps are using storage space",
|
||||
"icon": "icon.png",
|
||||
"type": "RAM",
|
||||
"tags": "tool,storage,flash,memory",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": []
|
||||
}
|
||||
|
|
@ -6,3 +6,4 @@
|
|||
0.05: Make the "App source not found" warning less buggy
|
||||
0.06: Fixed a crash if an app has no tags (app.tags is undefined)
|
||||
0.07: Clear cached app list when updating showClocks setting
|
||||
0.08: Add haptic feedback option when selecting app or category in menu, increase vector size limit in settings
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ Settings
|
|||
|
||||
- `Font` - The font used (`4x6`, `6x8`, `12x20`, `6x15` or `Vector`). Default `12x20`.
|
||||
- `Vector Font Size` - The size of the font if `Font` is set to `Vector`. Default `10`.
|
||||
- `Haptic Feedback` - Whether or not to vibrate slightly when selecting an app or category in the launcher. Default `No`.
|
||||
- `Show Clocks` - If set to `No` then clocks won't appear in the app list. Default `Yes`.
|
||||
- `Fullscreen` - If set to `Yes` then widgets won't be loaded. Default `No`.
|
||||
|
||||
|
|
@ -28,3 +29,4 @@ Contributors
|
|||
|
||||
- [atjn](https://github.com/atjn)
|
||||
- [BlueFox4](https://github.com/BlueFox4)
|
||||
- [RKBoss6](https://github.com/RKBoss6)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ let vectorval = 20;
|
|||
let font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
fullscreen: false
|
||||
fullscreen: false,
|
||||
buzz:false
|
||||
}, s.readJSON("taglaunch.json", true) || {});
|
||||
if ("vectorsize" in settings)
|
||||
vectorval = parseInt(settings.vectorsize);
|
||||
|
|
@ -108,15 +109,25 @@ let showTagMenu = (tag) => {
|
|||
}
|
||||
},
|
||||
select : i => {
|
||||
let app = appsByTag[tag][i];
|
||||
if (!app) return;
|
||||
if (!app.src || require("Storage").read(app.src)===undefined) {
|
||||
Bangle.setUI();
|
||||
E.showMessage(/*LANG*/"App Source\nNot found");
|
||||
setTimeout(showMainMenu, 2000);
|
||||
} else {
|
||||
load(app.src);
|
||||
const loadApp = () => {
|
||||
let app = appsByTag[tag][i];
|
||||
if (!app) return;
|
||||
if (!app.src || require("Storage").read(app.src)===undefined) {
|
||||
Bangle.setUI();
|
||||
E.showMessage(/*LANG*/"App Source\nNot found");
|
||||
setTimeout(showMainMenu, 2000);
|
||||
} else {
|
||||
load(app.src);
|
||||
}
|
||||
};
|
||||
if(settings.buzz){
|
||||
Bangle.buzz(25);
|
||||
//let the buzz have effect
|
||||
setTimeout(loadApp,27);
|
||||
}else{
|
||||
loadApp();
|
||||
}
|
||||
|
||||
},
|
||||
back : showMainMenu,
|
||||
remove: unload
|
||||
|
|
@ -138,6 +149,7 @@ let showMainMenu = () => {
|
|||
}
|
||||
},
|
||||
select : i => {
|
||||
if(settings.buzz)Bangle.buzz(25);
|
||||
let tag = tagKeys[i];
|
||||
showTagMenu(tag);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
"id": "taglaunch",
|
||||
"name": "Tag Launcher",
|
||||
"shortName": "Taglauncher",
|
||||
"version": "0.07",
|
||||
"description": "Launcher that puts all applications into submenus based on their tag. With many applications installed this can result in a faster application selection than the linear access of the default launcher.",
|
||||
"version": "0.08",
|
||||
"description": "Launcher that puts all applications into submenus based on their tag. With many applications installed this can result in a faster application selection than the linear access from the default launcher.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"type": "launch",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
(function(back) {
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
fullscreen: false
|
||||
fullscreen: false,
|
||||
buzz:false
|
||||
}, require("Storage").readJSON("taglaunch.json", true) || {});
|
||||
|
||||
let fonts = g.getFonts();
|
||||
|
|
@ -21,9 +22,16 @@
|
|||
},
|
||||
/*LANG*/"Vector Font Size": {
|
||||
value: settings.vectorsize || 10,
|
||||
min:10, max: 20,step:1,wrap:true,
|
||||
min:10, max: 25,step:1,wrap:true,
|
||||
onchange: (m) => {save("vectorsize", m)}
|
||||
},
|
||||
/*LANG*/"Haptic Feedback": {
|
||||
value: settings.buzz == true,
|
||||
onchange: (m) => {
|
||||
save("buzz", m);
|
||||
|
||||
}
|
||||
},
|
||||
/*LANG*/"Show Clocks": {
|
||||
value: settings.showClocks == true,
|
||||
onchange: (m) => {
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Get time zone from settings for showing the clock
|
||||
0.05: Minor code improvements
|
||||
0.06: Adjust format of title, save counter before leaving help screen
|
||||
0.07: Refactor code, fix stuttering timer, add settings menu
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
A simple timer. You can easily set up the time. The initial time is 2:30
|
||||
|
||||
On the first screen, you can
|
||||
- tap to get help
|
||||
- double tap to get help
|
||||
- swipe up/down to change the timer by +/- one minute
|
||||
- swipe left/right to change the time by +/- 15 seconds
|
||||
- press Btn1 to start
|
||||
|
|
@ -12,24 +12,31 @@ Press Btn1 again to stop the timer
|
|||
- when time is up, your Bangle will buzz for 15 seconds
|
||||
- and it will count up to 60 seconds and stop after that
|
||||
|
||||
## Images
|
||||
_1. Startscreen_
|
||||
The time changes can be adjusted in the settings menu.
|
||||
|
||||

|
||||
## Images
|
||||
_1. Start screen_
|
||||
|
||||

|
||||
Current time is displayed below the Title. Initial time is 2:30.
|
||||
|
||||
_2. Help Screen_
|
||||
|
||||

|
||||

|
||||
|
||||
_3. Tea Timer running_
|
||||
|
||||

|
||||
Remainig time is shown in big font size. Above the initial time is shown.
|
||||

|
||||
Remainig time is shown in big font size.
|
||||
|
||||
_4. When time is up_
|
||||
_4. Pause Timer
|
||||
|
||||

|
||||

|
||||
While the timer is running, you can pause and unpause it by pressing BTN1.
|
||||
|
||||
_5. When time is up_
|
||||
|
||||

|
||||
When time is up, the watch will buzz for 15 seconds. It will count up to 60 seconds.
|
||||
|
||||
## Requests
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |