Merge remote-tracking branch 'upstream/master'

pull/1335/head
hughbarney 2022-01-22 00:19:26 +00:00
commit 5532df8306
34 changed files with 1291 additions and 7 deletions

2
.gitignore vendored
View File

@ -9,4 +9,4 @@ appdates.csv
_config.yml
tests/Layout/bin/tmp.*
tests/Layout/testresult.bmp
apps.json
apps.local.json

View File

@ -8,6 +8,9 @@
# create apps.json as your site is hosted, or if you're hosting
# yourself you can run bin/create_apps_json.sh
#
# If you serve the store from localhost for development/testing,
# the loader looks for apps.local.json instead, you can run
# `bin/create_apps_json.sh apps.local.json` to create that file.
# =================================================================
# Uncomment the following line if you only want explicitly listed

1
apps/timerclk/ChangeLog Normal file
View File

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

72
apps/timerclk/README.md Normal file
View File

@ -0,0 +1,72 @@
# Timer Clock
A clock based on the Anton Clock with stopwatches, timers and alarms based on the Stopwatch Touch style and an alarm widget based on the one from Default alarm & timer.
## Features
* two slots for stopwatches / timers on the clock screen
* configurable font and size (Anton font has fixed size)
* stopwatch with modifiable start value
* timer that can be paused
* alarms
* multiple stopwatches, timers and alarms
* stopwatches and timers keep running in the background
## Images
![](screenshot.png)
### Stopwatch
![](screenshot_stopwatch1.png)
![](screenshot_stopwatch2.png)
### Settings
![](screenshot_settings1.png)
![](screenshot_settings2.png)
![](screenshot_settings3.png)
## Controls
### Bangle.js 1
#### Clock
* Left: Stopwatch
* Right: Timer
* Button 1 / 2: Alarm
#### Stopwatch / Timer / Alarm
* Button 1:
* edit mode: increase
* control mode: play / pause
* Button 2: switch between edit / control mode
* Button 3:
* edit mode: decrease
* control mode: reset / remove
* Left:
* edit mode: previous index
* control mode: previous stopwatch / timer / alarm
* Right:
* edit mode: next index
* control mode: next stopwatch / timer / alarm
### Bangle.js 2
#### Clock
* Swipe left: Stopwatch
* Swipe right: Timer
* Swipe over date: Alarm
#### Stopwatch / Timer / Alarm
* Swipe left: previous stopwatch / timer / alarm
* Swipe right: next stopwatch / timer / alarm
* Swipe up: increase index swiped over
* Swipe down: decrease index swiped over

View File

@ -0,0 +1,57 @@
if (timerclkAlarmTimeout) clearInterval(timerclkAlarmTimeout);
var timerclk = require("timerclk.lib.js");
var settings = require('Storage').readJSON("timerclk.json", true) || {};
settings = Object.assign({
"vibrate":10
}, settings.alarm||{});
function showAlarm(alarm) {
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setLocked(false);
E.showPrompt("Alarm!",{
title:"ALARM!",
buttons : {/*LANG*/"Ok":true}
}).then(function(ok) {
buzzCount = 0;
if (ok) {
alarm.last = new Date().getDate();
}
require("Storage").write("timerclk.alarm.json",JSON.stringify(alarms));
load();
});
function vibrate(counter) {
VIBRATE.write(1);
setTimeout(() => VIBRATE.write(0), 100);
if (--counter) setTimeout(() => vibrate(counter), 250);
}
function buzz() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
vibrate(4);
if (buzzCount--)
setTimeout(buzz, 3000);
else { // auto-snooze
buzzCount = settings.vibrate;
setTimeout(buzz, 600000);
}
}
var buzzCount = settings.vibrate;
buzz();
}
// Check for alarms
console.log("checking for alarms...");
var alarms = require("Storage").readJSON("timerclk.alarm.json",1)||[];
var active = alarms.filter(e=>e.on);
if (active.length) {
// if there's an alarm, show it
active = active.sort((a,b)=>(a.time-b.time)+(a.last-b.last)*86400000);
if (active[0].last != new Date().getDate()) {
showAlarm(active[0]);
} else {
setTimeout(load, 100);
}
} else {
// otherwise just go back to default app
setTimeout(load, 100);
}

1
apps/timerclk/alarm.info Normal file
View File

