Merge branch 'espruino:master' into Smart-Battery
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New app! (settings, boot.js).
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# BackLite
|
||||||
|
### This app needs the latest settings app update (v 0.80), to ensure that setting the brightness to `0` does not default to `1`.
|
||||||
|
|
||||||
|
BackLite is an app which greatly conserves battery life by only turning the backlight on when you long press the button from a locked state.
|
||||||
|
|
||||||
|
Modern watches have a dedicated button to turn the backlight on, so as not to waste battery in an already light environment. This app recreates that functionality for the Bangle.js, which only has one button.
|
||||||
|
|
||||||
|
#### Warning: This app overwrites the LCD brightness setting in `Bangle.js LCD settings`. If it is changed, the app will basically lose functionality. It auto-fixes itself every boot, so if you change the brightness, just reboot :)
|
||||||
|
# Usage
|
||||||
|
When you unlock with a press of the button, or any other way you unlock the watch, the backlight will not turn on, as most of the time you are able to read it, due to the transreflective display on the Bangle.js 2.
|
||||||
|
|
||||||
|
If you press and hold the button to unlock the watch (for around half a second), the backlight will turn on for 5 seconds - just enough to see what you need to see. After that, it will turn off again.
|
||||||
|
|
||||||
|
Some apps like `Light Switch Widget` will prevent this app from working properly.
|
||||||
|
# Settings
|
||||||
|
`Brightness` - The LCD brightness when unlocked with a long press.
|
||||||
|
# Creator
|
||||||
|
RKBoss6
|
||||||
|
|
||||||
|
TODO: Add a setting for long press time, or light duration
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
let getSettings = function(){
|
||||||
|
return Object.assign({
|
||||||
|
// default values
|
||||||
|
brightness: 0.3,
|
||||||
|
|
||||||
|
}, require('Storage').readJSON("BackLite.settings.json", true) || {});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//Set LCD to zero every reboot
|
||||||
|
let s = require("Storage").readJSON("setting.json", 1) || {};
|
||||||
|
s.brightness = 0;
|
||||||
|
if (!("lcdTimeout" in s)) s.lcdTimeout = 5; // fallback so logic doesn't break
|
||||||
|
require("Storage").writeJSON("setting.json", s);
|
||||||
|
|
||||||
|
const longPressTime=400; //(ms)
|
||||||
|
|
||||||
|
Bangle.on('lock', function(isLocked) {
|
||||||
|
Bangle.setLCDBrightness(0);
|
||||||
|
|
||||||
|
if (!isLocked) {
|
||||||
|
// Just unlocked — give a short delay and check if BTN1 is still pressed
|
||||||
|
setTimeout(() => {
|
||||||
|
if (digitalRead(BTN1)) {
|
||||||
|
//set brightness until. locked.
|
||||||
|
Bangle.setLCDBrightness(getSettings().brightness);
|
||||||
|
} else {
|
||||||
|
Bangle.setLCDBrightness(0);
|
||||||
|
}
|
||||||
|
}, longPressTime); // Slight delay to allow unlock to settle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 1.4 MiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "backlite",
|
||||||
|
"name": "BackLite",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Conserves battery life by turning the backlight on only on a long press of the button from a locked state. **Requires the latest settings update (v0.80)**",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"type": "bootloader",
|
||||||
|
"tags": "system",
|
||||||
|
"readme": "README.md",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"backlite.boot.js","url":"boot.js"},
|
||||||
|
{"name":"backlite.settings.js","url":"settings.js"}
|
||||||
|
|
||||||
|
],
|
||||||
|
"data": [{"name":"BackLite.settings.json"}]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
(function(back) {
|
||||||
|
var FILE = "BackLite.settings.json";
|
||||||
|
// Load settings
|
||||||
|
var settings = Object.assign({
|
||||||
|
brightness: 0.3,
|
||||||
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the menu
|
||||||
|
E.showMenu({
|
||||||
|
"" : { "title" : "BackLite" },
|
||||||
|
'Brightness': {
|
||||||
|
value: 0.3|settings.brightness,
|
||||||
|
min: 0.1, max: 1,
|
||||||
|
step: 0.1,
|
||||||
|
onchange: v => {
|
||||||
|
settings.brightness = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
"type": "clkinfo",
|
"type": "clkinfo",
|
||||||
"tags": "clkinfo",
|
"tags": "clkinfo,clock",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"clkinfoclk.clkinfo.js","url":"clkinfo.js"}
|
{"name":"clkinfoclk.clkinfo.js","url":"clkinfo.js"}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -0,0 +1,22 @@
|
||||||
|
(function() {
|
||||||
|
let strideLength = (require("Storage").readJSON("myprofile.json",1)||{}).strideLength ?? 0.79,
|
||||||
|
lastSteps = 0;
|
||||||
|
function stepUpdateHandler() { distance.emit("redraw"); }
|
||||||
|
var distance = {
|
||||||
|
name : "Distance",
|
||||||
|
get : () => { let v = (Bangle.getHealthStatus("day").steps - lastSteps)*strideLength; return {
|
||||||
|
text : require("locale").distance(v,1),
|
||||||
|
img : atob("GBiBAAMAAAeAAA/AAA/AAA/gAA/gwAfh4AfD4APD4AOH4AAH4ADj4AHjwAHhwADgAAACAAAHgAAPAAAHAAgCEBgAGD///BgAGAgAEA==")
|
||||||
|
};},
|
||||||
|
run : function() {
|
||||||
|
lastSteps = (lastSteps>=Bangle.getHealthStatus("day").steps) ? 0 : Bangle.getHealthStatus("day").steps;
|
||||||
|
this.emit("redraw");
|
||||||
|
},
|
||||||
|
show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); },
|
||||||
|
hide : function() { Bangle.removeListener("step", stepUpdateHandler); }
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
name: "Bangle",
|
||||||
|
items: [ distance ]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,14 @@
|
||||||
|
{ "id": "clkinfodist",
|
||||||
|
"name": "Clockinfo Distance",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Uses the 'My Profile' app's Stride Length to calculate distance travelled based on step count. Tap to reset for measuring distances.",
|
||||||
|
"icon": "app.png",
|
||||||
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
"type": "clkinfo",
|
||||||
|
"tags": "clkinfo,distance,steps,outdoors,tool",
|
||||||
|
"dependencies": {"myprofile":"app"},
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"storage": [
|
||||||
|
{"name":"clkinfodist.clkinfo.js","url":"clkinfo.js"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -17,3 +17,5 @@
|
||||||
0.16: Add BLE clkinfo entry
|
0.16: Add BLE clkinfo entry
|
||||||
0.17: Fix BLE icon alignment and border on some clocks
|
0.17: Fix BLE icon alignment and border on some clocks
|
||||||
0.18: Tweak BLE icon to add gap and ensure middle of B isn't filled
|
0.18: Tweak BLE icon to add gap and ensure middle of B isn't filled
|
||||||
|
0.19: Fix Altitude ClockInfo after BLE added
|
||||||
|
Tapping Altitude now updates the reading
|
||||||
|
|
@ -39,22 +39,23 @@ exports.load = function() {
|
||||||
var hrm = 0;
|
var hrm = 0;
|
||||||
var alt = "--";
|
var alt = "--";
|
||||||
// callbacks (needed for easy removal of listeners)
|
// callbacks (needed for easy removal of listeners)
|
||||||
function batteryUpdateHandler() { bangleItems[0].emit("redraw"); }
|
function batteryUpdateHandler() { bangleItems.find(i=>i.name=="Battery").emit("redraw"); }
|
||||||
function stepUpdateHandler() { bangleItems[1].emit("redraw"); }
|
function stepUpdateHandler() { bangleItems.find(i=>i.name=="Steps").emit("redraw"); }
|
||||||
function hrmUpdateHandler(e) {
|
function hrmUpdateHandler(e) {
|
||||||
if (e && e.confidence>60) hrm = Math.round(e.bpm);
|
if (e && e.confidence>60) hrm = Math.round(e.bpm);
|
||||||
bangleItems[2].emit("redraw");
|
bangleItems.find(i=>i.name=="HRM").emit("redraw");
|
||||||
}
|
}
|
||||||
function altUpdateHandler() {
|
function altUpdateHandler() {
|
||||||
try {
|
try {
|
||||||
Bangle.getPressure().then(data=>{
|
Bangle.getPressure().then(data=>{
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
alt = Math.round(data.altitude) + "m";
|
alt = Math.round(data.altitude) + "m";
|
||||||
bangleItems[3].emit("redraw");
|
bangleItems.find(i=>i.name=="Altitude").emit("redraw");
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("Caught "+e+"\n in function altUpdateHandler in module clock_info");
|
print("Caught "+e+"\n in function altUpdateHandler in module clock_info");
|
||||||
bangleItems[3].emit('redraw');}
|
bangleItems.find(i=>i.name=="Altitude").emit('redraw');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// actual menu
|
// actual menu
|
||||||
var menu = [{
|
var menu = [{
|
||||||
|
|
@ -120,7 +121,6 @@ exports.load = function() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ name: "BLE",
|
{ name: "BLE",
|
||||||
hasRange: false,
|
|
||||||
isOn: () => {
|
isOn: () => {
|
||||||
const s = NRF.getSecurityStatus();
|
const s = NRF.getSecurityStatus();
|
||||||
return s.advertising || s.connected;
|
return s.advertising || s.connected;
|
||||||
|
|
@ -156,6 +156,7 @@ exports.load = function() {
|
||||||
min : 0, max : settings.maxAltitude,
|
min : 0, max : settings.maxAltitude,
|
||||||
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==")
|
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAAACAAAGAAAPAAEZgAOwwAPwQAZgYAwAMBgAGBAACDAADGAABv///////wAAAAAAAAAAAAAAAAAAAA==")
|
||||||
}),
|
}),
|
||||||
|
run : function() { alt = "--"; this.emit("redraw"); altUpdateHandler(); },
|
||||||
show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); },
|
show : function() { this.interval = setInterval(altUpdateHandler, 60000); alt = "--"; altUpdateHandler(); },
|
||||||
hide : function() { clearInterval(this.interval); delete this.interval; },
|
hide : function() { clearInterval(this.interval); delete this.interval; },
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "clock_info",
|
{ "id": "clock_info",
|
||||||
"name": "Clock Info Module",
|
"name": "Clock Info Module",
|
||||||
"shortName": "Clock Info",
|
"shortName": "Clock Info",
|
||||||
"version":"0.18",
|
"version":"0.19",
|
||||||
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,5 @@
|
||||||
0.33: Ensure readAllRecordsSince always includes the current day
|
0.33: Ensure readAllRecordsSince always includes the current day
|
||||||
Speed improvements (put temporary functions in RAM where possible)
|
Speed improvements (put temporary functions in RAM where possible)
|
||||||
0.34: Fix readFullDatabase (was skipping first month of data)
|
0.34: Fix readFullDatabase (was skipping first month of data)
|
||||||
0.35: Update boot/lib.min.js
|
0.35: Update boot/lib.min.js
|
||||||
|
0.36: Fix Distance graphs that used '1*' to remove the suffix
|
||||||
|
|
@ -32,7 +32,7 @@ function menuStepCount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function menuDistance() {
|
function menuDistance() {
|
||||||
const distMult = 1*require("locale").distance(myprofile.strideLength, 2); // hackish: this removes the distance suffix, e.g. 'm'
|
const distMult = parseFloat(require("locale").distance(myprofile.strideLength, 2)); // this removes the distance suffix, e.g. 'm'
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
"": { title:/*LANG*/"Distance" },
|
"": { title:/*LANG*/"Distance" },
|
||||||
/*LANG*/"< Back": () => menuStepCount(),
|
/*LANG*/"< Back": () => menuStepCount(),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "health",
|
"id": "health",
|
||||||
"name": "Health Tracking",
|
"name": "Health Tracking",
|
||||||
"shortName": "Health",
|
"shortName": "Health",
|
||||||
"version": "0.35",
|
"version": "0.36",
|
||||||
"description": "Logs health data and provides an app to view it",
|
"description": "Logs health data and provides an app to view it",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"screenshots" : [ { "url":"screenshot.png" } ],
|
"screenshots" : [ { "url":"screenshot.png" } ],
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
// a message
|
// a message
|
||||||
require("messages").pushMessage({"t":"add","id":1575479849,"src":"WhatsApp","title":"My Friend","body":"Hey! How's everything going?",reply:1,negative:1})
|
require("messages").pushMessage({"t":"add","id":1575479849,"src":"WhatsApp","title":"My Friend","body":"Hey! How's everything going?",reply:1,negative:1})
|
||||||
require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1})
|
require("messages").pushMessage({"t":"add","id":1575479850,"src":"Skype","title":"My Friend","body":"Hey! How's everything going? This is a really really long message that is really so super long you'll have to scroll it lots and lots",positive:1,negative:1})
|
||||||
require("messages").pushMessage({"t":"add","id":23232,"src":"Skype","title":"Mr. Bobby McBobFace","body":"Boopedy-boop",positive:1,negative:1})
|
require("messages").pushMessage({"t":"add","id":23232,"src":"Skype","title":"Mr. Bobby McBobFace","body":"Boopedy-boop",positive:1,negative:1})
|
||||||
require("messages").pushMessage({"t":"add","id":23233,"src":"Skype","title":"Thyttan test","body":"Nummerplåtsbelysning trodo",positive:1,negative:1})
|
require("messages").pushMessage({"t":"add","id":23233,"src":"Skype","title":"Thyttan test","body":"Nummerplåtsbelysning trodo",positive:1,negative:1})
|
||||||
require("messages").pushMessage({"t":"add","id":23234,"src":"Skype","title":"Thyttan test 2","body":"Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo",positive:1,negative:1})
|
require("messages").pushMessage({"t":"add","id":23234,"src":"Skype","title":"Thyttan test 2","body":"Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo Nummerplåtsbelysning trodo",positive:1,negative:1})
|
||||||
|
|
|
||||||
|
|
@ -88,3 +88,4 @@ of 'Select Clock'
|
||||||
0.77: Save altitude calibration when user exits via reset
|
0.77: Save altitude calibration when user exits via reset
|
||||||
0.78: Fix menu scroll restore on BangleJS1
|
0.78: Fix menu scroll restore on BangleJS1
|
||||||
0.79: Ensure that tapping on pressure/altitude doesn't cause a menu to display temporarily
|
0.79: Ensure that tapping on pressure/altitude doesn't cause a menu to display temporarily
|
||||||
|
0.80: Add option to set LCD brightness to 0, default brightness is now 0 as well.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "setting",
|
"id": "setting",
|
||||||
"name": "Settings",
|
"name": "Settings",
|
||||||
"version": "0.79",
|
"version": "0.80",
|
||||||
"description": "A menu for setting up Bangle.js",
|
"description": "A menu for setting up Bangle.js",
|
||||||
"icon": "settings.png",
|
"icon": "settings.png",
|
||||||
"tags": "tool,system",
|
"tags": "tool,system",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
|
@ -474,11 +475,11 @@ function LCDMenu() {
|
||||||
Object.assign(lcdMenu, {
|
Object.assign(lcdMenu, {
|
||||||
/*LANG*/'LCD Brightness': {
|
/*LANG*/'LCD Brightness': {
|
||||||
value: settings.brightness,
|
value: settings.brightness,
|
||||||
min: 0.1,
|
min : BANGLEJS2 ? 0 : 0.1,
|
||||||
max: 1,
|
max: 1,
|
||||||
step: 0.1,
|
step: 0.1,
|
||||||
onchange: v => {
|
onchange: v => {
|
||||||
settings.brightness = v || 1;
|
settings.brightness = v ?? 1;
|
||||||
updateSettings();
|
updateSettings();
|
||||||
Bangle.setLCDBrightness(settings.brightness);
|
Bangle.setLCDBrightness(settings.brightness);
|
||||||
}
|
}
|
||||||
|
|
@ -1077,4 +1078,4 @@ function showAltitude() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the main menu
|
// Show the main menu
|
||||||
pushMenu(mainMenu());
|
pushMenu(mainMenu());
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Added pie chart for visualization, tweaked UI.
|
||||||
|
0.03: Fixed bug with total storage pie chart.
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB |
|
|
@ -3,67 +3,152 @@
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<script src="../../core/lib/customize.js"></script>
|
<script src="../../core/lib/customize.js"></script>
|
||||||
<div id="storageInfo"></div>
|
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
||||||
|
|
||||||
|
<!-- Toggle Buttons -->
|
||||||
|
<div class="btn-group" style="margin: 1em; display: flex; justify-content: center;">
|
||||||
|
<button id="tableButton" class="btn btn-primary">View Table</button>
|
||||||
|
<button id="pieChartButton" class="btn">View Pie Chart</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table View -->
|
||||||
|
<div id="storageTable"></div>
|
||||||
|
|
||||||
|
<!-- Chart View -->
|
||||||
|
<div id="storagePieChart" style="display: none; flex-direction: column; align-items: center;">
|
||||||
|
<div id="piechart" style="width: 100%; max-width: 600px; height: 400px;"></div>
|
||||||
|
<div id="totalStoragePie" style="width: 100%; max-width: 600px; height: 300px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Called when we know what device we're using
|
let globalApps = [];
|
||||||
|
let storageStats = null;
|
||||||
|
|
||||||
function onInit(device) {
|
function onInit(device) {
|
||||||
Util.showModal("Reading Storage...");
|
Util.showModal("Reading Storage...");
|
||||||
Puck.eval(`require("Storage").list(/\\.info$/).map(appInfoName => {
|
Puck.eval(`(()=>{
|
||||||
let appInfo = require("Storage").readJSON(appInfoName,1)||{};
|
let getApps = () => require("Storage").list(/\\.info$/).map(appInfoName => {
|
||||||
//print(appInfoName, appInfo);
|
let appInfo = require("Storage").readJSON(appInfoName,1)||{};
|
||||||
var fileSize = 0, dataSize = 0;
|
var fileSize = 0, dataSize = 0;
|
||||||
appInfo.files.split(",").forEach(f => fileSize += require("Storage").read(f).length);
|
appInfo.files.split(",").forEach(f => fileSize += require("Storage").read(f).length);
|
||||||
var data = (appInfo.data||"").split(";");
|
var data = (appInfo.data||"").split(";");
|
||||||
function wildcardToRegexp(wc) {
|
function wildcardToRegexp(wc) {
|
||||||
return new RegExp("^"+wc.replaceAll(".","\\\\.").replaceAll("?",".*")+"$");
|
return new RegExp("^"+wc.replaceAll(".","\\\\.").replaceAll("?",".*")+"$");
|
||||||
}
|
}
|
||||||
// normal files
|
if (data[0]) data[0].split(",").forEach(wc => {
|
||||||
if (data[0]) data[0].split(",").forEach(wc => {
|
require("Storage").list(wildcardToRegexp(wc), {sf:false}).forEach(f => {
|
||||||
require("Storage").list(wildcardToRegexp(wc), {sf:false}).forEach(f => {
|
dataSize += require("Storage").read(f).length
|
||||||
dataSize += require("Storage").read(f).length
|
});
|
||||||
});
|
});
|
||||||
});
|
if (data[1]) data[1].split(",").forEach(wc => {
|
||||||
// storage files
|
require("Storage").list(wildcardToRegexp(wc), {sf:true}).forEach(f => {
|
||||||
if (data[1]) data[1].split(",").forEach(wc => {
|
dataSize += require("Storage").open(f,"r").getLength();
|
||||||
require("Storage").list(wildcardToRegexp(wc), {sf:true}).forEach(f => {
|
});
|
||||||
dataSize += require("Storage").open(f,"r").getLength();
|
});
|
||||||
});
|
return [appInfo.id, fileSize, dataSize];
|
||||||
});
|
});
|
||||||
return [appInfo.id, fileSize, dataSize];
|
return [getApps(), require(\"Storage\").getStats()]; })()`, function(result) {
|
||||||
})`, function(apps) {
|
Util.hideModal();
|
||||||
apps.sort((a,b) => (b[1]+b[2]) - (a[1]+a[2]));
|
globalApps = result[0].sort((a,b) => (b[1]+b[2]) - (a[1]+a[2]));
|
||||||
Util.hideModal();
|
storageStats = result[1];
|
||||||
console.log(apps);
|
|
||||||
document.getElementById("storageInfo").innerHTML = `
|
if (globalApps.length === 0) {
|
||||||
<table class="table table-striped">
|
document.getElementById("storageTable").innerHTML = "<p>No apps found</p>";
|
||||||
<thead>
|
return;
|
||||||
<tr>
|
}
|
||||||
<th>App</th>
|
|
||||||
<th>Code (kb)</th>
|
drawTable();
|
||||||
<th>Data (kb)</th>
|
});
|
||||||
<th>Total (kb)</th>
|
}
|
||||||
</tr>
|
function roundDecimal(num){
|
||||||
</thead>
|
return Math.round(num * 10) / 10;
|
||||||
<tbody>
|
}
|
||||||
${apps.map(app => `
|
function drawTable() {
|
||||||
<tr>
|
document.getElementById("storageTable").innerHTML = `
|
||||||
<td>${app[0]}</td>
|
<table class="table table-striped">
|
||||||
<td>${(app[1]/1000).toFixed(1)}</td>
|
<thead>
|
||||||
<td>${(app[2]/1000).toFixed(1)}</td>
|
<tr>
|
||||||
<td>${((app[1]+app[2])/1000).toFixed(1)}</td>
|
<th>App</th>
|
||||||
</tr>`).join("")}
|
<th>Code (kb)</th>
|
||||||
</tbody>
|
<th>Data (kb)</th>
|
||||||
</table>`;
|
<th>Total (kb)</th>
|
||||||
if (apps.length === 0) {
|
</tr>
|
||||||
document.getElementById("storageInfo").innerHTML = "<p>No apps found</p>";
|
</thead>
|
||||||
}
|
<tbody>
|
||||||
});
|
${globalApps.map(app => `
|
||||||
|
<tr>
|
||||||
|
<td>${app[0]}</td>
|
||||||
|
<td>${(app[1]/1000).toFixed(1)}</td>
|
||||||
|
<td>${(app[2]/1000).toFixed(1)}</td>
|
||||||
|
<td>${((app[1]+app[2])/1000).toFixed(1)}</td>
|
||||||
|
</tr>`).join("")}
|
||||||
|
</tbody>
|
||||||
|
</table>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawChart() {
|
||||||
|
if (globalApps.length === 0) return;
|
||||||
|
|
||||||
|
// App-specific chart
|
||||||
|
const chartData = [
|
||||||
|
['App', 'Total Size (KB)']
|
||||||
|
].concat(globalApps.map(app => [app[0], roundDecimal((app[1] + app[2])/1000)]));
|
||||||
|
|
||||||
|
const data = google.visualization.arrayToDataTable(chartData);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
title: 'App Storage Breakdown (KBs)',
|
||||||
|
chartArea: { width: '90%', height: '80%' },
|
||||||
|
legend: { position: 'bottom' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const chart = new google.visualization.PieChart(document.getElementById('piechart'));
|
||||||
|
chart.draw(data, options);
|
||||||
|
|
||||||
|
// Total storage chart
|
||||||
|
if (storageStats) {
|
||||||
|
const usedKB = roundDecimal(storageStats.fileBytes / 1000);
|
||||||
|
const freeKB = roundDecimal(storageStats.freeBytes / 1000);
|
||||||
|
const trashKB = roundDecimal(storageStats.trashBytes / 1000);
|
||||||
|
const totalData = google.visualization.arrayToDataTable([
|
||||||
|
['Type', 'KB'],
|
||||||
|
['Used', usedKB],
|
||||||
|
['Free', freeKB],
|
||||||
|
['Trash', trashKB],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const totalOptions = {
|
||||||
|
title: 'Total Storage Usage (KBs)',
|
||||||
|
|
||||||
|
chartArea: { width: '90%', height: '80%' },
|
||||||
|
legend: { position: 'bottom' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const totalChart = new google.visualization.PieChart(document.getElementById('totalStoragePie'));
|
||||||
|
totalChart.draw(totalData, totalOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
google.charts.load('current', {'packages':['corechart']});
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById("pieChartButton").addEventListener("click", function () {
|
||||||
|
document.getElementById("storageTable").style.display = "none";
|
||||||
|
document.getElementById("storagePieChart").style.display = "flex";
|
||||||
|
drawChart();
|
||||||
|
this.classList.add("btn-primary");
|
||||||
|
document.getElementById("tableButton").classList.remove("btn-primary");
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("tableButton").addEventListener("click", function () {
|
||||||
|
document.getElementById("storageTable").style.display = "block";
|
||||||
|
document.getElementById("storagePieChart").style.display = "none";
|
||||||
|
drawTable();
|
||||||
|
this.classList.add("btn-primary");
|
||||||
|
document.getElementById("pieChartButton").classList.remove("btn-primary");
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"id": "storageanalyzer",
|
"id": "storageanalyzer",
|
||||||
"name": "Storage Analyzer",
|
"name": "Storage Analyzer",
|
||||||
"version": "0.01",
|
"version": "0.03",
|
||||||
"description": "Analyses Bangle.js storage and shows which apps are using it",
|
"description": "Analyzes Bangle.js storage and shows which apps are using storage space",
|
||||||
"icon": "app.png",
|
"icon": "icon.png",
|
||||||
"type": "RAM",
|
"type": "RAM",
|
||||||
"tags": "tool,storage,flash,memory",
|
"tags": "tool,storage,flash,memory",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@
|
||||||
0.05: Make the "App source not found" warning less buggy
|
0.05: Make the "App source not found" warning less buggy
|
||||||
0.06: Fixed a crash if an app has no tags (app.tags is undefined)
|
0.06: Fixed a crash if an app has no tags (app.tags is undefined)
|
||||||
0.07: Clear cached app list when updating showClocks setting
|
0.07: Clear cached app list when updating showClocks setting
|
||||||
|
0.08: Add haptic feedback option when selecting app or category in menu, increase vector size limit in settings
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ Settings
|
||||||
|
|
||||||
- `Font` - The font used (`4x6`, `6x8`, `12x20`, `6x15` or `Vector`). Default `12x20`.
|
- `Font` - The font used (`4x6`, `6x8`, `12x20`, `6x15` or `Vector`). Default `12x20`.
|
||||||
- `Vector Font Size` - The size of the font if `Font` is set to `Vector`. Default `10`.
|
- `Vector Font Size` - The size of the font if `Font` is set to `Vector`. Default `10`.
|
||||||
|
- `Haptic Feedback` - Whether or not to vibrate slightly when selecting an app or category in the launcher. Default `No`.
|
||||||
- `Show Clocks` - If set to `No` then clocks won't appear in the app list. Default `Yes`.
|
- `Show Clocks` - If set to `No` then clocks won't appear in the app list. Default `Yes`.
|
||||||
- `Fullscreen` - If set to `Yes` then widgets won't be loaded. Default `No`.
|
- `Fullscreen` - If set to `Yes` then widgets won't be loaded. Default `No`.
|
||||||
|
|
||||||
|
|
@ -28,3 +29,4 @@ Contributors
|
||||||
|
|
||||||
- [atjn](https://github.com/atjn)
|
- [atjn](https://github.com/atjn)
|
||||||
- [BlueFox4](https://github.com/BlueFox4)
|
- [BlueFox4](https://github.com/BlueFox4)
|
||||||
|
- [RKBoss6](https://github.com/RKBoss6)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ let vectorval = 20;
|
||||||
let font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
|
let font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
|
||||||
let settings = Object.assign({
|
let settings = Object.assign({
|
||||||
showClocks: true,
|
showClocks: true,
|
||||||
fullscreen: false
|
fullscreen: false,
|
||||||
|
buzz:false
|
||||||
}, s.readJSON("taglaunch.json", true) || {});
|
}, s.readJSON("taglaunch.json", true) || {});
|
||||||
if ("vectorsize" in settings)
|
if ("vectorsize" in settings)
|
||||||
vectorval = parseInt(settings.vectorsize);
|
vectorval = parseInt(settings.vectorsize);
|
||||||
|
|
@ -108,15 +109,25 @@ let showTagMenu = (tag) => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
select : i => {
|
select : i => {
|
||||||
let app = appsByTag[tag][i];
|
const loadApp = () => {
|
||||||
if (!app) return;
|
let app = appsByTag[tag][i];
|
||||||
if (!app.src || require("Storage").read(app.src)===undefined) {
|
if (!app) return;
|
||||||
Bangle.setUI();
|
if (!app.src || require("Storage").read(app.src)===undefined) {
|
||||||
E.showMessage(/*LANG*/"App Source\nNot found");
|
Bangle.setUI();
|
||||||
setTimeout(showMainMenu, 2000);
|
E.showMessage(/*LANG*/"App Source\nNot found");
|
||||||
} else {
|
setTimeout(showMainMenu, 2000);
|
||||||
load(app.src);
|
} else {
|
||||||
|
load(app.src);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if(settings.buzz){
|
||||||
|
Bangle.buzz(25);
|
||||||
|
//let the buzz have effect
|
||||||
|
setTimeout(loadApp,27);
|
||||||
|
}else{
|
||||||
|
loadApp();
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
back : showMainMenu,
|
back : showMainMenu,
|
||||||
remove: unload
|
remove: unload
|
||||||
|
|
@ -138,6 +149,7 @@ let showMainMenu = () => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
select : i => {
|
select : i => {
|
||||||
|
if(settings.buzz)Bangle.buzz(25);
|
||||||
let tag = tagKeys[i];
|
let tag = tagKeys[i];
|
||||||
showTagMenu(tag);
|
showTagMenu(tag);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
"id": "taglaunch",
|
"id": "taglaunch",
|
||||||
"name": "Tag Launcher",
|
"name": "Tag Launcher",
|
||||||
"shortName": "Taglauncher",
|
"shortName": "Taglauncher",
|
||||||
"version": "0.07",
|
"version": "0.08",
|
||||||
"description": "Launcher that puts all applications into submenus based on their tag. With many applications installed this can result in a faster application selection than the linear access of the default launcher.",
|
"description": "Launcher that puts all applications into submenus based on their tag. With many applications installed this can result in a faster application selection than the linear access from the default launcher.",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"type": "launch",
|
"type": "launch",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
(function(back) {
|
(function(back) {
|
||||||
let settings = Object.assign({
|
let settings = Object.assign({
|
||||||
showClocks: true,
|
showClocks: true,
|
||||||
fullscreen: false
|
fullscreen: false,
|
||||||
|
buzz:false
|
||||||
}, require("Storage").readJSON("taglaunch.json", true) || {});
|
}, require("Storage").readJSON("taglaunch.json", true) || {});
|
||||||
|
|
||||||
let fonts = g.getFonts();
|
let fonts = g.getFonts();
|
||||||
|
|
@ -21,9 +22,16 @@
|
||||||
},
|
},
|
||||||
/*LANG*/"Vector Font Size": {
|
/*LANG*/"Vector Font Size": {
|
||||||
value: settings.vectorsize || 10,
|
value: settings.vectorsize || 10,
|
||||||
min:10, max: 20,step:1,wrap:true,
|
min:10, max: 25,step:1,wrap:true,
|
||||||
onchange: (m) => {save("vectorsize", m)}
|
onchange: (m) => {save("vectorsize", m)}
|
||||||
},
|
},
|
||||||
|
/*LANG*/"Haptic Feedback": {
|
||||||
|
value: settings.buzz == true,
|
||||||
|
onchange: (m) => {
|
||||||
|
save("buzz", m);
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
/*LANG*/"Show Clocks": {
|
/*LANG*/"Show Clocks": {
|
||||||
value: settings.showClocks == true,
|
value: settings.showClocks == true,
|
||||||
onchange: (m) => {
|
onchange: (m) => {
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
0.04: Get time zone from settings for showing the clock
|
0.04: Get time zone from settings for showing the clock
|
||||||
0.05: Minor code improvements
|
0.05: Minor code improvements
|
||||||
0.06: Adjust format of title, save counter before leaving help screen
|
0.06: Adjust format of title, save counter before leaving help screen
|
||||||
|
0.07: Refactor code, fix stuttering timer, add settings menu
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
A simple timer. You can easily set up the time. The initial time is 2:30
|
A simple timer. You can easily set up the time. The initial time is 2:30
|
||||||
|
|
||||||
On the first screen, you can
|
On the first screen, you can
|
||||||
- tap to get help
|
- double tap to get help
|
||||||
- swipe up/down to change the timer by +/- one minute
|
- swipe up/down to change the timer by +/- one minute
|
||||||
- swipe left/right to change the time by +/- 15 seconds
|
- swipe left/right to change the time by +/- 15 seconds
|
||||||
- press Btn1 to start
|
- press Btn1 to start
|
||||||
|
|
@ -12,24 +12,31 @@ Press Btn1 again to stop the timer
|
||||||
- when time is up, your Bangle will buzz for 15 seconds
|
- when time is up, your Bangle will buzz for 15 seconds
|
||||||
- and it will count up to 60 seconds and stop after that
|
- and it will count up to 60 seconds and stop after that
|
||||||
|
|
||||||
## Images
|
The time changes can be adjusted in the settings menu.
|
||||||
_1. Startscreen_
|
|
||||||
|
|
||||||

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

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

|

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

|

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

|

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

|
||||||
When time is up, the watch will buzz for 15 seconds. It will count up to 60 seconds.
|
When time is up, the watch will buzz for 15 seconds. It will count up to 60 seconds.
|
||||||
|
|
||||||
## Requests
|
## Requests
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
|
@ -1,237 +1,217 @@
|
||||||
// Tea Timer
|
const FILE = "teatimer.json";
|
||||||
// Button press stops timer, next press restarts timer
|
const DEFAULTS = {
|
||||||
let drag;
|
timerDuration: 150,
|
||||||
var counter = 0;
|
bigJump: 60,
|
||||||
var counterStart = 150; // 150 seconds
|
smallJump: 15,
|
||||||
var counterInterval;
|
finishBuzzDuration: 1500,
|
||||||
const states = {
|
overtimeBuzzDuration: 100,
|
||||||
init: 1, // unused
|
overtimeBuzzLimit: 60,
|
||||||
help: 2, // show help text
|
overtimeBuzzSeconds: 15
|
||||||
start: 4, // show/change initial counter
|
|
||||||
count: 8, // count down
|
|
||||||
countUp: 16, // count up after timer finished
|
|
||||||
stop: 32 // timer stopped
|
|
||||||
};
|
};
|
||||||
var state = states.start;
|
|
||||||
let setting = require("Storage").readJSON("setting.json",1);
|
|
||||||
E.setTimeZone(setting.timezone);
|
|
||||||
|
|
||||||
// Title showing current time
|
// Enum for states
|
||||||
function appTitle() {
|
const STATES = {
|
||||||
return "Tea Timer\n" + currentTime();
|
INIT: "init",
|
||||||
|
RUNNING: "running",
|
||||||
|
PAUSED: "paused",
|
||||||
|
FINISHED: "finished",
|
||||||
|
OVERTIME: "overtime"
|
||||||
|
};
|
||||||
|
|
||||||
|
let savedSettings = require("Storage").readJSON(FILE, 1) || {};
|
||||||
|
let settings = Object.assign({}, DEFAULTS, savedSettings);
|
||||||
|
|
||||||
|
let state = STATES.INIT;
|
||||||
|
let showHelp = false;
|
||||||
|
|
||||||
|
let startTime = 0;
|
||||||
|
let remaining = settings.timerDuration;
|
||||||
|
let target = 0;
|
||||||
|
|
||||||
|
let drag = null;
|
||||||
|
let dragAdjusted = false;
|
||||||
|
let lastTapTime = 0;
|
||||||
|
|
||||||
|
// === Helpers ===
|
||||||
|
function formatTime(s) {
|
||||||
|
let m = Math.floor(s / 60);
|
||||||
|
let sec = (s % 60).toString().padStart(2, '0');
|
||||||
|
return `${m}:${sec}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentTime() {
|
function getTimeStr() {
|
||||||
let min = Date().getMinutes();
|
let d = new Date();
|
||||||
if (min < 10) min = "0" + min;
|
return `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
|
||||||
return Date().getHours() + ":" + min;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeFormated(sec) {
|
function isState(s) {
|
||||||
let min = Math.floor(sec / 60);
|
return state === s;
|
||||||
sec = sec % 60;
|
|
||||||
if (sec < 10) sec = "0" + sec;
|
|
||||||
return min + ":" + sec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize timer and show timer value => state: start
|
function setState(s) {
|
||||||
function initTimer() {
|
state = s;
|
||||||
counter = counterStart;
|
|
||||||
setState(states.start);
|
|
||||||
showCounter(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// timer value (counter) can be changed in state start
|
// === UI Drawing ===
|
||||||
function changeCounter(diff) {
|
function drawUI() {
|
||||||
if (state == states.start) {
|
g.reset();
|
||||||
if (counter + diff > 0) {
|
g.setBgColor(g.theme.bg).clear();
|
||||||
counter = counter + diff;
|
g.setColor(g.theme.fg);
|
||||||
showCounter(true);
|
let cx = g.getWidth() / 2;
|
||||||
}
|
|
||||||
|
// Time (top right)
|
||||||
|
g.setFont("6x8", 2);
|
||||||
|
g.setFontAlign(1, 0);
|
||||||
|
g.drawString(getTimeStr(), g.getWidth() - 4, 10);
|
||||||
|
|
||||||
|
// Help text
|
||||||
|
if (showHelp) {
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
g.setFont("Vector", 15);
|
||||||
|
g.drawString(
|
||||||
|
`Swipe up/down: ±${settings.bigJump}s\nSwipe left/right: ±${settings.smallJump}s\n\nBTN1: Start/Pause\nDouble Tap: Hide Help`,
|
||||||
|
cx, 80
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
|
g.setFont("Vector", 20);
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
let label = (isState(STATES.OVERTIME)) ? "Time's Up!" : "Tea Timer";
|
||||||
|
g.drawString(label, cx, 40);
|
||||||
|
|
||||||
|
// Time remaining / overtime
|
||||||
|
g.setFont("Vector", 60);
|
||||||
|
g.setColor(isState(STATES.OVERTIME) ? "#f00" : g.theme.fg);
|
||||||
|
g.drawString(formatTime(remaining), cx, 100);
|
||||||
|
|
||||||
|
// Bottom state text
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
if (isState(STATES.PAUSED)) {
|
||||||
|
g.setFont("6x8", 2);
|
||||||
|
g.drawString("paused", cx, g.getHeight() - 20);
|
||||||
|
} else if (!isState(STATES.RUNNING) && !isState(STATES.OVERTIME)) {
|
||||||
|
g.setFont("Vector", 13);
|
||||||
|
g.drawString("double tap for help", cx, g.getHeight() - 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// start or restart timer => state: count
|
// === Timer Logic ===
|
||||||
function startTimer() {
|
function startTimer() {
|
||||||
counterStart = counter;
|
setState(STATES.RUNNING);
|
||||||
setState(states.count);
|
startTime = Date.now();
|
||||||
countDown();
|
target = startTime + remaining * 1000;
|
||||||
if (!counterInterval)
|
|
||||||
counterInterval = setInterval(countDown, 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* show current counter value at start and while count down
|
function pauseTimer() {
|
||||||
Show
|
if (isState(STATES.RUNNING)) {
|
||||||
- Title with current time
|
remaining = Math.max(0, Math.ceil((target - Date.now()) / 1000));
|
||||||
- initial timer value
|
setState(STATES.PAUSED);
|
||||||
- remaining time
|
|
||||||
- hint for help in state start
|
|
||||||
*/
|
|
||||||
function showCounter(withHint) {
|
|
||||||
g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier
|
|
||||||
E.showMessage("", appTitle());
|
|
||||||
g.reset().setFontAlign(0,0); // center font
|
|
||||||
// draw the current counter value
|
|
||||||
g.setBgColor(-1).setColor(0,0,1); // blue
|
|
||||||
g.setFont("Vector",20); // vector font, 20px
|
|
||||||
g.drawString("Timer: " + timeFormated(counterStart),80,55);
|
|
||||||
g.setFont("Vector",60); // vector font, 60px
|
|
||||||
g.drawString(timeFormated(counter),83,100);
|
|
||||||
if (withHint) {
|
|
||||||
g.setFont("Vector",20); // vector font, 80px
|
|
||||||
g.drawString("Tap for help",80,150);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// count down and update every second
|
function resumeTimer() {
|
||||||
// when time is up, start counting up
|
if (isState(STATES.PAUSED)) {
|
||||||
function countDown() {
|
startTime = Date.now();
|
||||||
counter--;
|
target = startTime + remaining * 1000;
|
||||||
// Out of time
|
setState(STATES.RUNNING);
|
||||||
if (counter<=0) {
|
|
||||||
outOfTime();
|
|
||||||
countUp();
|
|
||||||
counterInterval = setInterval(countUp, 1000);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
showCounter(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
function outOfTime() {
|
|
||||||
E.showMessage("Time is up!",appTitle());
|
|
||||||
setState(states.countUp);
|
|
||||||
resetTimer();
|
|
||||||
Bangle.buzz();
|
|
||||||
Bangle.buzz();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* this counts up (one minute), after time is up
|
|
||||||
Show
|
|
||||||
- Title with current time
|
|
||||||
- initial timer value
|
|
||||||
- "Time is up!"
|
|
||||||
- time since timer finished
|
|
||||||
*/
|
|
||||||
function countUp() {
|
|
||||||
// buzz for 15 seconds
|
|
||||||
counter++;
|
|
||||||
if (counter <=15) {
|
|
||||||
Bangle.buzz();
|
|
||||||
}
|
|
||||||
// stop counting up after 60 seconds
|
|
||||||
if (counter > 60) {
|
|
||||||
outOfTime();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier
|
|
||||||
E.showMessage("", appTitle());
|
|
||||||
g.reset().setFontAlign(0,0); // center font
|
|
||||||
g.setBgColor(-1).setColor(0,0,1); // blue
|
|
||||||
g.setFont("Vector",20); // vector font, 20px
|
|
||||||
g.drawString("Timer: " + timeFormated(counterStart),80,55);
|
|
||||||
g.setFont("Vector",30); // vector font, 80px
|
|
||||||
g.setBgColor(-1).setColor(1,0,0); // red
|
|
||||||
g.drawString("Time is up!",85,85);
|
|
||||||
g.setFont("Vector",40); // vector font, 80px
|
|
||||||
// draw the current counter value
|
|
||||||
g.drawString(timeFormated(counter),80,130);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset when interupted by user oder 60 seconds after timer finished
|
|
||||||
function resetTimer() {
|
function resetTimer() {
|
||||||
clearInterval();
|
setState(STATES.INIT);
|
||||||
counterInterval = undefined;
|
remaining = settings.timerDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
// timer is stopped by user => state: stop
|
function tick() {
|
||||||
function stopTimer() {
|
if (isState(STATES.RUNNING)) {
|
||||||
resetTimer();
|
remaining -= 1;
|
||||||
E.showMessage("Timer stopped!", appTitle());
|
if (remaining <= 0) {
|
||||||
setState(states.stop);
|
remaining = 0;
|
||||||
}
|
setState(STATES.OVERTIME);
|
||||||
|
startTime = Date.now();
|
||||||
// timer is stopped by user while counting up => state: start
|
remaining = 0; // Start overtime count-up from 0
|
||||||
function stopTimer2() {
|
Bangle.buzz(settings.finishBuzzDuration);
|
||||||
resetTimer();
|
}
|
||||||
initTimer();
|
} else if (isState(STATES.OVERTIME)) {
|
||||||
}
|
remaining += 1;
|
||||||
|
if (remaining <= settings.overtimeBuzzSeconds) {
|
||||||
|
Bangle.buzz(settings.overtimeBuzzDuration, 0.3);
|
||||||
function setState(st) {
|
}
|
||||||
state = st;
|
if (remaining >= settings.overtimeBuzzLimit) {
|
||||||
}
|
resetTimer(); // Stop overtime after max duration
|
||||||
|
|
||||||
function buttonPressed() {
|
|
||||||
switch(state) {
|
|
||||||
case states.init:
|
|
||||||
initTimer();
|
|
||||||
break;
|
|
||||||
case states.help:
|
|
||||||
initTimer();
|
|
||||||
break;
|
|
||||||
case states.start:
|
|
||||||
startTimer();
|
|
||||||
break;
|
|
||||||
case states.count:
|
|
||||||
stopTimer();
|
|
||||||
break;
|
|
||||||
case states.countUp:
|
|
||||||
stopTimer2();
|
|
||||||
break;
|
|
||||||
case states.stop:
|
|
||||||
initTimer();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
initTimer();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Change initial counter value by swiping
|
|
||||||
swipe up: +1 minute
|
|
||||||
swipe down: -1 minute
|
|
||||||
swipe right: +15 seconds
|
|
||||||
swipe left: -15 seconds */
|
|
||||||
function initDragEvents() {
|
|
||||||
Bangle.on("drag", e => {
|
|
||||||
if (state == states.start) {
|
|
||||||
if (!drag) { // start dragging
|
|
||||||
drag = {x: e.x, y: e.y};
|
|
||||||
} else if (!e.b) { // released
|
|
||||||
const dx = e.x-drag.x, dy = e.y-drag.y;
|
|
||||||
drag = null;
|
|
||||||
if (Math.abs(dx)>Math.abs(dy)+10) {
|
|
||||||
// horizontal
|
|
||||||
changeCounter(dx>0 ? 15 : -15);
|
|
||||||
} else if (Math.abs(dy)>Math.abs(dx)+10) {
|
|
||||||
// vertical
|
|
||||||
changeCounter(dy>0 ? -60 : 60);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
drawUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
// show help text while in start state (see initDragEvents())
|
// === UI Controls ===
|
||||||
function showHelp() {
|
function toggleTimer() {
|
||||||
if (state == states.start) {
|
if (showHelp) {
|
||||||
state = states.help;
|
showHelp = false;
|
||||||
g.setBgColor(g.theme.bg);
|
} else if (isState(STATES.OVERTIME)) {
|
||||||
g.setColor(g.theme.fg);
|
resetTimer();
|
||||||
E.showMessage("Swipe up/down\n+/- one minute\n\nSwipe left/right\n+/- 15 seconds\n\nPress Btn1 to start","Tea timer help");
|
} else if (isState(STATES.INIT)) {
|
||||||
|
startTimer();
|
||||||
|
} else if (isState(STATES.PAUSED)) {
|
||||||
|
resumeTimer();
|
||||||
|
} else if (isState(STATES.RUNNING)) {
|
||||||
|
pauseTimer();
|
||||||
}
|
}
|
||||||
// return to start
|
|
||||||
else if (state == states.help) {
|
drawUI();
|
||||||
counterStart = counter;
|
}
|
||||||
initTimer();
|
|
||||||
|
function handleDoubleTap() {
|
||||||
|
if (isState(STATES.INIT)) {
|
||||||
|
let now = Date.now();
|
||||||
|
if (now - lastTapTime < 400) {
|
||||||
|
showHelp = !showHelp;
|
||||||
|
drawUI();
|
||||||
|
}
|
||||||
|
lastTapTime = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// drag events in start state (to change counter value)
|
function adjustTimer(diff) {
|
||||||
initDragEvents();
|
if (isState(STATES.INIT)) {
|
||||||
// Show help test in start state
|
remaining = Math.max(5, remaining + diff);
|
||||||
Bangle.on('touch', function(button, xy) { showHelp(); });
|
settings.timerDuration = remaining;
|
||||||
// event handling for button1
|
drawUI();
|
||||||
setWatch(buttonPressed, BTN1, {repeat: true});
|
}
|
||||||
initTimer();
|
}
|
||||||
|
|
||||||
|
function handleDrag(e) {
|
||||||
|
if (isState(STATES.INIT) && !showHelp) {
|
||||||
|
if (e.b) {
|
||||||
|
if (!drag) {
|
||||||
|
drag = { x: e.x, y: e.y };
|
||||||
|
dragAdjusted = false;
|
||||||
|
} else if (!dragAdjusted) {
|
||||||
|
let dx = e.x - drag.x;
|
||||||
|
let dy = e.y - drag.y;
|
||||||
|
|
||||||
|
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > settings.smallJump) {
|
||||||
|
adjustTimer(dx > 0 ? settings.smallJump : -settings.smallJump);
|
||||||
|
dragAdjusted = true;
|
||||||
|
} else if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > settings.bigJump) {
|
||||||
|
adjustTimer(dy > 0 ? -settings.bigJump : settings.bigJump);
|
||||||
|
dragAdjusted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
drag = null;
|
||||||
|
dragAdjusted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Init App ===
|
||||||
|
setWatch(toggleTimer, BTN1, { repeat: true });
|
||||||
|
Bangle.on("drag", handleDrag);
|
||||||
|
Bangle.on("touch", handleDoubleTap);
|
||||||
|
|
||||||
|
resetTimer();
|
||||||
|
drawUI();
|
||||||
|
setInterval(tick, 1000);
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
{
|
{
|
||||||
"id": "teatimer",
|
"id": "teatimer",
|
||||||
"name": "Tea Timer",
|
"name": "Tea Timer",
|
||||||
"version": "0.06",
|
"version": "0.07",
|
||||||
"description": "A simple timer. You can easily set up the time.",
|
"description": "A simple timer. You can easily set up the time.",
|
||||||
"icon": "teatimer.png",
|
"icon": "teatimer.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
"tags": "tool",
|
"tags": "tool",
|
||||||
"supports": ["BANGLEJS2"],
|
"supports": ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
"data": [
|
||||||
|
{ "name": "teatimer.json" }
|
||||||
|
],
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"teatimer.app.js","url":"app.js"},
|
{"name":"teatimer.app.js","url":"app.js"},
|
||||||
|
{"name": "teatimer.settings.js", "url": "settings.js" },
|
||||||
{"name":"teatimer.img","url":"app-icon.js","evaluate":true}
|
{"name":"teatimer.img","url":"app-icon.js","evaluate":true}
|
||||||
],
|
],
|
||||||
"screenshots": [
|
"screenshots": [
|
||||||
{"url":"TeatimerStart.jpg"},
|
{"url":"TeatimerStart.png"},
|
||||||
{"url":"TeatimerHelp.jpg"},
|
{"url":"TeatimerHelp.png"},
|
||||||
{"url":"TeatimerRun.jpg"},
|
{"url":"TeatimerRun.png"},
|
||||||
{"url":"TeatimerUp.jpg"}
|
{"url":"TeatimerPause.png"},
|
||||||
|
{"url":"TeatimerUp.png"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
(function(back) {
|
||||||
|
const FILE = "teatimer.json";
|
||||||
|
const DEFAULTS = {
|
||||||
|
timerDuration: 150, // Initial timer duration in seconds
|
||||||
|
bigJump: 60, // Jump for vertical swipes
|
||||||
|
smallJump: 15 // Jump for horizontal swipes
|
||||||
|
};
|
||||||
|
|
||||||
|
let settings = require("Storage").readJSON(FILE, 1) || DEFAULTS;
|
||||||
|
|
||||||
|
function saveSettings() {
|
||||||
|
require("Storage").writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSettingsMenu() {
|
||||||
|
E.showMenu({
|
||||||
|
'': { title: 'Tea Timer Settings' },
|
||||||
|
'< Back': back,
|
||||||
|
'Default Duration (sec)': {
|
||||||
|
value: settings.timerDuration,
|
||||||
|
min: 5, max: 900, step: 5,
|
||||||
|
onchange: v => {
|
||||||
|
settings.timerDuration = v;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Swipe Up/Down (sec)': {
|
||||||
|
value: settings.bigJump,
|
||||||
|
min: 5, max: 300, step: 5,
|
||||||
|
onchange: v => {
|
||||||
|
settings.bigJump = v;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Swipe Left/Right (sec)': {
|
||||||
|
value: settings.smallJump,
|
||||||
|
min: 5, max: 60, step: 5,
|
||||||
|
onchange: v => {
|
||||||
|
settings.smallJump = v;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showSettingsMenu();
|
||||||
|
})
|
||||||