mirror of https://github.com/espruino/BangleApps
merge with upstream
commit
c05083a66a
|
@ -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`
|
||||
|
|
95
apps.json
95
apps.json
|
@ -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",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
|
|
@ -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});
|
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
|
@ -0,0 +1 @@
|
|||
0.1: Added source code
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwIjgg/gAp0IgfAiAFBjkP+E4AoM8n/8ngFBvn//8+AoP//Ef/4FBv/Agf+AoMPwEB+AFCjEYAoUenk8vAvCAoIvCnAFBjgFCC4IFCCgUeEQNwAoMO+EPuPD4eOAoPz8fH54FH+IRBx4FBDogpFGoxBFJopZFMopxFPoqJFSoqhFVoq5FgAFBa6gAW"))
|
|
@ -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);
|
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"interval":-1,"start":9,"end":21,"vlevel":0.5,"next_hour":-1,"next_minute":-1}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"BTN1": "",
|
||||
"BTN3": ""
|
||||
"BTN3": "",
|
||||
"right_hand": false
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -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>
|
Binary file not shown.
After Width: | Height: | Size: 767 B |
|
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
0.1: New watch face
|
||||
0.2: Use Bangle.setUI for button/launcher handling
|
|
@ -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="))
|
|
@ -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");
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -1,4 +1,5 @@
|
|||
0.02: Make minor adjustments to widget, and discard stale weather data after a configurable period.
|
||||
0.03: Fix flickering last updated time.
|
||||
0.04: Adjust "weather unknown" message according to Bluetooth connection.
|
||||
0.05: Add wind direction.
|
||||
0.05: Add wind direction.
|
||||
0.06: Use setUI for launcher.
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
## 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 |
|
@ -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);
|
||||
})()
|
Loading…
Reference in New Issue