@ -0,0 +1 @@
{"id":"timerclk","name":"tclk Alarm","src":"timerclk.alarm.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10}

116
apps/timerclk/alarm.js Normal file
View File

@ -0,0 +1,116 @@
var timerclk = require("timerclk.lib.js");
const height = g.getHeight(), width = g.getWidth();
var all = require("Storage").readJSON("timerclk.alarm.json") || [];
var settings = require('Storage').readJSON("timerclk.json", true) || {};
settings = Object.assign({
"font":"Vector",
"fontSize":40,
"indexFont":"6x8",
"indexFontSize":3,
"buttonHeight":40,
"vibrate":4,
}, settings = settings.alarm||{});
var defaultElement = {time:43200000, on:true, last:null};
var current = 0;
var editIndex = 0;
var drawInterval;
var drawIntervalTimeout;
var buttons;
var dragBorderHrsMins=0, dragBorderMinsSecs=0;
function update() {
if (drawInterval) clearInterval(drawInterval);
if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout);
if (all[current].start) {
drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, 1000); draw();}, 1000 - (timerclk.getTime(all[current]) % 1000));
} else {
drawInterval = null;
drawIntervalTimeout = null;
}
draw();
drawButtons();
}
function activate() {
all[current].on = !all[current].on;
all[current].last = null;
update();
require("Storage").write("timerclk.alarm.json",JSON.stringify(all));
timerclkCheckAlarms();
}
function remove() {
all.splice(current, 1);
if (current == all.length) current--;
if (all.length == 0) {
all.push(defaultElement.clone());
current++;
}
update();
require("Storage").write("timerclk.alarm.json",JSON.stringify(all));
timerclkCheckAlarms();
}
function edit(position, change) {
if (position == 1) all[current].time += change*1000;
else if (position == 2) all[current].time += change*60000;
else if (position == 3) all[current].time += change*3600000;
require("Storage").write("timerclk.alarm.json",JSON.stringify(all));
timerclkCheckAlarms();
}
var buttons = {
reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:"#f50"}, // remove
play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: activate, img: timerclk.play_img, col:"#0ff"}, // active
};
function drawButtons() {
if (all[current].on) {
buttons.play.img = timerclk.pause_img;
} else {
buttons.play.img = timerclk.play_img;
}
for (var button of buttons) {
g.setColor(button.col);
g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]);
g.setColor("#000");
// scale 24px images
let iw = settings.buttonHeight-10;
var scale = iw/24;
let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2);
let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2);
g.drawImage(button.img, ix, iy, {scale: scale});
}
}
function draw() {
var x = g.getWidth()/2;
var y = g.getHeight()/2;
g.reset();
g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight);
g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize);
g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2));
g.setFontAlign(0,0).setFont(settings.font, settings.fontSize);
var timeStr = timerclk.formatTime(all[current].time, false, false, true);
g.drawString(timeStr,x,y);
var start = (width-g.stringMetrics(timeStr).width)/2;
timeStr = timeStr.split(":");
var markerPosChange = g.stringMetrics("__").width/2;
if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange;
else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange;
else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange;
else x = 0;
if (x) g.drawString("__", x, y);
dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2;
dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2;
}
if (all.length == 0) {
all.push(defaultElement.clone());
}
timerclk.registerControls(this);
Bangle.loadWidgets();
Bangle.drawWidgets();
update();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgP/AFHzvmf+f8z/8tnv/vs9/1t/v+kv94jR/H4n/wn4CBAYPwnEP8AFDg/AAoUwAoPgmABBwfQAonwAo0/4gFC4AFE4gFLmGEAoQDBxgFCwEQAIIFIj4FD/k//hNBAoZZBAoc8j6oS8/P+1NAoP63+7+wMCz/u/YEB/v/v4dI1+pAQIFBx/J/2/AoP5tFJr71eA=="))

BIN
apps/timerclk/app-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

151
apps/timerclk/app.js Normal file

File diff suppressed because one or more lines are too long

48
apps/timerclk/boot.js Normal file
View File

@ -0,0 +1,48 @@
var timerclkTimerTimeout;
var timerclkAlarmTimeout;
function timerclkCheckTimers() {
if (timerclkTimerTimeout) clearTimeout(timerclkTimerTimeout);
var timers = require('Storage').readJSON('timerclk.timer.json',1)||[];
timers = timers.filter(e=>e.start);
if (timers.length) {
timers = timers.sort((a,b)=>{
var at = a.timeAdd;
if (a.start) at += Date.now()-a.start;
at = a.period-at;
var bt = b.timeAdd;
if (b.start) bt += Date.now()-b.start;
bt = b.period-bt;
return at-bt;
});
if (!require('Storage').read("timerclk.timer.alert.js")) {
console.log("No timer app!");
} else {
var time = timers[0].timeAdd;
if (timers[0].start) time += Date.now()-timers[0].start;
time = timers[0].time - time;
if (time<1000) t=1000;
if (timerclkTimerTimeout) clearTimeout(timerclkTimerTimeout);
timerclkTimerTimeout = setTimeout(() => load("timerclk.timer.alert.js"),time);
}
}
}
function timerclkCheckAlarms() {
if (timerclkAlarmTimeout) clearTimeout(timerclkAlarmTimeout);
var alarms = require('Storage').readJSON('timerclk.alarm.json',1)||[];
var currentTime = require("timerclk.lib.js").getCurrentTime();
alarms = alarms.filter(e=>e.on);
if (alarms.length) {
alarms = alarms.sort((a,b)=>(a.time-b.time)+(a.last-b.last)*86400000);
if (!require('Storage').read("timerclk.alarm.alert.js")) {
console.log("No alarm app!");
} else {
var time = alarms[0].time-currentTime;
if (alarms[0].last == new Date().getDate() || time < 0) time += 86400000;
if (time<1000) t=1000;
if (timerclkAlarmTimeout) clearTimeout(timerclkAlarmTimeout);
timerclkAlarmTimeout = setTimeout(() => load("timerclk.alarm.alert.js"),time);
}
}
}
timerclkCheckTimers();
timerclkCheckAlarms();

