merge with upstream

pull/812/head
hughbarney 2021-09-15 21:44:19 +01:00
commit c05083a66a
39 changed files with 622 additions and 50 deletions

View File

@ -49,7 +49,7 @@ easily distinguish between file types, we use the following:
## Adding your app to the menu
* Come up with a unique (all lowercase, nu spaces) name, we'll assume `7chname`. Bangle.js
* Come up with a unique (all lowercase, no spaces) name, we'll assume `7chname`. Bangle.js
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
try and keep filenames short to avoid overflowing the buffer.
* Create a folder called `apps/<id>`, lets assume `apps/7chname`

View File

@ -571,7 +571,7 @@
{ "id": "weather",
"name": "Weather",
"icon": "icon.png",
"version":"0.05",
"version":"0.06",
"description": "Show Gadgetbridge weather report",
"readme": "readme.md",
"tags": "widget,outdoors",
@ -1517,7 +1517,7 @@
"name": "OpenStreetMap",
"shortName":"OpenStMap",
"icon": "app.png",
"version":"0.08",
"version":"0.09",
"description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are",
"tags": "outdoors,gps,b2",
"custom": "custom.html", "customConnect":true,
@ -1944,26 +1944,16 @@
"id": "largeclock",
"name": "Large Clock",
"icon": "largeclock.png",
"version": "0.09",
"version": "0.10",
"description": "A readable and informational digital watch, with date, seconds and moon phase",
"readme": "README.md",
"tags": "clock",
"type": "clock",
"allow_emulator": true,
"storage": [
{
"name": "largeclock.app.js",
"url": "largeclock.js"
},
{
"name": "largeclock.img",
"url": "largeclock-icon.js",
"evaluate": true
},
{
"name": "largeclock.settings.js",
"url": "settings.js"
}
{"name": "largeclock.app.js", "url": "largeclock.js"},
{"name": "largeclock.img", "url": "largeclock-icon.js", "evaluate": true},
{"name": "largeclock.settings.js", "url": "settings.js"}
],
"data": [
{"name":"largeclock.json"}
@ -3265,14 +3255,15 @@
"name": "Hour Strike",
"shortName": "Hour Strike",
"icon": "app-icon.png",
"version": "0.07",
"version": "0.08",
"description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
"tags": "tool,alarm",
"readme": "README.md",
"storage": [
{"name":"hourstrike.app.js","url":"app.js"},
{"name":"hourstrike.boot.js","url":"boot.js"},
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true}
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true},
{"name":"hourstrike.json","url":"hourstrike.json"}
]
},
{ "id": "whereworld",
@ -3443,6 +3434,74 @@
{"name":"app.json"}
]
},
{ "id": "shortcuts",
"name": "Shortcuts",
"shortName":"Shortcuts",
"icon": "app.png",
"version":"0.01",
"description": "Quickly load your favourite apps from (almost) any watch face.",
"tags": "tool",
"type": "bootloader",
"readme": "README.md",
"storage": [
{"name":"shortcuts.boot.js","url":"boot.js"},
{"name":"shortcuts.settings.js","url":"settings.js"}
],
"data": [
{"name":"shortcuts.json"}
]
},
{ "id": "vectorclock",
"name": "Vector Clock",
"icon": "app.png",
"version": "0.02",
"description": "A digital clock that uses the built-in vector font.",
"tags": "clock",
"type": "clock",
"allow_emulator": true,
"storage": [
{"name":"vectorclock.app.js","url":"app.js"},
{"name":"vectorclock.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "fd6fdetect",
"name": "fd6fdetect",
"shortName":"fd6fdetect",
"icon": "app.png",
"version":"0.1",
"description": "Allows you to see 0xFD6F beacons near you.",
"tags": "tool",
"storage": [
{"name":"fd6fdetect.app.js","url":"app.js"},
{"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "choozi",
"name": "Choozi",
"icon": "app.png",
"version":"0.01",
"description": "Choose people or things at random using Bangle.js.",
"tags": "tool",
"readme": "README.md",
"allow_emulator":true,
"storage": [
{"name":"choozi.app.js","url":"app.js"},
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "widclkbttm",
"name": "Digital clock (Bottom) widget",
"shortName":"Digital clock Bottom Widget",
"icon": "widclkbttm.png",
"version":"0.03",
"description": "Displays time in the bottom area.",
"readme": "README.md",
"tags": "widget",
"type": "widget",
"storage": [
{"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"}
]
},
{ "id": "pastel",
"name": "Pastel Clock",
"shortName": "Pastel",

1
apps/choozi/ChangeLog Normal file
View File

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

27
apps/choozi/README.md Normal file
View File

@ -0,0 +1,27 @@
# Choozi
Choose people or things at random using Bangle.js.
<iframe width="560" height="315" src="https://www.youtube.com/embed/4cqOLNM5ei8" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Usage
You can use Choozi to pick a person to play first in a board game. With all
the players seated in a circle, set the number of segments equal to the number
of players, ensure that each person knows which colour represents them, and then
choose a segment. After a short animation, the chosen segment will fill the screen.
You can use Choozi to randomly select an element from any set with 2 to 13 members,
as long as you can define a bijection between members of the set and coloured
segments on the Bangle.js display.
## Controls
BTN1: increase the number of segments
BTN2: choose a segment at random
BTN3: decrease the number of segments
## Creator
James Stanley
September 2021

1
apps/choozi/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))

208
apps/choozi/app.js Normal file
View File

@ -0,0 +1,208 @@
/* Choozi - Choose people or things at random using Bangle.js.
* Inspired by the "Chwazi" Android app
*
* James Stanley 2021
*/
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
var stepAngle = 0.18; // radians - resolution of polygon
var gapAngle = 0.035; // radians - gap between segments
var perimMin = 110; // px - min. radius of perimeter
var perimMax = 120; // px - max. radius of perimeter
var segmentMax = 106; // px - max radius of filled-in segment
var segmentStep = 5; // px - step size of segment fill animation
var circleStep = 4; // px - step size of circle fill animation
// rolling ball animation:
var maxSpeed = 0.08; // rad/sec
var minSpeed = 0.001; // rad/sec
var animStartSteps = 300; // how many steps before it can start slowing?
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
var ballSize = 3; // px - ball radius
var ballTrack = 100; // px - radius of ball path
var centreX = 120; // px - centre of screen
var centreY = 120; // px - centre of screen
var fontSize = 50; // px
var radians = 2*Math.PI; // radians per circle
var defaultN = 3; // default value for N
var minN = 2;
var maxN = colours.length;
var N;
var arclen;
// https://www.frankmitchell.org/2015/01/fisher-yates/
function shuffle (array) {
var i = 0
, j = 0
, temp = null;
for (i = array.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// draw an arc between radii minR and maxR, and between
// angles minAngle and maxAngle
function arc(minR, maxR, minAngle, maxAngle) {
var step = stepAngle;
var angle = minAngle;
var inside = [];
var outside = [];
var c, s;
while (angle < maxAngle) {
c = Math.cos(angle);
s = Math.sin(angle);
inside.push(centreX+c*minR); // x
inside.push(centreY+s*minR); // y
// outside coordinates are built up in reverse order
outside.unshift(centreY+s*maxR); // y
outside.unshift(centreX+c*maxR); // x
angle += step;
}
c = Math.cos(maxAngle);
s = Math.sin(maxAngle);
inside.push(centreX+c*minR);
inside.push(centreY+s*minR);
outside.unshift(centreY+s*maxR);
outside.unshift(centreX+c*maxR);
var vertices = inside.concat(outside);
g.fillPoly(vertices, true);
}
// draw the arc segments around the perimeter
function drawPerimeter() {
g.clear();
for (var i = 0; i < N; i++) {
g.setColor(colours[i%colours.length]);
var minAngle = (i/N)*radians;
arc(perimMin,perimMax,minAngle,minAngle+arclen);
}
}
// animate a ball rolling around and settling at "target" radians
function animateChoice(target) {
var angle = 0;
var speed = 0;
var oldx = -10;
var oldy = -10;
var decelFromAngle = -1;
var allowDecel = false;
for (var i = 0; true; i++) {
angle = angle + speed;
if (angle > radians) angle -= radians;
if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) {
speed = speed + accel;
if (speed > maxSpeed) {
speed = maxSpeed;
/* when we reach max speed, we know how long it takes
* to accelerate, and therefore how long to decelerate, so
* we can work out what angle to start decelerating from */
if (decelFromAngle < 0) {
decelFromAngle = target-angle;
while (decelFromAngle < 0) decelFromAngle += radians;
while (decelFromAngle > radians) decelFromAngle -= radians;
}
}
} else {
if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true;
if (allowDecel) speed = speed - accel;
if (speed < minSpeed) speed = minSpeed;
if (speed == minSpeed && angle < target && angle+speed >= target) return;
}
var r = i/2;
if (r > ballTrack) r = ballTrack;
var x = centreX+Math.cos(angle)*r;
var y = centreY+Math.sin(angle)*r;
g.setColor('#000000');
g.fillCircle(oldx,oldy,ballSize+1);
g.setColor('#ffffff');
g.fillCircle(x, y, ballSize);
oldx=x;
oldy=y;
}
}
// choose a winning segment and animate its selection
function choose() {
var chosen = Math.floor(Math.random()*N);
var minAngle = (chosen/N)*radians;
var maxAngle = minAngle + arclen;
animateChoice((minAngle+maxAngle)/2);
g.setColor(colours[chosen%colours.length]);
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
arc(i, perimMax, minAngle, maxAngle);
arc(0, perimMax, minAngle, maxAngle);
for (var r = 1; r < segmentMax; r += circleStep)
g.fillCircle(centreX,centreY,r);
g.fillCircle(centreX,centreY,segmentMax);
}
// draw the current value of N in the middle of the screen, with
// up/down arrows
function drawN() {
g.setColor('#ffffff');
g.setFont("Vector",fontSize);
g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2);
if (N < maxN)
g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]);
if (N > minN)
g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]);
}
// update number of segments, with min/max limit, "arclen" update,
// and screen reset
function setN(n) {
N = n;
if (N < minN) N = minN;
if (N > maxN) N = maxN;
arclen = radians/N - gapAngle;
drawPerimeter();
}
// save N to choozi.txt
function writeN() {
var file = require("Storage").open("choozi.txt","w");
file.write(N);
}
// load N from choozi.txt
function readN() {
var file = require("Storage").open("choozi.txt","r");
var n = file.readLine();
if (n !== undefined) setN(parseInt(n));
else setN(defaultN);
}
shuffle(colours); // is this really best?
Bangle.setLCDMode("direct");
Bangle.setLCDTimeout(0); // keep screen on
readN();
drawN();
setWatch(() => {
setN(N+1);
drawN();
}, BTN1, {repeat:true});
setWatch(() => {
writeN();
drawPerimeter();
choose();
}, BTN2, {repeat:true});
setWatch(() => {
setN(N-1);
drawN();
}, BTN3, {repeat:true});

