Merge remote-tracking branch 'upstream/master'
|
@ -1,3 +1,9 @@
|
|||
0.01: New App!
|
||||
0.02: Support Bangle.js 2
|
||||
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
|
||||
0.04: Combine code for both apps
|
||||
Better colors for Bangle.js 2
|
||||
Fix selection animation for Bangle.js 2
|
||||
New icon
|
||||
Slightly wider arc segments for better visibility
|
||||
Extract arc drawing code in library
|
||||
|
|
|
@ -11,16 +11,21 @@ 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,
|
||||
You can use Choozi to randomly select an element from any set with 2 to 15 members,
|
||||
as long as you can define a bijection between members of the set and coloured
|
||||
segments on the Bangle.js display.
|
||||
|
||||
## Controls
|
||||
## Controls Bangle 1
|
||||
|
||||
BTN1: increase the number of segments
|
||||
BTN2: choose a segment at random
|
||||
BTN3: decrease the number of segments
|
||||
|
||||
## Controls Bangle 2
|
||||
|
||||
Swipe up/down: increase/decrease the number of segments
|
||||
BTN1 or tap: choose a segment at random
|
||||
|
||||
## Creator
|
||||
|
||||
James Stanley
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
|
||||
require("heatshrink").decompress(atob("mEwwcH/4AW/u27dt2wQL/YOBCIXbv4QI+AODAQVsh4RHwEbCI0LCI9gCIOANAXbsFbG437tkDPg1btoRFFoILBgmSpMggECHQO/CAf2CIVJkgRBAQIjC24RFsECCItIgIRFMYMAiQRFpMAlqmDVwPYgAOEAQUggu274RD4BWCCIskCIPbCIPt20ABwwCCwARFgIRJyEWCIVt2EJCJi2BCJmSUgIRCwARNt/7CIIOICI1sWAwCFoFbCOtt8EACJsAgARR8hwBCJlJk4RlgARQAgIRKDwMn/gRBdJgRPyARBn4RBpARLiQRB/4RBgIRJwAREpIRLAYP///ypMgCJMACI0ECI4JCp4RB/wZECIsAAYN/CIP/5JPDCIhjDCIraHTIWTCAX//K7DCI+fCIf/EZA1CCAn//ipCLIsBk4RF/5ZHCIIQG//wPo8vCI//6QRFpYQIAAPpCIeXCBQAC/VfBI4="))
|
|
@ -4,15 +4,16 @@
|
|||
*
|
||||
* James Stanley 2021
|
||||
*/
|
||||
|
||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
||||
const GU = require("graphics_utils");
|
||||
var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff'];
|
||||
var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0'];
|
||||
|
||||
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 perimMin = g.getWidth()*0.40; // px - min. radius of perimeter
|
||||
var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter
|
||||
|
||||
var segmentMax = 106; // px - max radius of filled-in segment
|
||||
var segmentMax = g.getWidth()*0.38; // 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
|
||||
|
||||
|
@ -22,10 +23,10 @@ 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 ballTrack = perimMin - ballSize*2; // px - radius of ball path
|
||||
|
||||
var centreX = 120; // px - centre of screen
|
||||
var centreY = 120; // px - centre of screen
|
||||
var centreX = g.getWidth()*0.5; // px - centre of screen
|
||||
var centreY = g.getWidth()*0.5; // px - centre of screen
|
||||
|
||||
var fontSize = 50; // px
|
||||
|
||||
|
@ -33,7 +34,6 @@ 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;
|
||||
|
||||
|
@ -51,42 +51,14 @@ function shuffle (array) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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.setBgColor('#000000');
|
||||
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);
|
||||
GU.fillArc(g, centreX, centreY, perimMin,perimMax,minAngle,minAngle+arclen, stepAngle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,6 +103,7 @@ function animateChoice(target) {
|
|||
g.fillCircle(x, y, ballSize);
|
||||
oldx=x;
|
||||
oldy=y;
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,11 +114,15 @@ function choose() {
|
|||
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)
|
||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){
|
||||
GU.fillArc(g, centreX, centreY, i, perimMax, minAngle, maxAngle, stepAngle);
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
GU.fillArc(g, centreX, centreY, 0, perimMax, minAngle, maxAngle, stepAngle);
|
||||
for (var r = 1; r < segmentMax; r += circleStep){
|
||||
g.fillCircle(centreX,centreY,r);
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
g.fillCircle(centreX,centreY,segmentMax);
|
||||
}
|
||||
|
||||
|
@ -171,38 +148,47 @@ function setN(n) {
|
|||
drawPerimeter();
|
||||
}
|
||||
|
||||
// save N to choozi.txt
|
||||
// save N to choozi.save
|
||||
function writeN() {
|
||||
var file = require("Storage").open("choozi.txt","w");
|
||||
file.write(N);
|
||||
var savedN = read();
|
||||
if (savedN != N) require("Storage").write("choozi.save","" + N);
|
||||
}
|
||||
|
||||
// load N from choozi.txt
|
||||
function read(){
|
||||
var n = require("Storage").read("choozi.save");
|
||||
if (n !== undefined) return parseInt(n);
|
||||
return defaultN;
|
||||
}
|
||||
|
||||
// load N from choozi.save
|
||||
function readN() {
|
||||
var file = require("Storage").open("choozi.txt","r");
|
||||
var n = file.readLine();
|
||||
if (n !== undefined) setN(parseInt(n));
|
||||
else setN(defaultN);
|
||||
setN(read());
|
||||
}
|
||||
|
||||
shuffle(colours); // is this really best?
|
||||
Bangle.setLCDMode("direct");
|
||||
Bangle.setLCDTimeout(0); // keep screen on
|
||||
if (process.env.HWVERSION == 1){
|
||||
colours=colours.concat(colours2);
|
||||
shuffle(colours);
|
||||
} else {
|
||||
shuffle(colours);
|
||||
shuffle(colours2);
|
||||
colours=colours.concat(colours2);
|
||||
}
|
||||
|
||||
var maxN = colours.length;
|
||||
if (process.env.HWVERSION == 1){
|
||||
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});
|
||||
Bangle.setUI("updown", (v)=>{
|
||||
if (!v){
|
||||
writeN();
|
||||
drawPerimeter();
|
||||
choose();
|
||||
} else {
|
||||
setN(N-v);
|
||||
drawN();
|
||||
}
|
||||
});
|
||||
|
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 551 B |
|
@ -1,207 +0,0 @@
|
|||
/* 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 = 80; // px - min. radius of perimeter
|
||||
var perimMax = 87; // px - max. radius of perimeter
|
||||
|
||||
var segmentMax = 70; // 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 = 75; // px - radius of ball path
|
||||
|
||||
var centreX = 88; // px - centre of screen
|
||||
var centreY = 88; // 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;
|
||||
g.flip();
|
||||
}
|
||||
}
|
||||
|
||||
// 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(g.theme.fg);
|
||||
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.setLCDTimeout(0); // keep screen on
|
||||
readN();
|
||||
drawN();
|
||||
|
||||
setWatch(() => {
|
||||
writeN();
|
||||
drawPerimeter();
|
||||
choose();
|
||||
}, BTN1, {repeat:true});
|
||||
|
||||
Bangle.on('touch', function(zone,e) {
|
||||
if(e.x>+88){
|
||||
setN(N-1);
|
||||
drawN();
|
||||
}else{
|
||||
setN(N+1);
|
||||
drawN();
|
||||
}
|
||||
});
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "choozi",
|
||||
"name": "Choozi",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Choose people or things at random using Bangle.js.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
|
@ -10,8 +10,10 @@
|
|||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
|
||||
"storage": [
|
||||
{"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]},
|
||||
{"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]},
|
||||
{"name":"choozi.app.js","url":"app.js"},
|
||||
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"choozi.save"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -15,4 +15,5 @@
|
|||
0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
0.30: BJS2: swipe seems to be working now
|
||||
0.31: Tweaking the swipe option; Added mylocation as a dependency.
|
||||
Remove calls to Bangle.loadWidgets as they are not needed and create warnings
|
||||
Remove calls to Bangle.loadWidgets as they are not needed and create warnings
|
||||
0.32: Added setting to show single timezone small, like where multiple ones are configured
|
|
@ -9,6 +9,7 @@ All this is configurable:
|
|||
|
||||
- Show seconds only when unlocked (saves battery) / always / do not show seconds
|
||||
- Green color on dark mode (on/off)
|
||||
- 1 Offset Small: single location shows as small (like more than 1)
|
||||
- Show sun info (on/off) (set your location in the mylocation app)
|
||||
- Rotation degree on swipe (off / 90 / 180 / 270)
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ let def = function(value, def) {
|
|||
let settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||
secondsMode = def(settings.secondsMode, "when unlocked");
|
||||
showSunInfo = def(settings.showSunInfo, true);
|
||||
singleOffsetSmall = def(settings.singleOffsetSmall, false);
|
||||
colorWhenDark = def(settings.colorWhenDark, "green");
|
||||
rotationTarget = def(settings.rotationTarget, "90");
|
||||
rotationTarget = parseInt(rotationTarget) || 0;
|
||||
|
@ -257,7 +258,7 @@ let draw = function() {
|
|||
minutes = doublenum(dx.getMinutes());
|
||||
|
||||
|
||||
if (offsets.length === 1) {
|
||||
if (offsets.length === 1 && !singleOffsetSmall) {
|
||||
let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
|
||||
// For a single secondary timezone, draw it bigger and drop time zone to second line
|
||||
const xOffset = 30;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "hworldclock",
|
||||
"name": "Hanks World Clock",
|
||||
"shortName": "Hanks World Clock",
|
||||
"version": "0.31",
|
||||
"version": "0.32",
|
||||
"description": "Current time zone plus up to three others",
|
||||
"allow_emulator":true,
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -36,13 +36,20 @@
|
|||
"< Back": () => back(),
|
||||
"Seconds": stringInSettings("secondsMode", ["always", "when unlocked", "none"]),
|
||||
"Color w. dark": stringInSettings("colorWhenDark", ["green", "default"]),
|
||||
"Show SunInfo": {
|
||||
value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true),
|
||||
onchange: v => {
|
||||
settings.showSunInfo = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"1 Offset Small": {
|
||||
value: (settings.singleOffsetSmall !== undefined ? settings.singleOffsetSmall : false),
|
||||
onchange: v=> {
|
||||
settings.singleOffsetSmall = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Show SunInfo": {
|
||||
value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true),
|
||||
onchange: v => {
|
||||
settings.showSunInfo = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
"Rotation": stringInSettings("rotationTarget", ["off", "90", "180", "270"]),
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Create mitherm app with support for pvvx firmware only
|
|
@ -0,0 +1,22 @@
|
|||
Reads BLE advertisement data from Xiaomi temperature/humidity sensors running the
|
||||
`pvvx` custom firmware (https://github.com/pvvx/ATC_MiThermometer).
|
||||
|
||||
## Features
|
||||
|
||||
* Display temperature
|
||||
* Display humidity
|
||||
* Display battery state of sensor
|
||||
* Auto-refresh every 5 minutes
|
||||
* Manual refresh on demand
|
||||
* Add aliases for MAC addresses to easily recognise devices
|
||||
|
||||
## Planned features
|
||||
|
||||
* Supprt for other advertising formats:
|
||||
* atc1441 format
|
||||
* BTHome
|
||||
* Xiaomi Mijia format
|
||||
* Configurable auto-refresh interval
|
||||
* Configurable scan length (currently 30s)
|
||||
* Alerts when temperature outside defined limits (with a widget or bootcode to
|
||||
work when app is inactive)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/AH4Ac5gWVhnM4AWVAAIYTCwQABCywYRIoYADJJwWHDB4RD5sz7hJPFIlP//0MRxFE6f/AAM9JJgWE4gWCAANMDBZcEn4XE+ZiKFwhcBCYPdDYRiEGAoXDLgf97vfMQwXILggXFMQYXHLgoXB6czMQoXHLgQXJMQQXG4YWEI44ABngXGh4XHF4v/+DAGC6DXGC5BHGC509F4IXTdwIABV4gXOIwIABJAoX/C6p3Xa4a/UABAXfgczABswC/4XmAH4A/ABY"))
|
|
@ -0,0 +1,172 @@
|
|||
var filterTemperature = [{
|
||||
serviceData: {
|
||||
"181a": {}
|
||||
}
|
||||
}];
|
||||
var results = {};
|
||||
var macs = [];
|
||||
|
||||
var aliases = require("Storage").readJSON("mitherm.json", true);
|
||||
if (!aliases) aliases = {};
|
||||
|
||||
var lastSeen = {};
|
||||
var current = 0;
|
||||
var scanning = false;
|
||||
var timeoutDraw;
|
||||
var timeoutScan;
|
||||
|
||||
|
||||
const scan = function() {
|
||||
if (!scanning) { // Don't start scanning if already doing so.
|
||||
scanning = true;
|
||||
if (timeoutScan) clearTimeout(timeoutScan);
|
||||
timeoutScan = setTimeout(scan, 300000); // Scan again in 5 minutes.
|
||||
drawScanState(scanning);
|
||||
NRF.findDevices(function(devices) {
|
||||
onDevices(devices);
|
||||
}, {
|
||||
filters: filterTemperature,
|
||||
timeout: 30000 // Scan for 30s
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const onDevices = function(devices) {
|
||||
let now = Date.now();
|
||||
for (let i = 0; i < devices.length; i++) {
|
||||
let device = devices[i];
|
||||
|
||||
let processedData = extractData(device.data);
|
||||
console.log({
|
||||
rssi: device.rssi,
|
||||
data: processedData
|
||||
});
|
||||
if (!macs.includes(processedData.MAC)) {
|
||||
macs.push(processedData.MAC);
|
||||
}
|
||||
results[processedData.MAC] = processedData;
|
||||
lastSeen[processedData.MAC] = now;
|
||||
}
|
||||
console.log("Scan complete.");
|
||||
scanning = false;
|
||||
writeOutput();
|
||||
};
|
||||
|
||||
|
||||
const extractData = function(thedata) {
|
||||
let data = DataView(thedata);
|
||||
let MAC = [];
|
||||
for (let i = 9; i > 3; i--) {
|
||||
MAC.push(data.getUint8(i, true).toString(16).padStart(2, "0"));
|
||||
}
|
||||
out = {
|
||||
size: data.getUint8(0, true),
|
||||
uid: data.getUint8(1, true),
|
||||
UUID: data.getUint16(2, true),
|
||||
MAC: MAC.join(":"),
|
||||
temperature: data.getInt16(10, true) * 0.01,
|
||||
humidity: data.getUint16(12, true) * 0.01,
|
||||
battery_mv: data.getUint16(14, true),
|
||||
battery_level: data.getUint8(16, true),
|
||||
};
|
||||
return out;
|
||||
};
|
||||
|
||||
|
||||
const writeOutput = function() {
|
||||
let now = Date.now();
|
||||
if (timeoutDraw) clearTimeout(timeoutDraw);
|
||||
timeoutDraw = setTimeout(writeOutput, 60000); // Refresh in 1 minute.
|
||||
g.clear(true);
|
||||
Bangle.drawWidgets();
|
||||
g.reset();
|
||||
drawScanState(scanning);
|
||||
|
||||
if (macs.length == 0) return;
|
||||
|
||||
processedData = results[macs[current]];
|
||||
g.setFont12x20(2);
|
||||
g.drawString(`${processedData.temperature.toFixed(2)}°C`, 10, 30);
|
||||
g.drawString(`${processedData.humidity.toFixed(2)} %`, 10, 70);
|
||||
|
||||
g.setFont6x15();
|
||||
g.drawString(`${((now - lastSeen[macs[current]]) / 60000).toFixed(0)} min ago`, 10, 130);
|
||||
g.drawString(`${processedData.battery_level} % battery`, 80, 130);
|
||||
g.drawString(` ${processedData.MAC in aliases ? aliases[processedData.MAC] : processedData.MAC}: ${current + 1} / ${macs.length}`, 10, 150);
|
||||
};
|
||||
|
||||
|
||||
const scrollDevices = function(directionLR) {
|
||||
// Swipe left or right to move between devices.
|
||||
current -= directionLR; // inverted feels a more familiar gesture.
|
||||
if (current + 1 > macs.length)
|
||||
current = 0;
|
||||
if (current < 0)
|
||||
current = macs.length - 1;
|
||||
writeOutput();
|
||||
};
|
||||
|
||||
const drawScanState = function(state) {
|
||||
if (state)
|
||||
g.fillRect(160, 160, 170, 170);
|
||||
else
|
||||
g.clearRect(160, 160, 170, 170);
|
||||
};
|
||||
|
||||
const setAlias = function(mac, alias) {
|
||||
if (alias === "") {
|
||||
delete aliases[mac];
|
||||
}
|
||||
else {
|
||||
aliases[mac] = alias;
|
||||
require("Storage").writeJSON("mitherm.json", aliases);
|
||||
}
|
||||
};
|
||||
|
||||
const changeAlias = function(mac) {
|
||||
g.clear();
|
||||
require("textinput").input((mac in aliases) ? aliases[mac] : "").then(function(text) {
|
||||
setAlias(mac, text);
|
||||
setUI();
|
||||
writeOutput();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const setUI = function() {
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
swipe: scrollDevices,
|
||||
btn: function() {
|
||||
E.showMenu(actionsMenu);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const actionsMenu = {
|
||||
"": {
|
||||
"title": "-- Actions --",
|
||||
"back": function() {
|
||||
E.showMenu();
|
||||
},
|
||||
"remove": function() {
|
||||
setUI();
|
||||
writeOutput();
|
||||
},
|
||||
},
|
||||
"Scan now": function() {
|
||||
scan();
|
||||
E.showMenu();
|
||||
},
|
||||
"Edit alias": function() {
|
||||
changeAlias(macs[current]);
|
||||
},
|
||||
};
|
||||
|
||||
setUI();
|
||||
Bangle.loadWidgets();
|
||||
g.setClipRect(Bangle.appRect);
|
||||
scan();
|
||||
writeOutput();
|
After Width: | Height: | Size: 863 B |
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "mitherm",
|
||||
"name": "Xiaomi Mijia Temperature and Humidity display",
|
||||
"shortName": "MiTherm",
|
||||
"version": "0.01",
|
||||
"description": "Reads and displays data from Xiaomi temperature/humidity sensors running custom firmware",
|
||||
"icon": "app.png",
|
||||
"tags": "xiaomi,mi,ble,bluetooth,thermometer,humidity",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"mitherm.app.js","url":"app.js"},
|
||||
{"name":"mitherm.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Reset font to save some memory during remove
|
||||
0.03: Added support for locale based time
|
||||
0.04: Stability improvements
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
{ // 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
|
||||
|
||||
const fontBitmap = E.toString(require('heatshrink').decompress(atob('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')));
|
||||
|
||||
Graphics.prototype.setFontPaytoneOne = function(scale) {
|
||||
// Actual height 81 (91 - 11)
|
||||
this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('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'))),
|
||||
fontBitmap,
|
||||
46,
|
||||
atob("ITZOMzs7SDxHNUdGIQ=="),
|
||||
113+(scale<<8)+(1<<16)
|
||||
|
@ -9,8 +14,6 @@ Graphics.prototype.setFontPaytoneOne = function(scale) {
|
|||
return this;
|
||||
};
|
||||
|
||||
{ // 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 g2 = Graphics.createArrayBuffer(g.getWidth(),90,1,{msb:true});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{ "id": "slopeclock",
|
||||
"name": "Slope Clock",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"slopeclock.app.js","url":"app.js"},
|
||||
{"name":"slopeclock.img","url":"app-icon.js","evaluate":true}
|
||||
|
|
|
@ -7,3 +7,5 @@
|
|||
0.05: Images in clkinfo are optional now
|
||||
0.06: Added support for locale based time
|
||||
0.07: README file update as UI interaction was not easy to understand
|
||||
0.08: Stability improvements - ensure we continue even if a flat string can't be allocated
|
||||
Stop ClockInfo text drawing outside the allocated area
|
||||
|
|
|
@ -1,14 +1,3 @@
|
|||
Graphics.prototype.setFontPaytoneOne = function(scale) {
|
||||
// Actual height 71 (81 - 11)
|
||||
this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('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'))),
|
||||
46,
|
||||
atob("HTBFLTQ0PzU/Lz8+HQ=="),
|
||||
100+(scale<<8)+(1<<16)
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
{ // 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
|
||||
|
||||
|
@ -16,6 +5,17 @@ let settings = Object.assign(
|
|||
require("Storage").readJSON("slopeclockpp.default.json", true) || {},
|
||||
require("Storage").readJSON("slopeclockpp.json", true) || {}
|
||||
);
|
||||
const fontBitmap = E.toString(require('heatshrink').decompress(atob('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')));
|
||||
|
||||
Graphics.prototype.setFontPaytoneOne = function(scale) {
|
||||
// Actual height 71 (81 - 11)
|
||||
this.setFontCustom(fontBitmap,
|
||||
46,
|
||||
atob("HTBFLTQ0PzU/Lz8+HQ=="),
|
||||
100+(scale<<8)+(1<<16)
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
let drawTimeout;
|
||||
|
||||
|
@ -47,6 +47,15 @@ let bgColor = bgColors[(Math.random()*bgColors.length)|0]||"#000";
|
|||
|
||||
// Draw the hour, and the minute into an offscreen buffer
|
||||
let draw = function() {
|
||||
// queue next draw
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
animate(false, function() {
|
||||
draw();
|
||||
});
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
// Now draw this one
|
||||
R = Bangle.appRect;
|
||||
x = R.w / 2;
|
||||
y = R.y + R.h / 2 - 12; // 12 = room for date
|
||||
|
@ -70,15 +79,6 @@ let draw = function() {
|
|||
g2.setColor(0).fillPoly([0,0, g2.getWidth(),0, 0,slope*2]);
|
||||
// start the animation *in*
|
||||
animate(true);
|
||||
|
||||
// queue next draw
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
animate(false, function() {
|
||||
draw();
|
||||
});
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
};
|
||||
|
||||
let isAnimIn = true;
|
||||
|
@ -123,7 +123,9 @@ let animate = function(isIn, callback) {
|
|||
// clock info menus (scroll up/down for info)
|
||||
let clockInfoDraw = (itm, info, options) => {
|
||||
let texty = options.y+41;
|
||||
g.reset().setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty-15, options.x+options.w-2, texty);
|
||||
// set a cliprect to stop us drawing outside our box
|
||||
g.reset().setClipRect(options.x, options.y, options.x+options.w-1, options.y+options.h-1);
|
||||
g.setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty-15, options.x+options.w-2, texty);
|
||||
|
||||
if (options.focus) g.setColor(options.hl);
|
||||
if (options.x < g.getWidth()/2) { // left align
|
||||
|
@ -135,6 +137,8 @@ let clockInfoDraw = (itm, info, options) => {
|
|||
if (info.img) g.clearRect(x-23, options.y, x, options.y+23).drawImage(info.img, x-23, options.y);
|
||||
g.setFontAlign(1,1).drawString(info.text, x,texty);
|
||||
}
|
||||
// return ClipRect
|
||||
g.setClipRect(0,0,g.getWidth()-1, g.getHeight()-1);
|
||||
};
|
||||
let clockInfoItems = require("clock_info").load();
|
||||
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:126, y:24, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#f00"/*red*/ });
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "slopeclockpp",
|
||||
"name": "Slope Clock ++",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows extra information and allows the colors to be selected.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Set sortorder to -10 so that others can take -1 etc
|
||||
0.06: Set sortorder to -10 in widget code
|
||||
0.07: Remove check for .isLocked (extremely old firmwares), speed up widget loading
|
||||
0.08: Don't completely remove the lock widget when screen unlocked (use 1px) to ensure appRect/drawWidgets still thinks there are widgets
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "widlock",
|
||||
"name": "Lock Widget",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
Bangle.on("lock", function() {
|
||||
WIDGETS["lock"].width = Bangle.isLocked()?16:0;
|
||||
WIDGETS["lock"].width = Bangle.isLocked()?16:1;
|
||||
Bangle.drawWidgets();
|
||||
});
|
||||
WIDGETS["lock"]={area:"tl",sortorder:10,width:Bangle.isLocked()?16:0,draw:function(w) {
|
||||
WIDGETS["lock"]={area:"tl",sortorder:10,width:Bangle.isLocked()?16:1,draw:function(w) {
|
||||
if (Bangle.isLocked())
|
||||
g.reset().drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), w.x+1, w.y+4);
|
||||
}};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
0.01: Moved messages widget into standalone widget app
|
||||
0.02: Fix 'srcs' being defined in global scope
|
||||
Remove library stub
|
||||
0.03: Fix messages not showing if UI auto-open is disabled
|
||||
0.03: Fix messages not showing if UI auto-open is disabled
|
||||
0.04: Now shows message icons again (#2416)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "widmessages",
|
||||
"name": "Message Widget",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Widget showing new messages",
|
||||
"icon": "app.png",
|
||||
"type": "widget",
|
||||
|
|
|
@ -22,10 +22,9 @@
|
|||
let settings = Object.assign({flash: true, maxMessages: 3}, require("Storage").readJSON("messages.settings.json", true) || {});
|
||||
if (recall!==true || settings.flash) {
|
||||
const msgsShown = E.clip(this.srcs.length, 0, settings.maxMessages);
|
||||
srcs = Object.keys(this.srcs);
|
||||
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23);
|
||||
for(let i = 0; i<msgsShown; i++) {
|
||||
const src = srcs[i];
|
||||
const src = this.srcs[i];
|
||||
const colors = [
|
||||
g.theme.bg,
|
||||
require("messageicons").getColor(src, {settings: settings})
|
||||
|
|
|
@ -49,7 +49,7 @@ function onFoundDeviceInfo(deviceId, deviceVersion) {
|
|||
if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") {
|
||||
showToast(`You're using ${deviceId}, not a Bangle.js. Did you want <a href="https://espruino.com/apps">espruino.com/apps</a> instead?` ,"warning", 20000);
|
||||
} else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) {
|
||||
showToast(`You're using an old Bangle.js firmware (${deviceVersion}) and ${RECOMMENDED_VERSION} is available (<a href="http://www.espruino.com/ChangeLog" target="_blank">see changes</a>). You can update ${fwExtraText}<a href="${fwURL}" target="_blank">with the instructions here</a>` ,"warning", 20000);
|
||||
showToast(`You're using an old Bangle.js firmware (${deviceVersion}) and ${RECOMMENDED_VERSION} is available (<a href="https://www.espruino.com/ChangeLog" target="_blank">see changes</a>). You can update ${fwExtraText}<a href="${fwURL}" target="_blank">with the instructions here</a>` ,"warning", 20000);
|
||||
}
|
||||
// check against features shown?
|
||||
filterAppsForDevice(deviceId);
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// draw an arc between radii minR and maxR, and between angles minAngle and maxAngle centered at X,Y. All angles are radians.
|
||||
exports.fillArc = function(graphics, X, Y, minR, maxR, minAngle, maxAngle, stepAngle) {
|
||||
var step = stepAngle || 0.2;
|
||||
var angle = minAngle;
|
||||
var inside = [];
|
||||
var outside = [];
|
||||
var c, s;
|
||||
while (angle < maxAngle) {
|
||||
c = Math.cos(angle);
|
||||
s = Math.sin(angle);
|
||||
inside.push(X+c*minR); // x
|
||||
inside.push(Y+s*minR); // y
|
||||
// outside coordinates are built up in reverse order
|
||||
outside.unshift(Y+s*maxR); // y
|
||||
outside.unshift(X+c*maxR); // x
|
||||
angle += step;
|
||||
}
|
||||
c = Math.cos(maxAngle);
|
||||
s = Math.sin(maxAngle);
|
||||
inside.push(X+c*minR);
|
||||
inside.push(Y+s*minR);
|
||||
outside.unshift(Y+s*maxR);
|
||||
outside.unshift(X+c*maxR);
|
||||
|
||||
var vertices = inside.concat(outside);
|
||||
graphics.fillPoly(vertices, true);
|
||||
}
|
||||
|
||||
exports.degreesToRadians = function(degrees){
|
||||
return Math.PI/180 * degrees;
|
||||
}
|
||||
|
||||
exports.radiansToDegrees = function(radians){
|
||||
return 180/Math.PI * degrees;
|
||||
}
|
|
@ -53,8 +53,14 @@ exports.cleanup = function() {
|
|||
back onscreen with a downwards swipe. Use .show to undo.
|
||||
First parameter controls automatic hiding time, 0 equals not hiding at all.
|
||||
Default value is 2000ms until hiding.
|
||||
Bangle.js 2 only at the moment. */
|
||||
Bangle.js 2 only at the moment. On Bangle.js 1 widgets will be hidden permanently.
|
||||
|
||||
Note: On Bangle.js 1 is is possible to draw widgets in an offscreen area of the LCD
|
||||
and use Bangle.setLCDOffset. However we can't detect a downward swipe so how to
|
||||
actually make this work needs some thought.
|
||||
*/
|
||||
exports.swipeOn = function(autohide) {
|
||||
if (process.env.HWVERSION!==2) return exports.hide();
|
||||
exports.cleanup();
|
||||
if (!global.WIDGETS) return;
|
||||
exports.autohide=autohide===undefined?2000:autohide;
|
||||
|
|