127
apps/timerclk/lib.js Normal file
View File

@ -0,0 +1,127 @@
exports.pause_img = atob("GBiBAf///////////+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B////////////w==");
exports.play_img = atob("GBiBAf////////////P///D///A///Af//AH//AB//AAf/AAH/AAB/AAB/AAH/AAf/AB//AH//Af//A///D///P//////////////w==");
exports.reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w==");
exports.remove_img = atob("GBiBAf///////////+P/x+H/h+D/B/B+D/g8H/wYP/4Af/8A//+B//+B//8A//4Af/wYP/g8H/B+D+D/B+H/h+P/x////////////w==");
exports.formatTime = function(t, short, tnthEnable, fullTime) {
var negative = "";
if (t < 0) {
t = t*(-1);
negative = "-";
}
let hrs = Math.floor(t/3600000);
let mins = Math.floor(t/60000)%60;
let secs = Math.floor(t/1000)%60;
var tnth = "";
if (tnthEnable) {
tnth = Math.floor(t/100)%10;
tnth = "."+tnth;
}
var hrsStr = hrs;
if (hrs < 10 && !negative) hrsStr = "0"+hrs;
var text;
if (short) {
if (hrs === 0) text = negative + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
else text = negative + hrsStr + "/" + ("0"+mins).substr(-2);
} else {
if (hrs === 0 && !fullTime) text = negative + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + tnth;
else text = negative + hrsStr + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
}
return text;
};
exports.getTime = function(e) {
var time = e.timeAdd;
if (e.start) {
time += Date.now() - e.start;
}
return time;
};
exports.getCurrentTime = function() {
var date = new Date();
return date.getHours()*3600000+date.getMinutes()*60000+date.getSeconds()*1000+date.getMilliseconds();
};
exports.registerControls = function(o) {
if (process.env.HWVERSION==1) {
setWatch(()=>{
if (o.editIndex == 0) o.buttons.play.callback();
else o.edit(o.editIndex, 1);
o.draw();
}, BTN1, {repeat:true});
setWatch(()=>{
o.editIndex = !o.editIndex;
o.draw();
}, BTN2, {repeat:true});
setWatch(()=>{
if (o.editIndex == 0) o.buttons.reset.callback();
else o.edit(o.editIndex, -1);
o.draw();
}, BTN3, {repeat:true});
setWatch(()=>{
if (o.editIndex) {
o.editIndex++;
if (o.editIndex > 3) o.editIndex = 1;
} else if (o.current > 0) o.current--;
o.update();
}, BTN4, {repeat:true});
setWatch(()=>{
if (o.editIndex) {
o.editIndex--;
if (o.editIndex < 1) o.editIndex = 3;
} else {
o.current++;
if (o.current == o.all.length) o.all.push(o.defaultElement.clone());
}
o.update();
}, BTN5, {repeat:true});
} else {
setWatch(()=>load(), BTN1);
Bangle.on('touch',(n,e)=>{
for (var button of o.buttons) {
if (e.x>=button.pos[0] && e.y>=button.pos[1] &&
e.x<button.pos[2] && e.y<button.pos[3]) {
button.callback();
break;
}
}
});
var absX, lastX, lastY;
Bangle.on('drag', e=>{
if (!e.b) {
if (lastX > 40) { // right
o.current++;
if (o.current == o.all.length) o.all.push(o.defaultElement.clone());
} else if (lastX < -40) { // left
if (o.current > 0) {
o.current--;
}
} else if (lastY > 30) { // down
if (absX < o.dragBorderHrsMins) {
o.edit(3, -1);
} else if (absX > o.dragBorderHrsMins && absX < o.dragBorderMinsSecs) {
o.edit(2, -1);
} else {
o.edit(1, -1);
}
} else if (lastY < -30) { // up
if (absX < o.dragBorderHrsMins) {
o.edit(3, 1);
} else if (absX > o.dragBorderHrsMins && absX < o.dragBorderMinsSecs) {
o.edit(2, 1);
} else {
o.edit(1, 1);
}
}
lastX = 0;
lastY = 0;
o.update();
} else {
absX = e.x;
lastX = lastX + e.dx;
lastY = lastY + e.dy;
}
});
}
};