BIN
apps/choozi/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -0,0 +1 @@
0.1: Added source code

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwIjgg/gAp0IgfAiAFBjkP+E4AoM8n/8ngFBvn//8+AoP//Ef/4FBv/Agf+AoMPwEB+AFCjEYAoUenk8vAvCAoIvCnAFBjgFCC4IFCCgUeEQNwAoMO+EPuPD4eOAoPz8fH54FH+IRBx4FBDogpFGoxBFJopZFMopxFPoqJFSoqhFVoq5FgAFBa6gAW"))

23
apps/fd6fdetect/app.js Normal file
View File

@ -0,0 +1,23 @@
g.clear();
let amount = 'global value';
function FindFD6FBeacons() {
NRF.findDevices(function(devices) {
g.setFont('Vector', 75);
g.setFontAlign(0,0);
var amount = devices.length;
g.clear();
g.drawString(amount, 125, 100);
if (amount == 1) {
g.setFont('Vector', 25);
g.drawString('FD6F', 125, 150);
g.drawString('beacon', 125, 175);
g.drawString('nearby', 125, 200);
} else{
g.setFont('Vector', 25);
g.drawString('FD6F', 125, 150);
g.drawString('beacons', 125, 175);
g.drawString('nearby', 125, 200);
}
}, {timeout : 1000, filters : [{services: ['fd6f'] }] });
}
setInterval(FindFD6FBeacons, 2000);