View File

@ -0,0 +1,38 @@
{
"id": "timerclk",
"name": "Timer Clock",
"shortName":"Timer Clock",
"version":"0.01",
"description": "A clock with stopwatches, timers and alarms build in.",
"icon": "app-icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS","BANGLEJS2"],
"screenshots": [
{"url":"screenshot.png"},
{"url":"screenshot_stopwatch1.png"},
{"url":"screenshot_stopwatch2.png"},
{"url":"screenshot_settings1.png"},
{"url":"screenshot_settings2.png"},
{"url":"screenshot_settings3.png"}
],
"readme": "README.md",
"storage": [
{"name":"timerclk.app.js","url":"app.js"},
{"name":"timerclk.img","url":"app-icon.js","evaluate":true},
{"name":"timerclk.boot.js","url":"boot.js"},
{"name":"timerclk.lib.js","url":"lib.js"},
{"name":"timerclk.wid.js","url":"wid.js"},
{"name":"timerclk.settings.js","url":"settings.js"},
{"name":"timerclk.stopwatch.js","url":"stopwatch.js"},
{"name":"timerclk.timer.js","url":"timer.js"},
{"name":"timerclk.timer.alert.js","url":"timer.alert.js"},
{"name":"timerclk.alarm.js","url":"alarm.js"},
{"name":"timerclk.alarm.alert.js","url":"alarm.alert.js"},
{"name":"timerclk.stopwatch.info","url":"stopwatch.info"},
{"name":"timerclk.timer.info","url":"timer.info"},
{"name":"timerclk.alarm.info","url":"alarm.info"}
],
"data": [{"name":"timerclk.json"},{"name":"timerclk.stopwatch.json"},{"name":"timerclk.timer.json"},{"name":"timerclk.alarm.json"}],
"sortorder": 0
}

BIN
apps/timerclk/pause-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
apps/timerclk/play-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
apps/timerclk/remove-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
apps/timerclk/reset-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

292
apps/timerclk/settings.js Normal file
View File

@ -0,0 +1,292 @@
(function(back) {
const FILE = "timerclk.json";
const BOOL_FORMAT = v=>v?/*LANG*/"On":/*LANG*/"Off";
// Load settings
var settings = require('Storage').readJSON(FILE, true) || {}
settings.clock = Object.assign({
"timeFont":"Anton",
"timeFontSize":0,
"dateFont":"6x8",
"dateFontSize":2,
"dowFont":"6x8",
"dowFontSize":2,
"specialFont":"6x8",
"specialFontSize":2,
"shortDate":true,
"showStopwatches":true,
"showTimers":true,
}, settings.clock||{});
settings.stopwatch = Object.assign({
"font":"Vector",
"fontSize":40,
"indexFont":"6x8",
"indexFontSize":3,
"buttonHeight":40,
}, settings.stopwatch||{});
settings.timer = Object.assign({
"font":"Vector",
"fontSize":40,
"indexFont":"6x8",
"indexFontSize":3,
"buttonHeight":40,
"vibrate":10,
}, settings.timer||{});
settings.alarm = Object.assign({
"font":"Vector",
"fontSize":40,
"indexFont":"6x8",
"indexFontSize":3,
"buttonHeight":40,
"vibrate":10,
}, settings.alarm||{});
var timeFonts = ["Anton"].concat(g.getFonts());
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
// Show the menu
var mainMenu = {
"" : { "title" : "Timer Clock" },
"< Back" : () => back(),
"Clock": ()=>{E.showMenu(clockMenu);},
"Stopwatch": ()=>{E.showMenu(stopwatchMenu);},
"Timer": ()=>{E.showMenu(timerMenu);},
"Alarm": ()=>{E.showMenu(alarmMenu);},
};
var clockMenu = {
"" : { "title" : "Clock" },
"< Back" : () => E.showMenu(mainMenu),
"time font":{
value: 0|timeFonts.indexOf(settings.clock.timeFont),
format: v => timeFonts[v],
min: 0, max: timeFonts.length-1,
onchange: v => {
settings.clock.timeFont = timeFonts[v];
writeSettings();
}
},
"time size":{
value: 0|settings.clock.timeFontSize,
min: 0,
onchange: v => {
settings.clock.timeFontSize = v;
writeSettings();
}
},
"date font":{
value: 0|g.getFonts().indexOf(settings.clock.dateFont),
format: v => g.getFonts()[v],
min: 0, max: g.getFonts().length-1,
onchange: v => {
settings.clock.dateFont = g.getFonts()[v];
writeSettings();
}
},
"date size":{
value: 0|settings.clock.dateFontSize,
min: 0,
onchange: v => {
settings.clock.dateFontSize = v;
writeSettings();
}
},
"dow font":{
value: 0|g.getFonts().indexOf(settings.clock.dowFont),
format: v => g.getFonts()[v],
min: 0, max: g.getFonts().length-1,
onchange: v => {
settings.clock.dowFont = g.getFonts()[v];
writeSettings();
}
},
"dow size":{
value: 0|settings.clock.dowFontSize,
min: 0,
onchange: v => {
settings.clock.dowFontSize = v;
writeSettings();
}
},
"short date": {
value: !!settings.clock.shortDate,
format: BOOL_FORMAT,
onchange: v => {
settings.clock.shortDate = v;
writeSettings();
}
},
"stopwatches": {
value: !!settings.clock.showStopwatches,
format: v=>v?/*LANG*/"Show":/*LANG*/"Hide",
onchange: v => {
settings.clock.showStopwatches = v;
writeSettings();
}
},
"timers": {
value: !!settings.clock.showTimers,
format: v=>v?/*LANG*/"Show":/*LANG*/"Hide",
onchange: v => {
settings.clock.showTimers = v;
writeSettings();
}
},
};
var stopwatchMenu = {
"" : { "title" : "Stopwatch" },
"< Back" : () => E.showMenu(mainMenu),
"font":{
value: 0|g.getFonts().indexOf(settings.stopwatch.font),
format: v => g.getFonts()[v],
min: 0, max: g.getFonts().length-1,
onchange: v => {
settings.settings.stopwatch.font = g.getFonts()[v];
writeSettings();
}
},
"fontsize":{
value: 0|settings.stopwatch.fontSize,
min: 0,
onchange: v => {
settings.stopwatch.fontSize = v;
writeSettings();
}
},
"index font":{
value: 0|g.getFonts().indexOf(settings.stopwatch.indexFont),
format: v => g.getFonts()[v],
min: 0, max: g.getFonts().length-1,
onchange: v => {
settings.settings.stopwatch.indexFont = g.getFonts()[v];
writeSettings();
}
},
"index size":{
value: 0|settings.stopwatch.indexFontSize,
min: 0,
onchange: v => {
settings.stopwatch.indexFontSize = v;
writeSettings();
}
},
"button height":{
value: 0|settings.stopwatch.buttonHeight,
min: 0,
onchange: v => {
settings.stopwatch.buttonHeight = v;
writeSettings();
}
},
};
var timerMenu = {
"" : { "title" : "Timer" },
"< Back" : () => E.showMenu(mainMenu),
"font":{
value: 0|g.getFonts().indexOf(settings.timer.font),
format: v => g.getFonts()[v],
min: 0, max: g.getFonts().length-1,
onchange: v => {
settings.settings.timer.font = g.getFonts()[v];
writeSettings();
}
},
"fontsize":{
value: 0|settings.timer.fontSize,
min: 0,
onchange: v => {
settings.timer.fontSize = v;
writeSettings();
}
},
"index font":{
value: 0|g.getFonts().indexOf(settings.timer.indexFont),
format: v => g.getFonts()[v],
min: 0, max: g.getFonts().length-1,
onchange: v => {
settings.settings.timer.indexFont = g.getFonts()[v];
writeSettings();
}
},
"index size":{
value: 0|settings.timer.indexFontSize,
min: 0,
onchange: v => {
settings.timer.indexFontSize = v;
writeSettings();
}
},
"button height":{
value: 0|settings.timer.buttonHeight,
min: 0,
onchange: v => {
settings.timer.buttonHeight = v;
writeSettings();
}
},
"vibrate":{
value: 0|settings.timer.vibrate,
min: 0,
onchange: v=>{
settings.timer.vibrate = v;
writeSettings();
}
}
};
var alarmMenu = {
"" : { "title" : "Alarm" },
"< Back" : () => E.showMenu(mainMenu),
"font":{
value: 0|g.getFonts().indexOf(settings.alarm.font),
format: v => g.getFonts()[v],
min: 0, max: g.getFonts().length-1,
onchange: v => {
settings.settings.alarm.font = g.getFonts()[v];
writeSettings();
}
},
"fontsize":{
value: 0|settings.alarm.fontSize,
min: 0,
onchange: v => {
settings.alarm.fontSize = v;
writeSettings();
}
},
"index font":{
value: 0|g.getFonts().indexOf(settings.alarm.indexFont),
format: v => g.getFonts()[v],
min: 0, max: g.getFonts().length-1,
onchange: v => {
settings.settings.alarm.indexFont = g.getFonts()[v];
writeSettings();
}
},
"index size":{
value: 0|settings.alarm.indexFontSize,
min: 0,
onchange: v => {
settings.alarm.indexFontSize = v;
writeSettings();
}
},
"button height":{
value: 0|settings.alarm.buttonHeight,
min: 0,
onchange: v => {
settings.alarm.buttonHeight = v;
writeSettings();
}
},
"vibrate":{
value: 0|settings.alarm.vibrate,
min: 0,
onchange: v=>{
settings.alarm.vibrate = v;
writeSettings();
}
}
};
E.showMenu(mainMenu);
});