BIN
apps/fd6fdetect/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -23,4 +23,4 @@
0.21: Fix HRM setting
0.22: Respect Quiet Mode
0.23: Allow notification dismiss to remove from phone too
0.24: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
0.24: tag HRM power requests to allow this to work alongside other widgets/apps (fix #799)

View File

@ -5,3 +5,4 @@
0.05: Add display for the next strike time
0.06: Move the next strike time to the first row of display
0.07: Change the boot function to avoid reloading the entire watch
0.08: Default to no strikes. Fix file-not-found issue during the first boot. Add data file.

View File

@ -1,25 +1,10 @@
const storage = require('Storage');
let settings;
var settings = storage.readJSON('hourstrike.json', 1);
function updateSettings() {
storage.write('hourstrike.json', settings);
}
function resetSettings() {
settings = {
interval: 3600,
start: 9,
end: 21,
vlevel: 0.5,
next_hour: -1,
next_minute: -1,
};
updateSettings();
}
settings = storage.readJSON('hourstrike.json', 1);
if (!settings) resetSettings();
function showMainMenu() {
var mode_txt = ['Off','1 min','5 min','10 min','1/4 h','1/2 h','1 h'];
var mode_interval = [-1,60,300,600,900,1800,3600];

View File

@ -1,6 +1,6 @@
(function() {
function setup () {
var settings = require('Storage').readJSON('hourstrike.json',1)||[];
var settings = require('Storage').readJSON('hourstrike.json',1);
var t = new Date();
var t_min_sec = t.getMinutes()*60+t.getSeconds();
var wait_msec = settings.interval>0?(settings.interval-t_min_sec%settings.interval)*1000:-1;

View File

@ -0,0 +1 @@
{"interval":-1,"start":9,"end":21,"vlevel":0.5,"next_hour":-1,"next_minute":-1}

View File

@ -7,3 +7,4 @@
0.07: Don't clear all intervals during initialisation
0.08: Use Bangle.setUI for button/launcher handling
0.09: fix font size for latest firmwares
0.10: Configure the side text direction based on the wrist on which you wear your watch

View File

@ -7,6 +7,7 @@ A readable and informational digital watch, with date, seconds and moon phase an
- Readable
- Informative: hours, minutes, secondsa, date, year and moon phase
- Pairs nicely with any other apps: in setting > large clock any installed app can be assigned to BTN1 and BTN3 in order to open it easily directly from the watch, without the hassle of passing trough the launcher. For example BTN1 can be assigned to alarm and BTN3 to chronometer.
- Configure the text direction on the side depending on the wrist on which you wear your watch.
## How to use it

View File

@ -14,6 +14,9 @@ const settings = require("Storage").readJSON("largeclock.json", 1)||{};
const BTN1app = settings.BTN1 || "";
const BTN3app = settings.BTN3 || "";
const right_hand = !!settings.right_hand;
const rotation = right_hand ? 3 : 1;
function drawMoon(d) {
const BLACK = 0,
MOON = 0x41f,
@ -145,9 +148,9 @@ function drawTime(d) {
g.setColor(1, 50, 1);
g.drawString(minutes, 40, 130, true);
g.setFont("Vector", 20);
g.setRotation(3);
g.drawString(`${dow} ${day} ${month}`, 60, 10, true);
g.drawString(year, is12Hour ? 46 : 75, 205, true);
g.setRotation(rotation);
g.drawString(`${dow} ${day} ${month}`, 60, right_hand?10:205, true);
g.drawString(year, is12Hour?(right_hand?56:120):(right_hand?85:115), right_hand?205:10, true);
lastMinutes = minutes;
}
g.setRotation(0);

View File

@ -1,4 +1,5 @@
{
"BTN1": "",
"BTN3": ""
"BTN3": "",
"right_hand": false
}

View File

@ -28,7 +28,8 @@
const settings = s.readJSON("largeclock.json", 1) || {
BTN1: "",
BTN3: ""
BTN3: "",
right_hand: false
};
function showApps(btn) {
@ -67,10 +68,19 @@
}
const mainMenu = {
"": { title: "Large Clock Settings" },
"": { title: "Large Clock" },
"< Back": back,
"BTN1 app": () => showApps("BTN1"),
"BTN3 app": () => showApps("BTN3")
"BTN3 app": () => showApps("BTN3"),
"On right hand": {
value: !!settings.right_hand,
format: v=>v?"Yes":"No",
onchange: v=>{
settings.right_hand = v;
s.writeJSON("largeclock.json", settings);
}
}
};
E.showMenu(mainMenu);
});

View File

@ -6,3 +6,4 @@
0.06: Add support for scrolling, option for 3 bit maps
0.07: Move to 96px tiles - less files (64 -> 25) and speed up rendering
0.08: Update for drag event refactor
0.09: Use current theme cols when drawing GPS info

View File

@ -24,10 +24,7 @@ function drawMarker() {
var fix;
Bangle.on('GPS',function(f) {
fix=f;
g.clearRect(0,y1,240,y1+8);
g.setColor(1,1,1);
g.setFont("6x8");
g.setFontAlign(0,0);
g.reset().clearRect(0,y1,240,y1+8).setFont("6x8").setFontAlign(0,0);
var txt = fix.satellites+" satellites";
if (!fix.fix)
txt += " - NO FIX";

1
apps/shortcuts/ChangeLog Normal file
View File

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

7
apps/shortcuts/README.md Normal file
View File

@ -0,0 +1,7 @@
# Shortcuts
Any installed app can be assigned to BTN1 and BTN3 and launched directly from compatible watch faces. This works with any watch face that uses `Bangle.setUI("clock")`.
## Credits
<a target="_blank" href="https://icons8.com/icon/i1z7pQ2orcJk/shortcut">Shortcut</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>

BIN
apps/shortcuts/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

16
apps/shortcuts/boot.js Normal file
View File

@ -0,0 +1,16 @@
(function() {
var sui = Bangle.setUI;
Bangle.setUI = function(mode, cb) {
if (mode!="clock") return sui(mode,cb);
return sui("clockupdown", (dir) => {
let settings = require("Storage").readJSON("shortcuts.json", 1)||{};
console.log(settings);
if (dir == -1) {
if (settings.BTN1) load(settings.BTN1);
} else if (dir == 1) {
if (settings.BTN3) load(settings.BTN3);
}
});
};
})();

View File

@ -0,0 +1,66 @@
(function(back) {
const s = require("Storage");
const apps = s
.list(/\.info$/)
.map(app => {
var a = s.readJSON(app, 1);
return a && (a.type=="app" || a.type=="clock" || !a.type) && {n: a.name, src: a.src};
})
.filter(Boolean);
apps.sort((a, b) => {
if (a.n < b.n) return -1;
if (a.n > b.n) return 1;
return 0;
});
apps.push({n: "NONE", src: null});
const settings = s.readJSON("shortcuts.json", 1) || {
BTN1: null,
BTN3: null
};
function showApps(btn) {
function format(v) {
return v === settings[btn] ? "*" : "";
}
function onchange(v) {
settings[btn] = v;
s.writeJSON("shortcuts.json", settings);
}
const btnMenu = {
"": {
title: `Apps for ${btn}`
},
"< Back": () => E.showMenu(mainMenu)
};
if (apps.length > 0) {
for (let a of apps) {
btnMenu[a.n] = {
value: a.src,
format: format,
onchange: onchange
};
}
} else {
btnMenu["...No Apps..."] = {
value: undefined,
format: () => "",
onchange: () => {}
};
}
E.showMenu(btnMenu);
}
const mainMenu = {
"": { title: "Shortcuts Settings" },
"< Back": back,
"BTN1 app": () => showApps("BTN1"),
"BTN3 app": () => showApps("BTN3")
};
E.showMenu(mainMenu);
});

View File

@ -0,0 +1,2 @@
0.1: New watch face
0.2: Use Bangle.setUI for button/launcher handling

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEIf4AxgMhgUAiIHCmYKCmcgBAUCmQDEgUzkcg+QJBl//AYfzDgX///wAYcCmMzmQXC+c/AYM/kU/iEAgfzkfyAYcCAYM/HIUwC4QxCkEhgcwEYIDDgUwgcSC4QsBLQf/iATEAYcBiB3FC4cvKAIvINAMxgSGDC4UT/4ICC5EDkcjI40/mIHCgaNBC4IDCBAMDmUiXwU/mcQ/8zAYMyQ4M/RYQDBC4yxBIgIDDE4M//5FBAYasBifxf5QA/AC0iAALFDA4ICBgMhC5SBB//wA4gCBUoIXLXYKaBAAUjC54KJC6Ejmcxe4MAiczC4IJBmEjNwILBL4b5Bl7vCD4IEB+cCNgUP+A3EL4MyC4IkBiE/BoMD+cP+MvCoPygfxI4wXBA4UD+QZBh8wgacB/4ODC5YvC+EfC4JVBiAXLgP/n5JBZgcPSwgXIRYPz+cBQoIXBLwgARgU/c4gAQJYJeDF6TXBAH5OMmUBmcQkcgicxBQMTkBbDBIMCSAcTl8jmcxmXymczBQIECCIQEBkbDBAAUfFgMwgPymUDiUg+EjiUwgUhBIMQC4cCfgIXBeAINBicwC4JBBgY7BgcAC4cfkEDkUx+UAmUjBQPxmZZDBIQXDl//kQBC+UvDQIKBmM//4FCBYP/bX4A/ACIA="))

93
apps/vectorclock/app.js Normal file
View File

@ -0,0 +1,93 @@
const is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
const locale = require("locale");
function padNum(n, l) {
return ("0".repeat(l)+n).substr(-l);
}
let rects = {};
let rectsToClear = {};
let commands = [];
function pushCommand(command) {
let hash = E.CRC32(E.toJS(arguments));
if (!delete rectsToClear[hash]) {
commands.push({hash: hash, command: Function.apply.bind(command, null, arguments.slice(1))});
}
}
function executeCommands() {
"ram";
for (let hash in rectsToClear) delete rects[hash];
for (let r of rectsToClear) if (r) g.clearRect(r.x1, r.y1, r.x2, r.y2);
g.getModified(true);
for (let c of commands) {
c.command();
rects[c.hash] = g.getModified(true);
}
rectsToClear = Object.assign({}, rects);
commands = [];
}
function drawVectorText(text, size, x, y, alignX, alignY) {
g.setFont("Vector", size).setFontAlign(alignX, alignY).drawString(text, x, y);
}
function draw() {
g.reset();
let d = new Date();
let hours = is12Hour ? ((d.getHours() + 11) % 12) + 1 : d.getHours();
let timeText = `${hours}:${padNum(d.getMinutes(), 2)}`;
let meridian = is12Hour ? ((d.getHours() < 12) ? "AM" : "PM") : "";
let secondsText = padNum(d.getSeconds(), 2);
let dowText = locale.dow(d);
let dateText = locale.date(d, true);
g.setFont("Vector", 256);
let timeFontSize = g.getWidth() / ((g.stringWidth(timeText) / 256) + (Math.max(g.stringWidth(meridian), g.stringWidth(secondsText)) / 512 * 9 / 10));
let dowFontSize = g.getWidth() / (g.stringWidth(dowText) / 256);
let dateFontSize = g.getWidth() / (g.stringWidth(dateText) / 256);
let timeHeight = g.setFont("Vector", timeFontSize).getFontHeight() * 9 / 10;
let dowHeight = g.setFont("Vector", dowFontSize).getFontHeight();
let dateHeight = g.setFont("Vector", dateFontSize).getFontHeight();
let remainingHeight = g.getHeight() - 24 - timeHeight - dowHeight - dateHeight;
let spacer = remainingHeight / 4;
let y = 24 + spacer;
pushCommand(drawVectorText, timeText, timeFontSize, 0, y, -1, -1);
pushCommand(drawVectorText, meridian, timeFontSize*9/20, g.getWidth(), y, 1, -1);
pushCommand(drawVectorText, secondsText, timeFontSize*9/20, g.getWidth(), y + timeHeight, 1, 1);
y += timeHeight + spacer;
pushCommand(drawVectorText, dowText, dowFontSize, g.getWidth()/2, y, 0, -1);
y += dowHeight + spacer;
pushCommand(drawVectorText, dateText, dateFontSize, g.getWidth()/2, y, 0, -1);
executeCommands();
}
let timeout;
function tick() {
draw();
timeout = setTimeout(tick, 1000 - getTime() % 1 * 1000);
}
Bangle.on('lcdPower', function(on) {
if (timeout) clearTimeout(timeout);
timeout = null;
if (on) tick();
});
g.clear();
tick();
Bangle.loadWidgets();
Bangle.drawWidgets();
// Show launcher when middle button pressed
Bangle.setUI("clock");

BIN
apps/vectorclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -2,3 +2,4 @@
0.03: Fix flickering last updated time.
0.04: Adjust "weather unknown" message according to Bluetooth connection.
0.05: Add wind direction.
0.06: Use setUI for launcher.

View File

@ -88,7 +88,7 @@
update();
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: 'falling'});
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -0,0 +1,4 @@
0.01: Fork of widclk v0.04 github.com/espruino/BangleApps/tree/master/apps/widclk
0.02: Modification for bottom widget area and text color
0.03: based in widclk v0.05 compatible at same time, bottom area and color

28
apps/widclkbttm/README.md Normal file
View File

@ -0,0 +1,28 @@
# Digital clock widget (bottom widget area)
This very basic widget clock allows to test the unfrequently used widget bottom area.
forked from
https://github.com/espruino/BangleApps/tree/master/apps/widclk
## Photo
Example of usage
![](widTextBottom_ss1.jpg)
## Usage
Upload the widget file
Open an app that supports displaying widgets
## Support
This app is so basic that probably the easiest is to just edit the code ;)
Otherwise you can contact me [here](https://github.com/dapgo)

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

View File

@ -0,0 +1,31 @@
(function() {
// don't show widget if we know we have a clock app running
if (Bangle.CLOCK) return;
let intervalRef = null;
var width = 5 * 6*2;
var text_color=0x07FF;//cyan
function draw() {
g.reset().setFont("6x8", 2).setFontAlign(-1, 0).setColor(text_color);
var time = require("locale").time(new Date(),1);
g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60
}
function clearTimers(){
if(intervalRef) {
clearInterval(intervalRef);
intervalRef = null;
}
}
function startTimers(){
intervalRef = setInterval(()=>WIDGETS["wdclkbttm"].draw(), 60*1000);
WIDGETS["wdclkbttm"].draw();
}
Bangle.on('lcdPower', (on) => {
clearTimers();
if (on) startTimers();
});
WIDGETS["wdclkbttm"]={area:"br",width:width,draw:draw};
if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclkbttm"].draw(), 60*1000);
})()