View File

@ -0,0 +1 @@
{"id":"timerclk","name":"tclk Stopwatch","src":"timerclk.stopwatch.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10}

135
apps/timerclk/stopwatch.js Normal file
View File

@ -0,0 +1,135 @@
var timerclk = require("timerclk.lib.js");
const height = g.getHeight(), width = g.getWidth();
var all = require("Storage").readJSON("timerclk.stopwatch.json") || [];
var settings = require('Storage').readJSON("timerclk.json", true) || {};
settings = Object.assign({
"font":"Vector",
"fontSize":40,
"indexFont":"6x8",
"indexFontSize":3,
"buttonHeight":40,
}, settings.stopwatch||{});
var defaultElement = {start:null, timeAdd:0};
var current = 0;
var editIndex = 0;
var drawInterval;
var drawIntervalTimeout;
var buttons;
function update() {
if (drawInterval) clearInterval(drawInterval);
if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout);
var interval = Math.floor(timerclk.getTime(all[current])/3600000)?1000:100;
if (all[current].start) {
drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, interval); draw();}, interval - (timerclk.getTime(all[current]) % interval));
} else {
drawInterval = null;
drawIntervalTimeout = null;
}
draw();
drawButtons();
}
function play() {
if (all[current].start) { // running
all[current].timeAdd += Date.now() - all[current].start;
all[current].start = null;
update();
} else { // paused
all[current].start = Date.now();
update();
}
require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all));
}
function reset() {
all[current] = defaultElement.clone();
update();
require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all));
}
function remove() {
all.splice(current, 1);
if (current == all.length) current--;
if (all.length == 0) {
all.push(defaultElement.clone());
current++;
}
update();
require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all));
}
function edit(position, change) {
if (position == 1) all[current].timeAdd += change*1000;
else if (position == 2) all[current].timeAdd += change*60000;
else if (position == 3) all[current].timeAdd += change*3600000;
require("Storage").write("timerclk.stopwatch.json",JSON.stringify(all));
}
var buttonsRunning = {
reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: reset, img: timerclk.reset_img, col:"#f50"},
play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:"#0ff"},
};
var buttonsNormal = {
reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:buttonsRunning.reset.col},
play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:buttonsRunning.play.col},
};
buttons = buttonsNormal;
function drawButtons() {
if (all[current].start || all[current].time) {
buttons = buttonsRunning;
if (all[current].start) {
buttons.play.img = timerclk.pause_img;
} else {
buttons.play.img = timerclk.play_img;
}
} else {
buttons = buttonsNormal;
}
for (var button of buttons) {
g.setColor(button.col);
g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]);
g.setColor("#000");
// scale 24px images
let iw = settings.buttonHeight-10;
var scale = iw/24;
let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2);
let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2);
g.drawImage(button.img, ix, iy, {scale: scale});
}
}
function draw() {
var x = g.getWidth()/2;
var y = g.getHeight()/2;
g.reset();
var timeStr = timerclk.formatTime(timerclk.getTime(all[current]), false, true);
g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight);
g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize);
g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2));
g.setFontAlign(0,0).setFont(settings.font, settings.fontSize);
g.drawString(timeStr,x,y);
var start = (width-g.stringMetrics(timeStr).width)/2;
timeStr = timeStr.split(".")[0].split(":");
if (timeStr.length < 3) timeStr = [""].concat(timeStr);
var markerPosChange = g.stringMetrics("__").width/2;
if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange;
else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange;
else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange;
else x = 0;
if (x) g.drawString("__", x, y);
dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2;
dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2;
}
if (all.length == 0) {
all.push(defaultElement.clone());
}
timerclk.registerControls(this);
Bangle.loadWidgets();
Bangle.drawWidgets();
update();

View File

@ -0,0 +1,62 @@
if (timerclkTimerTimeout) clearInterval(timerclkTimerTimeout);
var timerclk = require("timerclk.lib.js");
var settings = require('Storage').readJSON("timerclk.json", true) || {};
settings = Object.assign({
"vibrate":10
}, settings.timer||{});
function showTimer(timer) {
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setLocked(false);
E.showPrompt("Timer finished!",{
title:"TIMER!",
buttons : {/*LANG*/"Ok":true}
}).then(function(ok) {
buzzCount = 0;
if (ok) {
timer.time += Date.now() - timer.start;
timer.start = null;
}
require("Storage").write("timerclk.timer.json",JSON.stringify(timers));
load();
});
function vibrate(counter) {
VIBRATE.write(1);
setTimeout(() => VIBRATE.write(0), 100);
if (--counter) setTimeout(() => vibrate(counter), 250);
}
function buzz() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
vibrate(4);
if (buzzCount--)
setTimeout(buzz, 3000);
else { // auto-snooze
buzzCount = settings.vibrate;
setTimeout(buzz, 600000);
}
}
var buzzCount = settings.vibrate;
buzz();
}
// Check for timers
console.log("checking for timers...");
var timers = require("Storage").readJSON("timerclk.timer.json",1)||[];
var active = timers.filter(e=>e.start);
if (active.length) {
// if there's an timer, show it
active = active.sort((a,b)=>{
var at = a.time;
if (a.start) at += Date.now()-a.start;
at = a.period-at;
var bt = b.time;
if (b.start) bt += Date.now()-b.start;
bt = b.period-bt;
return at-bt;
});
showTimer(active[0]);
} else {
// otherwise just go back to default app
setTimeout(load, 100);
}

1
apps/timerclk/timer.info Normal file
View File

@ -0,0 +1 @@
{"id":"timerclk","name":"tclk Timer","src":"timerclk.timer.js","icon":"timerclk.img","version":"0.01","tags":"","files":"","sortorder":10}

139
apps/timerclk/timer.js Normal file
View File

@ -0,0 +1,139 @@
var timerclk = require("timerclk.lib.js");
const height = g.getHeight(), width = g.getWidth();
var all = require("Storage").readJSON("timerclk.timer.json") || [];
var settings = require('Storage').readJSON("timerclk.json", true) || {};
settings = Object.assign({
"font":"Vector",
"fontSize":40,
"indexFont":"6x8",
"indexFontSize":3,
"buttonHeight":40,
"vibrate":4,
}, settings = settings.timer||{});
var defaultElement = {time:300000, start:null, timeAdd:0};
var current = 0;
var editIndex = 0;
var drawInterval;
var drawIntervalTimeout;
var buttons;
var dragBorderHrsMins=0, dragBorderMinsSecs=0;
function update() {
if (drawInterval) clearInterval(drawInterval);
if (drawIntervalTimeout) clearTimeout(drawIntervalTimeout);
if (all[current].start) {
drawIntervalTimeout = setTimeout(() => {drawInterval = setInterval(draw, 1000); draw();}, 1000 - (timerclk.getTime(all[current]) % 1000));
} else {
drawInterval = null;
drawIntervalTimeout = null;
}
draw();
drawButtons();
}
function play() {
if (all[current].start) { // running
all[current].timeAdd += Date.now() - all[current].start;
all[current].start = null;
update();
} else { // paused
all[current].start = Date.now();
update();
}
require("Storage").write("timerclk.timer.json",JSON.stringify(all));
timerclkCheckTimers();
}
function reset() {
all[current] = defaultElement.clone();
update();
require("Storage").write("timerclk.timer.json",JSON.stringify(all));
timerclkCheckTimers();
}
function remove() {
all.splice(current, 1);
if (current == all.length) current--;
if (all.length == 0) {
all.push(defaultElement.clone());
current++;
}
update();
require("Storage").write("timerclk.timer.json",JSON.stringify(all));
timerclkCheckTimers();
}
function edit(position, change) {
if (position == 1) all[current].time += change*1000;
else if (position == 2) all[current].time += change*60000;
else if (position == 3) all[current].time += change*3600000;
require("Storage").write("timerclk.timer.json",JSON.stringify(all));
timerclkCheckTimers();
}
var buttonsRunning = {
reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: reset, img: timerclk.reset_img, col:"#f50"},
play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:"#0ff"},
};
var buttonsNormal = {
reset: {pos:[0, height-settings.buttonHeight, width/2, height], callback: remove, img: timerclk.remove_img, col:buttonsRunning.reset.col},
play: {pos:[width/2, height-settings.buttonHeight, width, height], callback: play, img: timerclk.play_img, col:buttonsRunning.play.col},
};
buttons = buttonsNormal;
function drawButtons() {
if (all[current].start || all[current].timeAdd) {
buttons = buttonsRunning;
if (all[current].start) {
buttons.play.img = timerclk.pause_img;
} else {
buttons.play.img = timerclk.play_img;
}
} else {
buttons = buttonsNormal;
}
for (var button of buttons) {
g.setColor(button.col);
g.fillRect(button.pos[0], button.pos[1], button.pos[2], button.pos[3]);
g.setColor("#000");
// scale 24px images
let iw = settings.buttonHeight-10;
var scale = iw/24;
let ix = button.pos[0] + ((button.pos[2]-button.pos[0] - iw) /2);
let iy = button.pos[1] + ((button.pos[3]-button.pos[1] - iw) /2);
g.drawImage(button.img, ix, iy, {scale: scale});
}
}
function draw() {
var x = g.getWidth()/2;
var y = g.getHeight()/2;
g.reset();
var time = all[current].time - timerclk.getTime(all[current]);
g.clearRect(Bangle.appRect.x, Bangle.appRect.y, Bangle.appRect.x2, Bangle.appRect.y2-settings.buttonHeight);
g.setFontAlign(0,0).setFont(settings.indexFont, settings.indexFontSize);
g.drawString(current+1, x, Bangle.appRect.y + (g.stringMetrics("0").height/2));
g.setFontAlign(0,0).setFont(settings.font, settings.fontSize);
var timeStr = timerclk.formatTime(time, false, false, true);
g.drawString(timeStr,x,y);
var start = (width-g.stringMetrics(timeStr).width)/2;
timeStr = timeStr.split(":");
var markerPosChange = g.stringMetrics("__").width/2;
if (editIndex == 3) x = start + g.stringMetrics(timeStr[0]).width - markerPosChange;
else if (editIndex == 2) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]).width - markerPosChange;
else if (editIndex == 1) x = start + g.stringMetrics(timeStr[0]+":"+timeStr[1]+":"+timeStr[2]).width - markerPosChange;
else x = 0;
if (x) g.drawString("__", x, y);
dragBorderHrsMins = start+g.stringMetrics(timeStr[0]).width+g.stringMetrics(":").width/2;
dragBorderMinsSecs = start+g.stringMetrics(timeStr[0]+":"+timeStr[1]).width+g.stringMetrics(":").width/2;
}
if (all.length == 0) {
all.push(defaultElement.clone());
}
timerclk.registerControls(this);
Bangle.loadWidgets();
Bangle.drawWidgets();
update();

7
apps/timerclk/wid.js Normal file
View File

@ -0,0 +1,7 @@
WIDGETS["timerclk.alarm"]={area:"tl",width:0,draw:function() {
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
},reload:function() {
WIDGETS["timerclk.alarm"].width = (require('Storage').readJSON('timerclk.alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
}
};
WIDGETS["timerclk.alarm"].reload();

View File

@ -13,17 +13,36 @@
#
# If you do this, please do not attempt to commit your modified
# apps.json back into the main BangleApps repository!
#
# You can pass an optional filename to this script, and it will write
# to that instead, apps.local.json is used when opening the loader on localhost
outfile="${1:-apps.json}"
cd `dirname $0`/..
echo "[" > apps.json
echo "[" > "$outfile"
first=1
for app in apps/*/; do
echo "Processing $app...";
if [[ "$app" =~ ^apps/_example.* ]]; then
echo "Ignoring $app"
else
cat ${app}metadata.json >> apps.json
if [ $first -eq 1 ]; then
first=0;
else
echo "," >> "$outfile"
fi;
cat ${app}metadata.json >> "$outfile"
# echo ",\"$app\"," >> apps.json # DEBUG ONLY
echo "," >> apps.json
fi
done
echo "null]" >> apps.json
echo "]" >> "$outfile"
if [ -z "$1"]; then
# Running with no arguments: prevent accidental commit of modified apps.json.
# You can use `create_apps.json.sh apps.json` if you really want to both
# overwrite and still commit apps.json
git update-index --skip-worktree apps.json
echo "Told git to ignore modified apps.json."
# If you want to unignore it, use
# 'git update-index --no-skip-worktree apps.json'
fi

2
core

@ -1 +1 @@
Subproject commit 5023ee1228030130ba9f026d5dbe920f7527ee7d
Subproject commit 3093d78a5d752cbf03ea8f9a1a7c0b50b9c8123b

View File

@ -5,6 +5,11 @@ if (window.location.host=="banglejs.com") {
document.title += " [Development]";
document.getElementById("apploaderlinks").innerHTML =
'This is the development Bangle.js App Loader - you can also try the <a href="https://banglejs.com/apps/">Official Version</a> for stable apps.';
} else if (window.location.hostname==='localhost') {
document.title += " [Local]";
Const.APPS_JSON_FILE = "apps.local.json";
document.getElementById("apploaderlinks").innerHTML =
'This is your local Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.';
} else {
document.title += " [Unofficial]";
document.getElementById("apploaderlinks").innerHTML =

View File

@ -9,9 +9,17 @@
"scripts": {
"lint-apps": "eslint ./apps --ext .js",
"test": "node bin/sanitycheck.js && eslint ./apps --ext .js",
"update-local-apps": "./bin/create_apps_json.sh apps.local.json",
"local": "npm-watch & npx http-server -a localhost -c-1",
"start": "npx http-server -c-1"
},
"watch": {
"update-local-apps": "apps/*/metadata.json"
},
"dependencies": {
"acorn": "^7.2.0"
},
"devDpendencies": {
"npm-watch": "^0.11.0"
}
}