1
0
Fork 0
Jason Dekarske 2022-04-08 18:20:51 -07:00
commit a06068315c
100 changed files with 2138 additions and 446 deletions

2
apps/90sclk/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Fullscreen settings.

13
apps/90sclk/README.md Normal file
View File

@ -0,0 +1,13 @@
# 90s Clock
A watch face in 90s style:
![](screenshot_2.png)
Fullscreen mode can be enabled in the settings:
![](screenshot.png)
## Creator
- [David Peer](https://github.com/peerdavid)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgc8+fAgEgwAMDvPnz99BYdl2weHtu27ft2AGBiEcuEAhAPDg4jGgECIRMN23fthUNgP374vBAB3gAgc/gAXNjlx4EDxwJEpAjG/6IBjkBL4UAjVgBAJuCgPHBQMFEIkkyQjFhwEClgXBEYNBwkQJoibCBwNFBAUCEAVAQZAjC/8euPHDon//hKB//xEYMP//jBYP/+ARDNYM///+EYIgBj1B/8fCIUhEYQRB//FUIM/EZU4EYMkEYP/8VhEYUH/gRBWAUfI4MD+AjBoAsBwEH8EB/EDwE4HwYjCuEHWAOHgExEYKbBCIZNB8fAEYQHByE/EwPABAY+BgRHDBANyJQXHNwIjD8CSBj/+BwMSTwOOBYK2D/4CCNYZQB/iJBQwYjCCIcAgeBSoOAWYQjEVoIRCNAIjKAQKJBgAFC8ZoCWwJbDABMHGQPAAoMQB5EDx/4A4gqBZwIGCWwIABuBWC4EBZwPgv/AcwS/EAAcIU4IRBVQIRKEwIjBv0ARIUDCJIjD//x/ARK/5HC/+BCJkcI45uDgECUgQjCWAM4WwUBWYanEAA8cTARWBEYUC5RAHw1YgEOFQXADQPHIIkAhgICuARBh0A23blhHBagIKBsOGjNswhHDEYUUAoTUBhkxEYMwKwU503bvuwXILmCEYMYsumWYYjB85lDEYovBEYXm7fs25EBI4kYtOWNwIjD4+8NYsw4YjGz9/2hrEoOGjVBwE4NYdzNYSwBuEDEYcxaIUA8+atugGogjBiVgWAI"))

144
apps/90sclk/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/90sclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
apps/90sclk/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

18
apps/90sclk/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "90sclk",
"name": "90s Clock",
"version": "0.02",
"description": "A 90s style watch-face",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"90sclk.app.js","url":"app.js"},
{"name":"90sclk.img","url":"app-icon.js","evaluate":true},
{"name":"90sclk.settings.js","url":"settings.js"}
]
}

BIN
apps/90sclk/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

31
apps/90sclk/settings.js Normal file
View File

@ -0,0 +1,31 @@
(function(back) {
const SETTINGS_FILE = "90sclk.setting.json";
// initialize with default settings...
const storage = require('Storage')
let settings = {
fullscreen: false,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
function save() {
storage.write(SETTINGS_FILE, settings)
}
E.showMenu({
'': { 'title': '90s Clock' },
'< Back': back,
'Full Screen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();
},
}
});
})

View File

@ -14,3 +14,7 @@
0.13: Alarm widget state now updates when setting/resetting an alarm
0.14: Order of 'back' menu item
0.15: Fix hour/minute wrapping code for new menu system
0.16: Adding alarm library
0.17: Moving alarm internals to 'sched' library
0.18: Cope with >1 identical alarm at once (#1667)
0.19: Ensure rescheduled alarms that already fired have 'last' reset

6
apps/alarm/README.md Normal file
View File

@ -0,0 +1,6 @@
Default Alarm & Timer
======================
This allows you to add/modify any running timers.
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.

View File

@ -1,72 +0,0 @@
// Chances are boot0.js got run already and scheduled *another*
// 'load(alarm.js)' - so let's remove it first!
clearInterval();
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function showAlarm(alarm) {
var msg = formatTime(alarm.hr);
var buzzCount = 10;
if (alarm.msg)
msg += "\n"+alarm.msg;
Bangle.loadWidgets();
Bangle.drawWidgets();
E.showPrompt(msg,{
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
buttons : {/*LANG*/"Sleep":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;
if (sleep) {
if(alarm.ohr===undefined) alarm.ohr = alarm.hr;
alarm.hr += 10/60; // 10 minutes
} else {
alarm.last = (new Date()).getDate();
if (alarm.ohr!==undefined) {
alarm.hr = alarm.ohr;
delete alarm.ohr;
}
if (!alarm.rp) alarm.on = false;
}
require("Storage").write("alarm.json",JSON.stringify(alarms));
load();
});
function buzz() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
Bangle.buzz(100).then(()=>{
setTimeout(()=>{
Bangle.buzz(100).then(function() {
if (buzzCount--)
setTimeout(buzz, 3000);
else if(alarm.as) { // auto-snooze
buzzCount = 10;
setTimeout(buzz, 600000);
}
});
},100);
});
}
buzz();
}
// Check for alarms
var day = (new Date()).getDate();
var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early
var alarms = require("Storage").readJSON("alarm.json",1)||[];
var active = alarms.filter(a=>a.on&&(a.hr<hr)&&(a.last!=day));
if (active.length) {
// if there's an alarm, show it
active = active.sort((a,b)=>a.hr-b.hr);
showAlarm(active[0]);
} else {
// otherwise just go back to default app
setTimeout(load, 100);
}

View File

@ -1,36 +1,43 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var alarms = require("Storage").readJSON("alarm.json",1)||[];
/*alarms = [
{ on : true,
hr : 6.5, // hours + minutes/60
msg : "Eat chocolate",
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
rp : true, // repeat
as : false, // auto snooze
timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes
}
];*/
var alarms = require("sched").getAlarms();
// An array of alarm objects (see sched/README.md)
// time in ms -> { hrs, mins }
function decodeTime(t) {
t = 0|t; // sanitise
var hrs = 0|(t/3600000);
return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) };
}
// time in { hrs, mins } -> ms
function encodeTime(o) {
return o.hrs*3600000 + o.mins*60000;
}
function formatTime(t) {
var hrs = 0|t;
var mins = Math.round((t-hrs)*60);
return hrs+":"+("0"+mins).substr(-2);
var o = decodeTime(t);
return o.hrs+":"+("0"+o.mins).substr(-2);
}
function formatMins(t) {
mins = (0|t)%60;
hrs = 0|(t/60);
return hrs+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
function getCurrentTime() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
return (
time.getHours() * 3600000 +
time.getMinutes() * 60000 +
time.getSeconds() * 1000
);
}
function saveAndReload() {
require("sched").setAlarms(alarms);
require("sched").reload();
}
function showMainMenu() {
// Timer img "\0"+atob("DhKBAP////MDDAwwMGGBzgPwB4AeAPwHOBhgwMMzDez////w")
// Alarm img "\0"+atob("FBSBAABgA4YcMPDGP8Zn/mx/48//PP/zD/8A//AP/wD/8A//AP/wH/+D//w//8AAAADwAAYA")
const menu = {
'': { 'title': 'Alarm/Timer' },
/*LANG*/'< Back' : ()=>{load();},
@ -38,140 +45,161 @@ function showMainMenu() {
/*LANG*/'New Timer': ()=>editTimer(-1)
};
alarms.forEach((alarm,idx)=>{
var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
if (alarm.timer) {
txt = /*LANG*/"TIMER "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatMins(alarm.timer);
type = /*LANG*/"Timer";
txt = " "+formatTime(alarm.timer);
} else {
txt = /*LANG*/"ALARM "+(alarm.on?/*LANG*/"on ":/*LANG*/"off ")+formatTime(alarm.hr);
if (alarm.rp) txt += /*LANG*/" (repeat)";
type = /*LANG*/"Alarm";
txt = " "+formatTime(alarm.t);
}
menu[txt] = function() {
if (alarm.timer) editTimer(idx);
else editAlarm(idx);
if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
// rename duplicate alarms
if (menu[type+txt]) {
var n = 2;
while (menu[type+" "+n+txt]) n++;
txt = type+" "+n+txt;
} else txt = type+txt;
// add to menu
menu[txt] = {
value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
onchange : function() {
if (alarm.timer) editTimer(idx, alarm);
else editAlarm(idx, alarm);
}
};
});
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
return E.showMenu(menu);
}
function editAlarm(alarmIndex) {
function editDOW(dow, onchange) {
const menu = {
'': { 'title': /*LANG*/'Days of Week' },
'< Back' : () => onchange(dow)
};
for (var i = 0; i < 7; i++) (i => {
var dayOfWeek = require("locale").dow({ getDay: () => i });
menu[dayOfWeek] = {
value: !!(dow&(1<<i)),
format: v => v ? "Yes" : "No",
onchange: v => v ? dow |= 1<<i : dow &= ~(1<<i),
};
})(i);
E.showMenu(menu);
}
function editAlarm(alarmIndex, alarm) {
var newAlarm = alarmIndex<0;
var hrs = 12;
var mins = 0;
var en = true;
var repeat = true;
var as = false;
if (!newAlarm) {
var a = alarms[alarmIndex];
hrs = 0|a.hr;
mins = Math.round((a.hr-hrs)*60);
en = a.on;
repeat = a.rp;
as = a.as;
var a = {
t : 12*3600000, // 12 o clock default
on : true,
rp : false, // repeat not the default
as : false,
dow : 0b1111111,
last : 0,
vibrate : ".."
}
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a,alarm);
var t = decodeTime(a.t);
const menu = {
'': { 'title': /*LANG*/'Alarm' },
/*LANG*/'< Back' : showMainMenu,
'< Back' : () => showMainMenu(),
/*LANG*/'Hours': {
value: hrs, min : 0, max : 23, wrap : true,
onchange: v => hrs=v
value: t.hrs, min : 0, max : 23, wrap : true,
onchange: v => t.hrs=v
},
/*LANG*/'Minutes': {
value: mins, min : 0, max : 59, wrap : true,
onchange: v => mins=v
value: t.mins, min : 0, max : 59, wrap : true,
onchange: v => t.mins=v
},
/*LANG*/'Enabled': {
value: en,
value: a.on,
format: v=>v?"On":"Off",
onchange: v=>en=v
onchange: v=>a.on=v
},
/*LANG*/'Repeat': {
value: en,
value: a.rp,
format: v=>v?"Yes":"No",
onchange: v=>repeat=v
onchange: v=>a.rp=v
},
/*LANG*/'Days': {
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
onchange: () => editDOW(a.dow, d=>{a.dow=d;editAlarm(alarmIndex,a)})
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
/*LANG*/'Auto snooze': {
value: as,
value: a.as,
format: v=>v?"Yes":"No",
onchange: v=>as=v
onchange: v=>a.as=v
}
};
function getAlarm() {
var hr = hrs+(mins/60);
var day = 0;
// If alarm is for tomorrow not today (eg, in the past), set day
if (hr < getCurrentHr())
day = (new Date()).getDate();
// Save alarm
return {
on : en, hr : hr,
last : day, rp : repeat, as: as
};
}
menu[/*LANG*/"> Save"] = function() {
if (newAlarm) alarms.push(getAlarm());
else alarms[alarmIndex] = getAlarm();
require("Storage").write("alarm.json",JSON.stringify(alarms));
menu[/*LANG*/"Save"] = function() {
a.t = encodeTime(t);
a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
if (newAlarm) alarms.push(a);
else alarms[alarmIndex] = a;
saveAndReload();
showMainMenu();
};
if (!newAlarm) {
menu[/*LANG*/"> Delete"] = function() {
menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("alarm.json",JSON.stringify(alarms));
saveAndReload();
showMainMenu();
};
}
return E.showMenu(menu);
}
function editTimer(alarmIndex) {
function editTimer(alarmIndex, alarm) {
var newAlarm = alarmIndex<0;
var hrs = 0;
var mins = 5;
var en = true;
if (!newAlarm) {
var a = alarms[alarmIndex];
mins = (0|a.timer)%60;
hrs = 0|(a.timer/60);
en = a.on;
var a = {
timer : 5*60*1000, // 5 minutes
on : true,
rp : false,
as : false,
dow : 0b1111111,
last : 0,
vibrate : ".."
}
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
if (alarm) Object.assign(a,alarm);
var t = decodeTime(a.timer);
const menu = {
'': { 'title': /*LANG*/'Timer' },
'< Back' : () => showMainMenu(),
/*LANG*/'Hours': {
value: hrs, min : 0, max : 23, wrap : true,
onchange: v => hrs=v
value: t.hrs, min : 0, max : 23, wrap : true,
onchange: v => t.hrs=v
},
/*LANG*/'Minutes': {
value: mins, min : 0, max : 59, wrap : true,
onchange: v => mins=v
value: t.mins, min : 0, max : 59, wrap : true,
onchange: v => t.mins=v
},
/*LANG*/'Enabled': {
value: en,
format: v=>v?/*LANG*/"On":/*LANG*/"Off",
onchange: v=>en=v
}
value: a.on,
format: v=>v?"On":"Off",
onchange: v=>a.on=v
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
};
function getTimer() {
var d = new Date(Date.now() + ((hrs*60)+mins)*60000);
var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600);
// Save alarm
return {
on : en,
timer : (hrs*60)+mins,
hr : hr,
rp : false, as: false
};
}
menu["> Save"] = function() {
if (newAlarm) alarms.push(getTimer());
else alarms[alarmIndex] = getTimer();
require("Storage").write("alarm.json",JSON.stringify(alarms));
menu[/*LANG*/"Save"] = function() {
a.timer = encodeTime(t);
a.t = getCurrentTime() + a.timer;
a.last = 0;
if (newAlarm) alarms.push(a);
else alarms[alarmIndex] = a;
saveAndReload();
showMainMenu();
};
if (!newAlarm) {
menu["> Delete"] = function() {
menu[/*LANG*/"Delete"] = function() {
alarms.splice(alarmIndex,1);
require("Storage").write("alarm.json",JSON.stringify(alarms));
saveAndReload();
showMainMenu();
};
}

View File

@ -1,25 +0,0 @@
// check for alarms
(function() {
var alarms = require('Storage').readJSON('alarm.json',1)||[];
var time = new Date();
var active = alarms.filter(a=>a.on);
if (active.length) {
active = active.sort((a,b)=>(a.hr-b.hr)+(a.last-b.last)*24);
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
if (!require('Storage').read("alarm.js")) {
console.log("No alarm app!");
require('Storage').write('alarm.json',"[]");
} else {
var t = 3600000*(active[0].hr-hr);
if (active[0].last == time.getDate() || t < 0) t += 86400000;
if (t<1000) t=1000;
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("alarm.js");
},t);
}
}
})();

View File

@ -1,18 +1,17 @@
{
"id": "alarm",
"name": "Default Alarm & Timer",
"name": "Alarm & Timer",
"shortName": "Alarms",
"version": "0.15",
"description": "Set and respond to alarms and timers",
"version": "0.19",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm,widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"dependencies": {"scheduler":"type"},
"storage": [
{"name":"alarm.app.js","url":"app.js"},
{"name":"alarm.boot.js","url":"boot.js"},
{"name":"alarm.js","url":"alarm.js"},
{"name":"alarm.img","url":"app-icon.js","evaluate":true},
{"name":"alarm.wid.js","url":"widget.js"}
],
"data": [{"name":"alarm.json"}]
]
}

View File

@ -1,7 +1,8 @@
WIDGETS["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["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0;
// don't include library here as we're trying to use as little RAM as possible
WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0;
}
};
WIDGETS["alarm"].reload();

2
apps/altimeter/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Actually upload correct code

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///t9TmuV3+GJf4AN+ALVgf8BasP/4LVn//4ALUWgJUJBZUDBYJUIBZcP3/nKhEOt/WBZE5r+VKg0KgEVr9V3wLHqtaqt9sALElWAqoABt1QBZNeBYuq0ILCrVUBYulBYVWBYkCBYgABBZ8K1WVBYlABZegKQWqBQlVqALKqWoKQWpBYtWBZeqKRAAB1WABZZSHAANq0ALLKQ6qC1ALLKQ5UEAH4AG"))

30
apps/altimeter/app.js Normal file
View File

@ -0,0 +1,30 @@
Bangle.setBarometerPower(true, "app");
g.clear(1);
Bangle.loadWidgets();
Bangle.drawWidgets();
var zero = 0;
var R = Bangle.appRect;
var y = R.y + R.h/2;
var MEDIANLENGTH = 20;
var avr = [], median;
var value = 0;
Bangle.on('pressure', function(e) {
while (avr.length>MEDIANLENGTH) avr.pop();
avr.unshift(e.altitude);
median = avr.slice().sort();
g.reset().clearRect(0,y-30,g.getWidth()-10,y+30);
if (median.length>10) {
var mid = median.length>>1;
value = E.sum(median.slice(mid-4,mid+5)) / 9;
g.setFont("Vector",50).setFontAlign(0,0).drawString((value-zero).toFixed(1), g.getWidth()/2, y);
}
});
g.reset();
g.setFont("6x8").setFontAlign(0,0).drawString(/*LANG*/"ALTITUDE (m)", g.getWidth()/2, y-40);
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"ZERO", g.getWidth()-5, g.getHeight()/2);
setWatch(function() {
zero = value;
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});

BIN
apps/altimeter/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,12 @@
{ "id": "altimeter",
"name": "Altimeter",
"version":"0.02",
"description": "Simple altimeter that can display height changed using Bangle.js 2's built in pressure sensor.",
"icon": "app.png",
"tags": "tool,outdoors",
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"altimeter.app.js","url":"app.js"},
{"name":"altimeter.img","url":"app-icon.js","evaluate":true}
]
}

3
apps/bee/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: New app!
0.02: Fix bug with regenerating index, fix bug in word lookups
0.03: Improve word search performance

View File

@ -27,10 +27,7 @@ least once and yields an additional 7 points. Each game contains at least one pa
The game uses an internal dictionary consisting of a newline separated list of English words ('bee.words', using the '2of12inf' word list).
The dictionary is fairly large (~700kB of flash space) and thus requires appropriate space on the watch and will make installing the app somewhat
slow. Because of its size it cannot be compressed (heatshrink needs to hold the compressed/uncompressed data in memory).
In order to make checking the validity of a guessed word faster an index file ('bee_lindex.json') is installed with
the app that facilitates faster word lookups. This index file is specific to the dictionary file used. If one were to
replace the dictionary file with a different version (e.g. a different language) the index file has to be regenerated. The easiest
way to do so is to delete (via the Web IDE or the fileman app on the watch) the file 'bee_lindex.json' - it will be regenerated (and saved,
i.e. it only happens once) on app startup automatically, a process that takes roughly 30 seconds.
This file can be replaced with a custom dictionary, an ASCII file containing a newline-separated (single "\n", not DOS-style "\r\n") alphabetically
sorted (sorting is important for the word lookup algorithm) list of words.
![Screenshot](./bee_screenshot.png)

View File

@ -1,7 +1,7 @@
const S = require("Storage");
const words = S.read("bee.words");
var letters = [];
var letterIdx = [];
var centers = [];
@ -12,28 +12,17 @@ var score = 0;
var intervalID = -1;
function prepareLetterIdx () {
function biSearch(w, ws, start, end, count) {
"compile"
var li = [0];
if (S.read("bee_lindex.json")!==undefined) li = S.readJSON("bee_lindex.json"); // check for cached index
else {
for (var i=1; i<26; ++i) {
var prefix = String.fromCharCode(97+i%26);
console.log(prefix);
li.push(S.read('bee.words').indexOf("\n"+prefix, li[i-1])+1);
}
li.push(S.read('bee.words').length);
S.writeJSON("bee_lindex.json", li);
}
for (var i=0; i<26; ++i) letterIdx[i] = S.read("bee.words", li[i], li[i+1]-li[i]);
}
function findWord (w) {
"compile"
var ci = w.charCodeAt(0)-97;
var f = letterIdx[ci].indexOf(w);
if (f>=0 && letterIdx[ci][f+w.length]=="\n") return true;
return false;
if (start>end-w.legnth || count--<=0) return ws.substr(start, end-start).indexOf("\n"+w+"\n");
var mid = (end+start)>>1;
if (ws[mid-1]==="\n") --mid;
else while (mid<end && ws[mid]!=="\n") mid++;
var i = 0;
while (i<w.length && ws[mid+i+1]==w[i]) ++i;
if (i==w.length && ws[mid+i+1]==="\n") return mid+1;
if (i==w.length || w[i]<ws[mid+i+1]) return biSearch(w, ws, start, mid+1, count);
if (w[i]>ws[mid+i+1]) return biSearch(w, ws, mid+1, end, count);
}
function isPangram(w) {
@ -45,8 +34,9 @@ function isPangram(w) {
function checkWord (w) {
if (w.indexOf(String.fromCharCode(97+letters[0]))==-1) return false; // does it contain central letter?
if (foundWords.indexOf(w)>=0) return false; // already found
if (findWord(w)) {
if (biSearch(w, words, 0, words.length, 20)>-1) {
foundWords.push(w);
foundWords.sort();
if (w.length==4) score++;
else score += w.length;
if (isPangram(w)) score += 7;
@ -91,13 +81,12 @@ function pickLetters() {
var ltrs = "";
while (ltrs.length!==7) {
ltrs = [];
var j = Math.floor(26*Math.random());
var i = Math.floor((letterIdx[j].length-10)*Math.random());
while (letterIdx[j][i]!="\n" && i<letterIdx[j].length) ++i;
if (i<letterIdx[j].length-1) {
var i = Math.floor((words.length-10)*Math.random());
while (words[i]!="\n" && i<words.length) ++i;
if (i<words.length-1) {
++i;
while (letterIdx[j][i]!=="\n") {
var c = letterIdx[j][i];
while (words[i]!=="\n") {
var c = words[i];
if (ltrs.indexOf(c)===-1) ltrs += c;
++i;
}
@ -185,7 +174,6 @@ function showWordList() {
});
}
prepareLetterIdx();
pickLetters();
drawHive();
drawScore();

View File

@ -1 +0,0 @@
[0,41048,80445,152390,198606,228714,257919,279071,303726,337982,343582,348026,367246,404452,419780,438696,496250,499697,544600,624304,659085,680996,691270,708186,708341,709916,710883]

View File

@ -2,7 +2,7 @@
"name": "Bee",
"shortName":"Bee",
"icon": "app.png",
"version":"0.01",
"version":"0.03",
"description": "Spelling bee",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
@ -10,7 +10,6 @@
"storage": [
{"name":"bee.app.js","url":"bee.app.js"},
{"name":"bee.words","url":"bee_words_2of12"},
{"name":"bee_lindex.json","url":"bee_lindex.json"},
{"name":"bee.img","url":"app-icon.js","evaluate":true}
]
}

5
apps/bwclk/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.01: New App.
0.02: Use build in function for steps and other improvements.
0.03: Adapt colors based on the theme of the user.
0.04: Steps can be hidden now such that the time is even larger.
0.05: Included icons for information.

16
apps/bwclk/README.md Normal file
View File

@ -0,0 +1,16 @@
# Black & White clock
![](screenshot.png)
## Features
- Fullscreen on/off
- The design is adapted to the theme of your bangle.
- Tab left/right of screen to show steps, temperature etc.
- Enable / disable lock icon in the settings.
- If the "sched" app is installed tab top / bottom of the screen to set the timer.
## Thanks to
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
## Creator
- [David Peer](https://github.com/peerdavid)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgIcah0EgEB/H8iFsAoOY4kMBYMDhmGgXkAoUGiWkAoQQBoAFCjgnCAoM4hgFDuEI+wpC8EKyg1C/0eAoMAsEAiQvBAAeAApQAB/4Ao+P4v/wn0P8Pgn/wnkH4Pjv/j/nn9PH//n/nj/IFF4F88AXBAoM88EcAoPHj//jlDAoOf/+Y+YFHjnnjAjBEIIjD+BHDO9IALA=="))

412
apps/bwclk/app.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/bwclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

18
apps/bwclk/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "bwclk",
"name": "BlackWhite Clock",
"version": "0.05",
"description": "Black and white clock.",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"bwclk.app.js","url":"app.js"},
{"name":"bwclk.img","url":"app-icon.js","evaluate":true},
{"name":"bwclk.settings.js","url":"settings.js"}
]
}

BIN
apps/bwclk/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
apps/bwclk/screenshot_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

40
apps/bwclk/settings.js Normal file
View File

@ -0,0 +1,40 @@
(function(back) {
const SETTINGS_FILE = "bwclk.setting.json";
// initialize with default settings...
const storage = require('Storage')
let settings = {
fullscreen: false,
showLock: true,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
function save() {
storage.write(SETTINGS_FILE, settings)
}
E.showMenu({
'': { 'title': 'BlackWhite Clock' },
'< Back': back,
'Fullscreen': {
value: settings.fullscreen,
format: () => (settings.fullscreen ? 'Yes' : 'No'),
onchange: () => {
settings.fullscreen = !settings.fullscreen;
save();
},
},
'Show Lock': {
value: settings.showLock,
format: () => (settings.showLock ? 'Yes' : 'No'),
onchange: () => {
settings.showLock = !settings.showLock;
save();
},
}
});
})

View File

@ -3,3 +3,4 @@
0.03: Eliminate flickering
0.04: Fix for Bangle.js 2 and themes
0.05: Fix bearing not clearing correctly (visible in single or double digit bearings)
0.06: Add button for force compass calibration

29
apps/compass/README.md Normal file
View File

@ -0,0 +1,29 @@
# Compass
This app uses Bangle.js's built-in magnetometer as a compass.
## Usage
Hold your Bangle.js **face up** (so the display is parallel to the ground),
and the red arrow will point north, with the heading in degrees printed at
the top of the screen.
This compass app does not include tilt compensation - so much like a real
compass you should always keep it face up when taking a reading.
The first time you run the compass after your Bangle has booted (or if
you move to an area with a substantially different magnetic field) you will
need to recalibrate your compass (even if a heading is shown).
## Calibration
Press the button next to the `RESET` label on the screen. The North/South marker
will disappear and a message will appear asking you to rotate the watch 360 degrees.
* Hold the watch face up, so the display is parallel to the ground
* Rotate it around slowly, all 360 degrees (with the display still parallel to the ground)
* The `Uncalibrated` message will disappear before you have finished rotating the full 360 degrees - but you should still complete the full rotation in order for the compass to work properly.
Once you've rotated the full 360 degrees your compass should now work fine,
and calibration is stored between runs of the app. However if you go near
to a strong magnet you may still need to recalibrate.

View File

@ -38,7 +38,7 @@ Bangle.on('mag', function(m) {
if (!wasUncalibrated) {
g.clearRect(0,24,W,48);
g.setFontAlign(0,-1).setFont("6x8");
g.drawString("Uncalibrated\nturn 360° around",M,24+4);
g.drawString(/*LANG*/"Uncalibrated\nturn 360° around",M,24+4);
wasUncalibrated = true;
}
} else {
@ -64,7 +64,12 @@ Bangle.on('mag', function(m) {
oldHeading = m.heading;
});
g.clear();
g.clear(1);
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"RESET", g.getWidth()-5, g.getHeight()/2);
setWatch(function() {
Bangle.resetCompass();
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.setCompassPower(1);

View File

@ -1,12 +1,13 @@
{
"id": "compass",
"name": "Compass",
"version": "0.05",
"version": "0.06",
"description": "Simple compass that points North",
"icon": "compass.png",
"screenshots": [{"url":"screenshot_compass.png"}],
"tags": "tool,outdoors",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"compass.app.js","url":"compass.js"},
{"name":"compass.img","url":"compass-icon.js","evaluate":true}

1
apps/fuzzyw/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: First release

26
apps/fuzzyw/README.md Normal file
View File

@ -0,0 +1,26 @@
# Fuzzy Text Clock
An imprecise clock for when you're not in a rush.
This clock is a remake of one of my favourite Pebble watchfaces, Fuzzy Text International. I use this watch for weekends and holidays, when 'within 5 minutes of the actual time' is close enough!
By default it will use the language set on the watch, go to settings to pick:
* en_GB - English
* en_US - American
* es_ES - Spanish
* fr_FR - French
* no_NO - Norwegian
* sv_SE - Swedish
* de_DE - German
Most translations are taken from the original Fuzzy Text International code.
## TODO
* Bold hour word (as the pebble version has)
* Animation when changing time?
## References
Based on Pebble app Fuzzy Text International: https://github.com/hallettj/Fuzzy-Text-International
![](fuzzyw-light.png)
![](fuzzyw-dark.png)

View File

@ -0,0 +1,186 @@
{
"en_GB":{
"hours":[
"midnight", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine", "ten", "eleven",
"twelve", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine", "ten", "eleven"
],
"minutes":[
"*$1 o'clock",
"five past *$1",
"ten past *$1",
"quarter past *$1",
"twenty past *$1",
"twenty five past *$1",
"half past *$1",
"twenty five to *$2",
"twenty to *$2",
"quarter to *$2",
"ten to *$2",
"five to *$2"
],
"text_scale":3.5
},
"en_US":{
"hours":[
"midnight", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine", "ten", "eleven",
"twelve", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine", "ten", "eleven"
],
"minutes":[
"*$1 o'clock",
"five after *$1",
"ten after *$1",
"quarter after *$1",
"twenty after *$1",
"twenty five after *$1",
"half past *$1",
"twenty five to *$2",
"twenty to *$2",
"quarter to *$2",
"ten to *$2",
"five to *$2"
],
"text_scale":3.5
},
"es_ES":{
"hours":[
"doce", "una", "dos", "tres", "cuatro", "cinco",
"seis", "siete", "ocho", "nueve", "diez", "once",
"doce", "una", "dos", "tres", "cuatro", "cinco",
"seis", "siete", "ocho", "nueve", "diez", "once"
],
"minutes":[
"*$1 en punto",
"*$1 y cinco",
"*$1 y diez",
"*$1 y cuarto",
"*$1 y veinte",
"*$1 y veinti- cinco",
"*$1 y media",
"*$2 menos veinti- cinco",
"*$2 menos veinte",
"*$2 menos cuarto",
"*$2 menos diez",
"*$2 menos cinco"
],
"text_scale":3.5
},
"fr_FR":{
"hours":[
"douze", "une", "deux", "trois", "quatre", "cinq",
"six", "sept", "huit", "neuf", "dix", "onze",
"douze", "une", "deux", "trois", "quatre", "cinq",
"six", "sept", "huit", "neuf", "dix", "onze"
],
"minutes":[
"*$1 heures",
"*$1 heures cinq",
"*$1 heures dix",
"*$1 heures et quart",
"*$1 heures vingt",
"*$1 heures vingt- cinq",
"*$1 heures et demie",
"*$2 moins vingt- cinq",
"*$2 heures moins vingt",
"*$2 moins le quart",
"*$2 heures moins dix",
"*$2 heures moins cinq"
],
"text_scale":3.5
},
"no_NB":{
"hours":[
"tolv", "ett", "to", "tre", "fire", "fem",
"seks", "sju", "åtte", "ni", "ti", "elleve",
"tolv", "ett", "to", "tre", "fire", "fem",
"seks", "sju", "åtte", "ni", "ti", "elleve"
],
"minutes":[
"klokka er *$1",
"fem over *$1",
"ti over *$1",
"kvart over *$1",
"ti på halv *$2",
"fem på halv *$2",
"halv *$2",
"fem over halv *$2",
"ti over halv *$2",
"kvart på *$2",
"ti på *$2",
"fem på *$2"
],
"text_scale":3.5
},
"nn_NO":{
"hours":[
"tolv", "eitt", "to", "tre", "fire", "fem",
"seks", "sju", "åtte", "ni", "ti", "elleve",
"tolv", "eitt", "to", "tre", "fire", "fem",
"seks", "sju", "åtte", "ni", "ti", "elleve"
],
"minutes":[
"klokka er *$1",
"fem over *$1",
"ti over *$1",
"kvart over *$1",
"ti på halv *$2",
"fem på halv *$2",
"halv *$2",
"fem over halv *$2",
"ti over halv *$2",
"kvart på *$2",
"ti på *$2",
"fem på *$2"
],
"text_scale":3.5
},
"sv_SE":{
"hours":[
"tolv", "ett", "två", "tre", "fyra", "fem",
"sex", "sju", "åtta", "nio", "tio", "elva",
"tolv", "ett", "två", "tre", "fyra", "fem",
"sex", "sju", "åtta", "nio", "tio", "elva"
],
"minutes":[
"*$1",
"fem över *$1",
"tio över *$1",
"kvart över *$1",
"tjugo över *$1",
"fem i halv *$2",
"halv *$2",
"fem över halv *$2",
"tjugo i *$2",
"kvart i *$2",
"tio i *$2",
"fem i *$2"
],
"text_scale":3.5
},
"de_DE":{
"hours":[
"zwölf", "eins", "zwei", "drei", "vier", "fünf",
"sechs", "sieben", "acht", "neun", "zehn", "elf",
"zwölf", "eins", "zwei", "drei", "vier", "fünf",
"sechs", "sieben", "acht", "neun", "zehn", "elf"
],
"minutes":[
"*$1 uhr",
"fünf nach *$1",
"zehn nach *$1",
"viertel nach *$1",
"zwanzig nach *$1",
"fünf for halb *$2",
"halb *$2",
"fünf nach halb *$2",
"zwanzig vor *$2",
"viertel vor *$2",
"zehn vor *$2",
"fünf vor *$2"
],
"text_scale":3.5
}
}

BIN
apps/fuzzyw/fuzzyw-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

75
apps/fuzzyw/fuzzyw.app.js Normal file
View File

@ -0,0 +1,75 @@
// adapted from https://github.com/hallettj/Fuzzy-Text-International/
const fuzzy_strings = require("Storage").readJSON("fuzzy_strings.json");
const SETTINGS_FILE = "fuzzyw.settings.json";
let settings = require("Storage").readJSON(SETTINGS_FILE,1)|| {'language': 'System', 'alignment':'Centre'};
if (settings.language == 'System') {
settings.language = require('locale').name;
}
let fuzzy_string = fuzzy_strings[settings.language];
let timeout = 2.5*60;
let drawTimeout;
function queueDraw(seconds) {
let millisecs = seconds * 1000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, millisecs - (Date.now() % millisecs));
}
const h = g.getHeight();
const w = g.getWidth();
let align_mode = 0;
let align_pos = w/2;
if (settings.alignment =='Left') {
align_mode = -1;
align_pos = 0;
} else if (settings.alignment == 'Right') {
align_mode = 1;
align_pos = w;
}
function getTimeString(date) {
let segment = Math.round((date.getMinutes()*60 + date.getSeconds() + 1)/300);
let hour = date.getHours() + Math.floor(segment/12);
f_string = fuzzy_string.minutes[segment % 12];
if (f_string.includes('$1')) {
f_string = f_string.replace('$1', fuzzy_string.hours[(hour) % 24]);
} else {
f_string = f_string.replace('$2', fuzzy_string.hours[(hour + 1) % 24]);
}
return f_string;
}
function draw() {
let time_string = getTimeString(new Date()).replace('*', '');
// print(time_string);
g.setFont('Vector', (h-24*2)/fuzzy_string.text_scale);
g.setFontAlign(align_mode, 0);
g.clearRect(0, 24, w, h-24);
g.setColor(g.theme.fg);
g.drawString(g.wrapString(time_string, w).join("\n"), align_pos, h/2);
queueDraw(timeout);
}
g.clear();
draw();
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
Bangle.setUI('clock');
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgP/ABX8oYFD+AFE8AFE8IXE8YFKwFCj08h4FBocenEHCIPDjk4CoIFBhlwAoeMuIFEuBSBAoOI+AFD4HxGoQFB+AFD4P4uYFC8P4gYFD/w7BAFEfApfEj+B/Ecg/Ah8A+EMg/Dw0YseHj/Dw/8sfHAoPH/lhDoIFBwFwj4FB40AvkPAoU8v4dCAoIdDw04FIMP4EOgFwh47Bj8EvEfw/DJwgFXABY"))

BIN
apps/fuzzyw/fuzzyw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

View File

@ -0,0 +1,46 @@
(function(back) {
const SETTINGS_FILE = "fuzzyw.settings.json";
var align_options = ['Left','Centre','Right'];
var language_options = ['System', 'en_GB', 'en_US', 'es_ES', 'fr_FR', 'no_NO', 'sv_SE', 'de_DE'];
// initialize with default settings...
let s = {'language': 'System', 'alignment': 'Centre'};
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
const storage = require('Storage')
let settings = storage.readJSON(SETTINGS_FILE, 1) || s;
const saved = settings || {}
for (const key in saved) {
s[key] = saved[key]
}
function save() {
settings = s
storage.write(SETTINGS_FILE, settings)
}
E.showMenu({
'': { 'title': 'Fuzzy Text Clock' },
'< Back': back,
'Language': {
value: 0 | language_options.indexOf(s.language),
min: 0, max: language_options.length - 1,
format: v => language_options[v],
onchange: v => {
s.language = language_options[v];
save();
}
},
'Alignment': {
value: 0 | align_options.indexOf(s.alignment),
min: 0, max: align_options.length - 1,
format: v => align_options[v],
onchange: v => {
s.alignment = align_options[v];
save();
}
},
});
})

20
apps/fuzzyw/metadata.json Normal file
View File

@ -0,0 +1,20 @@
{
"id":"fuzzyw",
"name":"Fuzzy Text Clock",
"shortName": "Fuzzy Text",
"version": "0.01",
"description": "An imprecise clock for when you're not in a rush",
"readme": "README.md",
"icon":"fuzzyw.png",
"screenshots": [{"url":"fuzzyw-light.png"},{"url":"fuzzyw-dark.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"fuzzyw.app.js","url":"fuzzyw.app.js"},
{"name":"fuzzyw.settings.js","url":"fuzzyw.settings.js"},
{"name":"fuzzyw.img","url":"fuzzyw.icon.js","evaluate":true},
{"name":"fuzzy_strings.json","url":"fuzzy_strings.json"}
]
}

View File

@ -17,3 +17,4 @@
0.17: Settings for mph/kph and other minor improvements.
0.18: Fullscreen mode can now be enabled or disabled in the settings.
0.19: Alarms can not go bigger than 100.
0.20: Use alarm for alarm functionality instead of own implementation.

View File

@ -3,7 +3,8 @@
A simple LCARS inspired clock.
Note: To display the steps, the wpedom app is required. To show weather data
such as temperature, humidity or window you BangleJS must be connected
with Gadgetbride and the weather app must be installed.
with Gadgetbride and the weather app must be installed. To use the timer
the "sched" app must be installed on your device.
## Control
* Tap left / right to change between screens.
@ -15,7 +16,7 @@ with Gadgetbride and the weather app must be installed.
* Tab on left/right to switch between different screens.
* Cusomizable data that is shown on screen 1 (steps, weather etc.)
* Shows random and real images of planets.
* Tap on top/bottom of screen 1 to activate an alarm.
* Tap on top/bottom of screen 1 to activate an alarm. Depends on widtmr.
* The lower orange line indicates the battery level.
* Display graphs (day or month) for steps + hrm on the second screen.
@ -36,8 +37,9 @@ Access different screens via tap on the left/ right side of the screen
![](screenshot_1.png)
![](screenshot_2.png)
## Creator
- [David Peer](https://github.com/peerdavid)
## Contributors
- [David Peer](https://github.com/peerdavid).
- [Adam Schmalhofer](https://github.com/adamschmalhofer).
- [Jon Warrington](https://github.com/BartokW).
- [Adam Schmalhofer](https://github.com/adamschmalhofer)
- [Jon Warrington](https://github.com/BartokW)

View File

@ -1,6 +1,7 @@
const TIMER_IDX = "lcars";
const SETTINGS_FILE = "lcars.setting.json";
const locale = require('locale');
const storage = require('Storage')
const storage = require('Storage');
let settings = {
alarm: -1,
dataRow1: "Steps",
@ -124,11 +125,16 @@ Graphics.prototype.setFontAntonioLarge = function(scale) {
*/
var drawTimeout;
function queueDraw() {
// Faster updates during alarm to ensure that it is
// shown correctly...
var timeout = isAlarmEnabled() ? 10000 : 60000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}, timeout - (Date.now() % timeout));
}
/**
@ -238,6 +244,7 @@ function drawInfo(){
return;
}
g.setFontAlign(-1, -1, 0);
g.setFontAntonioMedium();
g.setColor(cOrange);
g.clearRect(120, 10, g.getWidth(), 75);
@ -480,9 +487,6 @@ function draw(){
// Queue draw first to ensure that its called in one minute again.
queueDraw();
// First handle alarm to show this correctly afterwards
handleAlarm();
// Next draw the watch face
g.reset();
g.clearRect(0, 0, g.getWidth(), g.getHeight());
@ -561,43 +565,57 @@ function getWeather(){
/*
* Handle alarm
*/
function getCurrentTimeInMinutes(){
return Math.floor(Date.now() / (1000*60));
}
function isAlarmEnabled(){
return settings.alarm >= 0;
try{
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
if(alarmObj===undefined || !alarmObj.on){
return false;
}
return true;
} catch(ex){ }
return false;
}
function getAlarmMinutes(){
var currentTime = getCurrentTimeInMinutes();
return settings.alarm - currentTime;
if(!isAlarmEnabled()){
return -1;
}
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
function handleAlarm(){
if(!isAlarmEnabled()){
return;
}
function increaseAlarm(){
try{
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, {
timer : (minutes+5)*60*1000,
});
alarm.reload();
} catch(ex){ }
}
if(getAlarmMinutes() > 0){
return;
}
function decreaseAlarm(){
try{
var minutes = getAlarmMinutes();
minutes -= 5;
// Alarm
var t = 300;
Bangle.buzz(t, 1)
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
.then(() => {
// Update alarm state to disabled
settings.alarm = -1;
storage.writeJSON(SETTINGS_FILE, settings);
});
var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, undefined);
if(minutes > 0){
alarm.setAlarm(TIMER_IDX, {
timer : minutes*60*1000,
});
}
alarm.reload();
} catch(ex){ }
}
@ -625,27 +643,6 @@ Bangle.on('charging',function(charging) {
});
function increaseAlarm(){
if(isAlarmEnabled() && getAlarmMinutes() < 95){
settings.alarm += 5;
} else {
settings.alarm = getCurrentTimeInMinutes() + 5;
}
storage.writeJSON(SETTINGS_FILE, settings);
}
function decreaseAlarm(){
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
settings.alarm -= 5;
} else {
settings.alarm = -1;
}
storage.writeJSON(SETTINGS_FILE, settings);
}
function feedback(){
Bangle.buzz(40, 0.3);
}

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.19",
"version":"0.20",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Add the option to enable touching the widget only on clock and settings.
0.03: Settings page now uses built-in min/max/wrap (fix #1607)
0.04: Add masking widget input to other apps (using espruino/Espruino#2151), add a oversize option to increase the touch area.

View File

@ -1,8 +1,11 @@
# Light Switch Widget
Whis this widget I wanted to create a solution to quickly en-/disable the LCD backlight and even change the brightness.
With this widget I wanted to create a solution to quickly en-/disable the LCD backlight and even change the brightness.
In addition it shows the lock status with the option to personalize the lock icon with a tiny image.
All touch and drag inputs related to this widget are cached/masked to prevent actions in the active app.
(See [espruino/Espruino#2151](https://github.com/espruino/Espruino/issues/2151) for more information.)
---
### Control
---
@ -39,6 +42,9 @@ In addition it shows the lock status with the option to personalize the lock ico
* _clk+launch_ -> on all apps of the types _clock_ and _launch_
* _except apps_ -> on all apps of the types _clock_ and _launch_ and in the settings
* _always on_ -> always enabled when the widget is displayed
* __Oversize__
_0px_ / _1px_ / _..._ / __20px__ / _..._ / _50px_
To make it easier to hit the widget, this value extends the touch area of the widget in all directions.
* __Drag Delay__
_off_ / _50ms_ / _100ms_ / _..._ / __500ms__ / _..._ / _1000ms_
Change the maximum delay between first touch and re-touch/drag to change the brightness or disable changing the brightness completely.
@ -85,8 +91,6 @@ This images are stored in a seperate file _(lightswitch.images.json)_.
### Worth Mentioning
---
#### To do list
* Catch the touch and draw input related to this widget to prevent actions in the active app.
_(For now I have no idea how to achieve this, help is appreciated)_
* Manage images for the lock icon through a _Customize and Upload App_ page.
#### Requests, Bugs and Feedback

View File

@ -2,7 +2,7 @@
"id": "lightswitch",
"name": "Light Switch Widget",
"shortName": "Light Switch",
"version": "0.03",
"version": "0.04",
"description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.",
"icon": "images/app.png",
"screenshots": [

View File

@ -6,7 +6,8 @@
var settings = Object.assign({
colors: "011",
image: "default",
touchOn: "clock,launch",
touchOn: "always",
oversize: 20,
dragDelay: 500,
minValue: 0.1,
unlockSide: "",
@ -45,7 +46,7 @@
return {
value: entry.value.indexOf(settings[key]),
min : 0,
max : entry.value.length-1,
max : entry.value.length - 1,
wrap : true,
format: v => entry.title ? entry.title[v] : entry.value[v],
onchange: function(v) {
@ -57,11 +58,11 @@
// return entry for numerical value
return {
value: settings[key] * entry.factor,
step: entry.step,
format: v => v > 0 ? v + entry.unit : "off",
min : entry.min,
max : entry.max,
step: entry.step,
wrap : true,
format: v => v > 0 ? v + entry.unit : "off",
onchange: function(v) {
writeSetting(key, v / entry.factor, entry.drawWidgets);
},
@ -96,6 +97,14 @@
value: ["", "clock", "clock,setting.app.js", "clock,launch", "clock,setting.app.js,launch", "always"],
drawWidgets: true
},
oversize: {
factor: 1,
unit: "px",
min: 0,
max: 50,
step: 1,
drawWidgets: true
},
dragDelay: {
factor: 1,
unit: "ms",
@ -142,6 +151,7 @@
"Image": getEntry("image"),
"-- Control": 0,
"Touch": getEntry("touchOn"),
"Oversize": getEntry("oversize"),
"Drag Delay": getEntry("dragDelay"),
"Min Value": getEntry("minValue"),
"-- Unlock": 0,

View File

@ -10,8 +10,8 @@
"101" -> magenta
* image: string //
"default" ->
"random" ->
"default" -> image nearest to the default lock
"random" -> a random image from all available
* touchOn: string // select when widget touch is active
"" -> only on default clock
@ -19,6 +19,9 @@
"clock,launch" -> on all clocks and lanchers (default)
"always" -> always
* oversize: int // extends the touch area of the widget in px in all directions
0 to 50, 20 as default
* dragDelay: int // drag listener reset time in ms
// time until a drag is needed to activate backlight changing mode
0 -> disabled
@ -59,6 +62,7 @@
"colors": "011",
"image": "default",
"touchOn": "clock,launch",
"oversize": 20,
"dragDelay": 500,
"minValue": 0.1,
"unlockSide": "",

View File

@ -3,7 +3,8 @@
var settings = Object.assign({
colors: "011",
image: "default",
touchOn: "clock,launch",
touchOn: "always",
oversize: 20,
dragDelay: 500,
minValue: 0.1,
unlockSide: "",
@ -162,6 +163,11 @@
// change brigthness value, skip write to storage while still touching
w.changeValue(value, event.b);
// masks this drag event by messing up the event handler
// see https://github.com/espruino/Espruino/issues/2151
Bangle.removeListener("drag", w.dragListener);
Bangle["#ondrag"] = [w.dragListener].concat(Bangle["#ondrag"]);
// on touch release remove drag listener and reset drag status to indicate stopped drag action
if (!event.b) {
Bangle.removeListener("drag", w.dragListener);
@ -184,14 +190,14 @@
if (w.dragStatus === "off") {
// check if inside widget area
if (!(!w || cursor.x < w.x || cursor.x > w.x + w.width ||
cursor.y < w.y || cursor.y > w.y + 23)) {
if (!(!w || cursor.x < w.x - w.oversize || cursor.x > w.x + w.width + w.oversize ||
cursor.y < w.y - w.oversize || cursor.y > w.y + 23 + w.oversize)) {
// first touch feedback
Bangle.buzz(25);
// check if drag is disabled
if (w.dragDelay) {
// add drag listener
Bangle.on("drag", w.dragListener);
// add drag listener at first position
Bangle["#ondrag"] = [w.dragListener].concat(Bangle["#ondrag"]);
// set drag timeout
w.dragStatus = setTimeout((w) => {
// remove drag listener
@ -204,6 +210,10 @@
}
// switch backlight
w.changeValue();
// masks this touch event by messing up the event handler
// see https://github.com/espruino/Espruino/issues/2151
Bangle.removeListener("touch", w.touchListener);
Bangle["#ontouch"] = [w.touchListener].concat(Bangle["#ontouch"]);
}
}
@ -236,11 +246,11 @@
// add lock listener
Bangle.on("lock", w.draw);
// add touch listener to control the light depending on settings
// add touch listener to control the light depending on settings at first position
if (w.touchOn === "always" || !global.__FILE__ ||
w.touchOn.includes(__FILE__) ||
w.touchOn.includes(require("Storage").readJSON(__FILE__.replace("app.js", "info")).type))
Bangle.on("touch", w.touchListener);
Bangle["#ontouch"] = [w.touchListener].concat(Bangle["#ontouch"]);
// add tap listener to unlock and/or flash backlight
if (w.unlockSide || w.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);

View File

@ -16,6 +16,7 @@
<p>Then click <button id="upload" class="btn btn-primary">Upload</button></p>
<script src="../../core/lib/customize.js"></script>
<script src="../../core/js/utils.js"></script>
<script src="locales.js" charset="utf-8"></script>
<script>
@ -59,7 +60,7 @@ exports = { name : "en_GB", currencySym:"£",
/*else if (chCode<256) // it's non-ascii, but <256 - just escape it
n = chCode;*/
else {
if (charFallbacks[ch]) return charFallbacks[ch];
if (CODEPAGE_CONVERSIONS[ch]) return CODEPAGE_CONVERSIONS[ch];
console.error(`Locale ${lang}: Character ${ch} (${chCode}) is not in Code Page ${codePage.name}`);
return undefined;
}

View File

@ -34,33 +34,8 @@ const codePages = {
`.replace(/[ \n]/g,"")
}
};
/* When it's not in the codepage, try and use
these conversions */
const charFallbacks = {
"ą":"a",
"ā":"a",
"č":"c",
"ć":"c",
"ě":"e",
"ę":"e",
"ē":"e",
"ģ":"g",
"i":"ī",
"ķ":"k",
"ļ":"l",
"ł":"l",
"ń":"n",
"ņ":"n",
"ő":"o",
"ó":"o",
"ř":"r",
"ś":"s",
"š":"s",
"ū":"u",
"ż":"z",
"ź":"z",
"ž":"z",
};
// charFallbacks is now in core/js/utils.js as CODEPAGE_CONVERSIONS
/*
timePattern / datePattern:
@ -80,7 +55,7 @@ timePattern / datePattern:
%S second (00..60)
%p locale's equivalent of either AM or PM; blank if not known
%P like %p, but lower case
*/
*/
var locales = {
"en_GB": { // this is default
@ -154,7 +129,7 @@ var locales = {
month: "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
abday: "So,Mo,Di,Mi,Do,Fr,Sa",
day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
"< Back": "< Zurück", "Delete": "Löschen", "Mark Unread": "Als ungelesen markieren" }
},
"en_US": {
@ -271,7 +246,7 @@ var locales = {
thousands_sep: ".",
currency_symbol: "kr",
int_curr_symbol: "SKR",
speed: "kmh",
speed: "km/tim",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: { 0: "fm", 1: "em" },
@ -289,7 +264,7 @@ var locales = {
thousands_sep: ".",
currency_symbol: "kr",
int_curr_symbol: "SKR",
speed: 'kmh',
speed: 'km/h',
distance: { "0": "m", "1": "km" },
temperature: '°C',
ampm: { 0: "", 1: "" },
@ -353,7 +328,7 @@ var locales = {
month: "Jänner,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
abday: "So,Mo,Di,Mi,Do,Fr,Sa",
day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
"< Back": "< Zurück", "Delete": "Löschen", "Mark Unread": "Als ungelesen markieren" }
},
"en_IL": {
@ -475,7 +450,7 @@ var locales = {
distance: { "0": "m", "1": "km" },
temperature: '°C',
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
@ -493,7 +468,7 @@ var locales = {
distance: { "0": "m", "1": "km" },
temperature: '°C',
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
timePattern: { 0: "%HH:%MM.%SS ", 1: "%HH:%MM" }, // 17:00.00 // 17:00
datePattern: { 0: "%d %b %Y", "1": "%d/%m/%Y" }, // 1 marzo 2020 // 01/03/2020
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
@ -572,7 +547,7 @@ var locales = {
abday: "dg,dl,dm,dc,dj,dv,ds",
day: "dimenge,diluns,dimars,dimècres,dijòus,divendres,dissabte",
trans: { yes: "òc", Yes: "Òc", no: "non", No: "Non", ok: "ok", on: "on", off: "off" }
},
},
"pt_BR": {
lang: "pt_BR",
decimal_point: ",",
@ -591,7 +566,7 @@ var locales = {
day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado",
trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "certo", on: "ligado", off: "desligado" }
},
"cs_CZ": { // THIS NEVER WORKED PROPERLY - many chars are not in the ISO8859-1 codepage and we use charFallbacks
"cs_CZ": { // THIS NEVER WORKED PROPERLY - many chars are not in the ISO8859-1 codepage and we use CODEPAGE_CONVERSIONS
lang: "cs_CZ",
decimal_point: ",",
thousands_sep: " ",
@ -681,6 +656,42 @@ var locales = {
day: "Pirmdiena,Otrdiena,Trešdiena,Ceturtdiena,Piektdiena,Sestdiena,Svētdiena",
trans: { yes: "jā", Yes: "Jā", no: "nē", No: "Nē", ok: "labi", on: "Ieslēgt", off: "Izslēgt", "< Back": "< Atpakaļ" }
},
"nn_NO": { // Using charfallbacks
lang: "nn_NO",
decimal_point: ",",
thousands_sep: " ",
currency_symbol: "kr",
int_curr_symbol: "NOK",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2020 // 01.03.20
abmonth: "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Des",
month: "Januar,Februar,Mars,April,Mai,Juni,Juli,August,September,Oktober,November,Desember",
abday: "Su,Må,Ty,On,To,Fr,La",
day: "Sundag,Måndag,Tysdag,Onsdag,Torsdag,Fredag,Laurdag",
trans: { yes: "ja", Yes: "Ja", no: "nei", No: "Nei", ok: "ok", on: "på", off: "av", "< Back": "< Tilbake", "Delete": "Slett", "Mark Unread": "Merk som ulesen" }
},
"nb_NO": { // Using charfallbacks
lang: "nb_NO",
decimal_point: ",",
thousands_sep: " ",
currency_symbol: "kr",
int_curr_symbol: "NOK",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: { 0: "", 1: "" },
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%d. %b %Y", "1": "%d.%m.%Y" }, // 1. Mar 2020 // 01.03.20
abmonth: "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Des",
month: "Januar,Februar,Mars,April,Mai,Juni,Juli,August,September,Oktober,November,Desember",
abday: "Sø,Ma,Ti,On,To,Fr,Lø",
day: "Søndag,Mandag,Tirsdag,Onsdag,Torsdag,Fredag,Lørdag",
trans: { yes: "ja", Yes: "Ja", no: "nei", No: "Nei", ok: "ok", on: "på", off: "av", "< Back": "< Tilbake", "Delete": "Slett", "Mark Unread": "Merk som ulest" }
},
/*,
"he_IL": { // This won't work until we get a font - see https://github.com/espruino/BangleApps/issues/399
codePage : "ISO8859-8",

1
apps/megadenti/ChangeLog Normal file
View File

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

2
apps/megadenti/README.md Normal file
View File

@ -0,0 +1,2 @@
Denti :
This teeth washing assistan helps you to wash your teeth

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4kA///A4M79/6823gvb70/qvLrXrrXdqmyzl2gvTzn0glS4ttuZh9iMQCykBvsRC6vUC6sdppIUgMb2Ol+IYBGaBGB6Oll1xGKMRpsfi/FrQXRjtHutai/uC6EBunRj9SrB5DPppeBj/njVV0IICu1xUpscqvBvgSCC5qlCuN1kqnCI5xGB69VwP1qJ2QUoMR4tcuOBA4LXOUoOKi/lq8RgMd64YMUoXCr8XFgMXs1p6JeM7cRjVS0PZtN5s2Z3oXKiKlBivHjl4swACu4XJgIWB6mxv0l0MZC4eZ3YXHKgPdolLuMR+tXC4lmzq/GQQNEonduILBDwOWC4lmBgLRG3vREQcBFwoABtfRC4kXaIKyFjAHBjtmy4EBvoXFjtBNA0X13u0N5j/O5+BEwhGBDwZICgPykUil0RwUikfqC4kR3YGCgOGwIuBCwIAB+NSkXh/3xIwlIiEBC4MbDgMaCoMl8U+AQNajXhIwcUIwIXBI4ceC4UcmVXJoMfC4nUVooABjhGC0LEBJAM+C4alBew5HCGAPu8QEBC4bsB2IXLAAk+0KlHXwoXLUop1BgK/CmQXG9ynCjtHIwUB3ZLDgJaCC4qJBLwJGDC4zvCAAbvBBoUReYZHCMAcTCYWvGgMu95CDRo7AFl8RiqmCCZSQGqKtCRwYAMgLqB4JYBmXuwIXP4UiRIXj9xyDCpTDBuUuYoWDRwYAKWIUVl4SCjXnC5I5DjORCQMxCQUf8JdNZITMEAggAvA="))

99
apps/megadenti/app.js Normal file
View File

@ -0,0 +1,99 @@
var i = 0;
var counter = 10;
var counterInterval;
var img = Graphics.createImage(`
##### #####
# ##### #
# #
# #
## ##
## ##
## ##
# #### #
# # # #
# # # #
## ##
## ##
`);
var img1 = Graphics.createImage(`
### # ##### ## ####
# # # # # # #
# # ### # # ####
# # # ###### # #
### #### ##### # # # #
##### #####
# ##### #
# #
# #
## ##
## ##
## ##
# #### #
# # # #
# # # #
## ##
## ##
`);
g.setColor('#012345');
function outOfTime() {
if (counterInterval) return;
E.showMessage("Out of Time", "My Timer");
Bangle.beep(200, 4000)
.then(() => new Promise(resolve => setTimeout(resolve,200)))
.then(() => Bangle.beep(200, 3000));
// again, 10 secs later
setTimeout(outOfTime, 10000);
g.setColor('#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0'));
}
function immagine(){
g.drawImage(img1, 90, 20, {scale:2});
}
function countDown() {
counter--;
// Out of time
if (counter<=0) {
clearInterval(counterInterval);
counterInterval = undefined;
setWatch(startTimer, (process.env.HWVERSION==2) ? BTN1 : BTN2);
g.clear(img);
outOfTime();
return;
}
g.clear(1);
g.setFontAlign(0,0); // center font
g.setFont("Vector",80); // vector font, 80px
// draw the current counter value
g.drawImage(img, 90, 20, {scale:2});
g.drawString(counter,120,120);
g.drawLine(50,50,180,50);
g.drawLine(50,51,180,51);
g.drawLine(50,52,180,52);
// optional - this keeps the watch LCD lit up
Bangle.setLCDPower(1);
if (counter<=5){
immagine();
}
}
function startTimer() {
counter = 10;
countDown();
if (!counterInterval)
counterInterval = setInterval(countDown, 1000);
}
startTimer();

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,15 @@
{ "id": "megadenti",
"name": "Denti",
"shortName":"My Denti",
"icon": "brush-teeth.png",
"version":"0.01",
"description": "This app allows you wash your teeth in an efficent way. A coloured timer guides you while your smile becomes bright!",
"tags": "game",
"supports": ["BANGLEJS"],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name":"megadenti.app.js","url":"app.js"},
{"name":"megadenti.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -43,3 +43,4 @@
0.28: Option to auto-unlock the watch when a new message arrives
0.29: Fix message list overwrites on Bangle.js 1 (fix #1642)
0.30: Add new Icons (Youtube, Twitch, MS TODO, Teams, Snapchat, Signal, Post & DHL, Nina, Lieferando, Kalender, Discord, Corona Warn, Bibel)
0.31: Option to disable icon flashing

View File

@ -21,6 +21,7 @@ is chosen if there isn't much message text, but this specifies the smallest the
it starts getting clipped.
* `Auto-Open Music` - Should the app automatically open when the phone starts playing music?
* `Unlock Watch` - Should the app unlock the watch when a new message arrives, so you can touch the buttons at the bottom of the app?
* `Flash Icon` - Toggle flashing of the widget icon.
## New Messages

View File

@ -1,7 +1,7 @@
{
"id": "messages",
"name": "Messages",
"version": "0.30",
"version": "0.31",
"description": "App to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",

View File

@ -7,6 +7,7 @@
settings.unlockWatch=!!settings.unlockWatch;
settings.openMusic=!!settings.openMusic;
settings.maxUnreadTimeout=240;
if (settings.flash===undefined) settings.flash=true;
return settings;
}
function updateSetting(setting, value) {
@ -15,18 +16,10 @@
require('Storage').writeJSON("messages.settings.json", settings);
}
var vibPatterns = [/*LANG*/"Off", ".", "-", "--", "-.-", "---"];
var mainmenu = {
"" : { "title" : /*LANG*/"Messages" },
"< Back" : back,
/*LANG*/'Vibrate': {
value: Math.max(0,vibPatterns.indexOf(settings().vibrate)),
min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off",
onchange: v => {
updateSetting("vibrate", vibPatterns[v]);
}
},
/*LANG*/'Vibrate': require("buzz_menu").pattern(settings().vibrate, v => updateSetting("vibrate", v)),
/*LANG*/'Repeat': {
value: settings().repeat,
min: 0, max: 10,
@ -55,6 +48,11 @@
format: v => v?/*LANG*/'Yes':/*LANG*/'No',
onchange: v => updateSetting("unlockWatch", v)
},
/*LANG*/'Flash Icon': {
value: !!settings().flash,
format: v => v?/*LANG*/'Yes':/*LANG*/'No',
onchange: v => updateSetting("flash", v)
},
};
E.showMenu(mainmenu);
})

View File

@ -1,5 +1,5 @@
WIDGETS["messages"]={area:"tl", width:0, iconwidth:24,
draw:function() {
draw:function(recall) {
// If we had a setTimeout queued from the last time we were called, remove it
if (WIDGETS["messages"].i) {
clearTimeout(WIDGETS["messages"].i);
@ -8,15 +8,18 @@ draw:function() {
Bangle.removeListener('touch', this.touch);
if (!this.width) return;
var c = (Date.now()-this.t)/1000;
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+this.iconwidth);
g.drawImage((c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y);
let settings = require('Storage').readJSON("messages.settings.json", true) || {};
if (settings.flash===undefined) settings.flash = true;
if (recall !== true || settings.flash) {
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23);
g.drawImage(settings.flash && (c&1) ? atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+DAADDAADDAADDwAPD8A/DOBzDDn/DA//DAHvDAPvjAPvjAPvjAPvh///gf/vAAD+AAB8AAAAA==") : atob("GBiBAAAAAAAAAAAAAAAAAAAAAB//+D///D///A//8CP/xDj/HD48DD+B8D/D+D/3vD/vvj/vvj/vvj/vvh/v/gfnvAAD+AAB8AAAAA=="), this.x, this.y-1);
}
if (settings.repeat===undefined) settings.repeat = 4;
if (c<120 && (Date.now()-this.l)>settings.repeat*1000) {
this.l = Date.now();
WIDGETS["messages"].buzz(); // buzz every 4 seconds
}
WIDGETS["messages"].i=setTimeout(()=>WIDGETS["messages"].draw(), 1000);
WIDGETS["messages"].i=setTimeout(()=>WIDGETS["messages"].draw(true), 1000);
if (process.env.HWVERSION>1) Bangle.on('touch', this.touch);
},show:function(quiet) {
WIDGETS["messages"].t=Date.now(); // first time
@ -32,14 +35,7 @@ draw:function() {
Bangle.drawWidgets();
},buzz:function() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet) return; // never buzz during Quiet Mode
let v = (require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".";
function b() {
var c = v[0];
v = v.substr(1);
if (c==".") Bangle.buzz().then(()=>setTimeout(b,100));
if (c=="-") Bangle.buzz(500).then(()=>setTimeout(b,100));
}
b();
require("buzz").pattern((require('Storage').readJSON("messages.settings.json", true) || {}).vibrate || ".");
},touch:function(b,c) {
var w=WIDGETS["messages"];
if (!w||!w.width||c.x<w.x||c.x>w.x+w.width||c.y<w.y||c.y>w.y+w.iconwidth) return;

View File

@ -1,3 +1,4 @@
0.01: First release
0.02: Enhanced icon, make it bolder
0.03: Fixed issue with defaulting back to London
0.04: Fixed issue selecting Frankfurt not saved

View File

@ -4,7 +4,7 @@
"icon": "mylocation.png",
"type": "app",
"screenshots": [{"url":"screenshot_1.png"}],
"version":"0.03",
"version":"0.04",
"description": "Sets and stores the lat and long of your preferred City or it can be set from the GPS. mylocation.json can be used by other apps that need your main location lat and lon. See README",
"readme": "README.md",
"tags": "tool,utility",

View File

@ -61,7 +61,7 @@ function showMainMenu() {
min: 0, max: locations.length - 1,
format: v => locations[v],
onchange: v => {
if (v != 6) {
if (locations[v] !== "???") {
s.location = locations[v];
s.lat = lats[v];
s.lon = lons[v];

View File

@ -1,3 +1,4 @@
0.01: Launch app.
0.02: 12k steps are 360 degrees - improves readability of steps.
0.03: Battery improvements through sleep (no minute updates) and partial updates of drawing.
0.03: Battery improvements through sleep (no minute updates) and partial updates of drawing.
0.04: Use alarm for timer instead of own alarm implementation.

View File

@ -8,8 +8,8 @@ black one the battery level (100% = 360 degrees).
The selected theme is also respected. Note that this watch face is in fullscreen
mode, but widgets are still loaded in background.
## Other features
- Set a timer - simply touch top (+5min.) or bottom (-5 min.).
## Other Features
- Set a timer - simply touch top (+5min.) or bottom (-5 min.). This only works if "sched" is installed.
- If the weather is available through the weather app, the outside temp. will be shown.
- Sleep modus at midnight to save more battery (no minute updates).
- Icons for charging and GPS.
@ -29,5 +29,5 @@ which helped a lot for this development.
Icons from <a href="https://www.flaticon.com/free-icons" title="icons">by Freepik - Flaticon</a>
## Contributors
## Creator
- [David Peer](https://github.com/peerdavid).

View File

@ -3,7 +3,7 @@
"name": "Not Analog",
"shortName":"Not Analog",
"icon": "notanalog.png",
"version":"0.03",
"version":"0.04",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "An analog watch face for people that can not read analog watch faces.",

View File

@ -1,7 +1,7 @@
/**
* NOT ANALOG CLOCK
*/
const TIMER_IDX = "notanalog";
const locale = require('locale');
const storage = require('Storage')
const SETTINGS_FILE = "notanalog.setting.json";
@ -291,7 +291,6 @@ function drawSleep(){
function draw(fastUpdate){
// Execute handlers
handleState(fastUpdate);
handleAlarm();
if(state.sleep){
drawSleep();
@ -377,82 +376,80 @@ Bangle.on('touch', function(btn, e){
* Some helpers
*/
function queueDraw() {
// Faster updates during alarm to ensure that it is
// shown correctly...
var timeout = isAlarmEnabled() ? 10000 : 60000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw(true);
}, 60000 - (Date.now() % 60000));
draw();
}, timeout - (Date.now() % timeout));
}
/*
* Handle alarm
*/
function getCurrentTimeInMinutes(){
return Math.floor(Date.now() / (1000*60));
}
function isAlarmEnabled(){
return settings.alarm >= 0;
}
try{
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
if(alarmObj===undefined || !alarmObj.on){
return false;
}
return true;
} catch(ex){ }
return false;
}
function getAlarmMinutes(){
var currentTime = getCurrentTimeInMinutes();
return settings.alarm - currentTime;
}
function handleAlarm(){
if(!isAlarmEnabled()){
return;
return -1;
}
if(getAlarmMinutes() > 0){
return;
}
// Alarm
var t = 300;
Bangle.buzz(t, 1)
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, t)))
.then(() => Bangle.buzz(t, 1))
.then(() => new Promise(resolve => setTimeout(resolve, 5E3)))
.then(() => {
// Update alarm state to disabled
settings.alarm = -1;
storage.writeJSON(SETTINGS_FILE, settings);
});
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
function increaseAlarm(){
if(isAlarmEnabled()){
settings.alarm += 5;
} else {
settings.alarm = getCurrentTimeInMinutes() + 5;
}
storage.writeJSON(SETTINGS_FILE, settings);
try{
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, {
timer : (minutes+5)*60*1000,
});
alarm.reload();
} catch(ex){ }
}
function decreaseAlarm(){
if(isAlarmEnabled() && (settings.alarm-5 > getCurrentTimeInMinutes())){
settings.alarm -= 5;
} else {
settings.alarm = -1;
}
try{
var minutes = getAlarmMinutes();
minutes -= 5;
storage.writeJSON(SETTINGS_FILE, settings);
var alarm = require('sched')
alarm.setAlarm(TIMER_IDX, undefined);
if(minutes > 0){
alarm.setAlarm(TIMER_IDX, {
timer : minutes*60*1000,
});
}
alarm.reload();
} catch(ex){ }
}
function feedback(){
Bangle.buzz(40, 0.6);
}
/*
* Lets start widgets, listen for btn etc.
*/

View File

@ -4,7 +4,7 @@
"shortName": "Q Alarm",
"icon": "app.png",
"version": "0.04",
"description": "Alarm and timer app with days of week and 'hard' option.",
"description": "[Not recommended - use 'Alarm & Timer' app] Alarm and timer app with days of week and 'hard' option.",
"tags": "tool,alarm,widget",
"supports": ["BANGLEJS", "BANGLEJS2"],
"storage": [

3
apps/sched/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: New App!
0.02: Fix scheduling of other alarms if there is a pending alarm from the past (fix #1667)
0.03: Fix `getTimeToAlarm` for a timer already used at same day, don't set `last` for timers.

89
apps/sched/README.md Normal file
View File

@ -0,0 +1,89 @@
Sched: Scheduling library for alarms and timers
====================================
This provides boot code, a library and tools for alarms and timers.
Other apps can use this to provide alarm functionality.
App
---
The Alarm app allows you to add/modify any running timers.
Internals / Library
-------------------
Alarms are stored in an array in `sched.json`, and take the form:
```
{
id : "mytimer", // optional ID for this alarm/timer, so apps can easily find *their* timers
appid : "myappid", // optional app ID for alarms that you set/use for your app
on : true, // is the alarm enabled?
t : 23400000, // Time of day since midnight in ms (if a timer, this is set automatically when timer starts)
dow : 0b1111111, // Binary encoding for days of the week to run alarm on
// SUN = 1
// MON = 2
// TUE = 4
// WED = 8
// THU = 16
// FRI = 32
// SAT = 64
date : "2022-04-04", // OPTIONAL date for the alarm, in YYYY-MM-DD format
// eg (new Date()).toISOString().substr(0,10)
msg : "Eat food", // message to display.
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day! (No change from 0 on timers)
rp : true, // repeat the alarm every day?
vibrate : "...", // OPTIONAL pattern of '.', '-' and ' ' to use for when buzzing out this alarm (defaults to '..' if not set)
hidden : false, // OPTIONAL if false, the widget should not show an icon for this alarm
as : false, // auto snooze
timer : 5*60*1000, // OPTIONAL - if set, this is a timer and it's the time in ms
js : "load('myapp.js')" // OPTIONAL - a JS command to execute when the alarm activates (*instead* of loading 'sched.js')
// when this code is run, you're responsible for setting alarm.on=false (or removing the alarm)
data : { ... } // OPTIONAL - your app can store custom data in here if needed (don't store a lot of data here)
}
```
The [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched/lib.js) contains
a few helpful functions for getting/setting alarms and timers, but is intentionally sparse so as not to
use too much RAM.
It can be used as follows:
```
// add/update an existing alarm
require("sched").setAlarm("mytimer", {
msg : "Wake up",
timer : 10*60*1000, // 10 Minutes
});
// Ensure the widget and alarm timer updates to schedule the new alarm properly
require("sched").reload();
// Get the time to the next alarm for us
var timeToNext = require("sched").getTimeToAlarm(require("sched").getAlarm("mytimer"));
// timeToNext===undefined if no alarm or alarm disabled
// delete an alarm
require("sched").setAlarm("mytimer", undefined);
// reload after deleting...
require("sched").reload();
// Or add an alarm that runs your own code - in this case
// loading the settings app. The alarm will not be removed/stopped
// automatically.
require("sched").setAlarm("customrunner", {
appid : "myapp",
js : "load('setting.app.js')",
timer : 1*60*1000, // 1 Minute
});
// If you have been specifying `appid` you can also find any alarms that
// your app has created with the following:
require("sched").getAlarms().filter(a=>a.appid=="myapp");
```
If your app requires alarms, you can specify that the alarms app needs to
be installed by adding `"dependencies": {"scheduler":"type"},` to your app's
metadata.

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkGswAhiMRCCAREAo4eHBIQLEAgwYHsIJDiwHB5gACBpIhHCoYZEGA4gFCw4ABGA4HEjgXJ4IXGAwcUB4VEmf//8zogICoJIFAodMBoNDCoIADmgJB4gXIFwXDCwoABngwFC4guB4k/CQXwh4EC+YMCC44iBp4qDC4n/+gNBC41sEIJCEC4v/GAPGC4dhXYRdFC4xhCCYIXCdQRdDC5HzegQXCsxGHC45IDCwQXCUgwXHJAIXGRogXJSIIXcOw4XIPAYXcBwv/mEDBAwXOgtQC65QGC5vzoEAJAx3Nmk/mEABIiPN+dDAQIwFC4zXGFwKRCGAjvMFwQECGAgXI4YuGGAUvAgU8C4/EFwwGCAgdMC4p4EFwobFOwoXDJAIoEAApGBC4xIEABJGHGAapEAAqNBFwwXD4heI+YuBC5BIBVQhdHIw4wD5inFS4IKCCxFmigNCokzCoMzogICoIWIsMRjgPCAA3BiMWC48RBQIXJEgMRFxAJCCw4lEC44IECooOIBAaBJKwhgIAH4ACA=="))

BIN
apps/sched/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

44
apps/sched/boot.js Normal file
View File

@ -0,0 +1,44 @@
// check for alarms
(function() { // run in closure to ensure allocated vars get removed
if (Bangle.SCHED) {
clearTimeout(Bangle.SCHED);
delete Bangle.SCHED;
}
var alarms = require('Storage').readJSON('sched.json',1)||[];
var time = new Date();
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
var d = time.getDate();
var active = alarms.filter(
a=>a.on && // enabled
a.last!=d && // not already fired today
a.t+60000>currentTime && // is not in the past by >1 minute
(a.dow>>time.getDay())&1 && // is allowed on this day of the week
(!a.date || a.date==time.toISOString().substr(0,10)) // is allowed on this date
);
if (active.length) {
active = active.sort((a,b)=>a.t-b.t); // sort by time
var t = active[0].t-currentTime;
if (t<1000) t=1000; // start alarm minimum 1 sec from now
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally.
If active[0].js is defined, just run that code as-is and not alarm.js */
Bangle.SCHED = setTimeout(active[0].js||'load("sched.js")',t);
} else { // check for new alarms at midnight (so day of week works)
Bangle.SCHED = setTimeout('eval(require("Storage").read("sched.boot.js"))', 86400000 - (Date.now()%86400000));
}
})();
/* DEBUGGING
===============
// show the current timer for the next event
global["\xff"].timers[Bangle.SCHED]
// time in hours of scheduled timer event
global["\xff"].timers[Bangle.SCHED].time / (1024*1024*60*60)
// set time 1 hour in the past
setTime(getTime() - 60*60)
*/

54
apps/sched/lib.js Normal file
View File

@ -0,0 +1,54 @@
// Return an array of all alarms
exports.getAlarms = function() {
return require("Storage").readJSON("sched.json",1)||[];
};
// Write a list of alarms back to storage
exports.setAlarms = function(alarms) {
return require("Storage").writeJSON("sched.json",alarms);
};
// Return an alarm object based on ID
exports.getAlarm = function(id) {
return exports.getAlarms().find(a=>a.id==id);
};
// Given a list of alarms from getAlarms, return a list of active alarms for the given time (or current time if time not specified)
exports.getActiveAlarms = function(alarms, time) {
if (!time) time = new Date();
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000)
+10000;// get current time - 10s in future to ensure we alarm if we've started the app a tad early
return alarms.filter(a=>a.on&&(a.t<currentTime)&&(a.last!=time.getDate()) && (!a.date || a.date==time.toISOString().substr(0,10))).sort((a,b)=>a.t-b.t);
}
// Set an alarm object based on ID. Leave 'alarm' undefined to remove it
exports.setAlarm = function(id, alarm) {
var alarms = exports.getAlarms().filter(a=>a.id!=id);
if (alarm !== undefined) {
alarm.id = id;
if (alarm.dow===undefined) alarm.dow = 0b1111111;
if (alarm.on!==false) alarm.on=true;
if (alarm.timer) { // if it's a timer, set the start time as a time from *now*
var time = new Date();
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
alarm.t = currentTime + alarm.timer;
}
alarms.push(alarm);
}
exports.setAlarms(alarms);
};
/// Get time until the given alarm (object). Return undefined if alarm not enabled, or if 86400000 or more, alarm could be *more* than a day in the future
exports.getTimeToAlarm = function(alarm, time) {
if (!alarm) return undefined;
if (!time) time = new Date();
var active = alarm.on && (alarm.dow>>time.getDay())&1 && (!alarm.date || alarm.date==time.toISOString().substr(0,10));
if (!active) return undefined;
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
var t = alarm.t-currentTime;
if (alarm.last == time.getDate() || t < -60000) t += 86400000;
return t;
};
/// Force a reload of the current alarms and widget
exports.reload = function() {
eval(require("Storage").read("sched.boot.js"));
if (WIDGETS["alarm"]) {
WIDGETS["alarm"].reload();
Bangle.drawWidgets();
}
};

18
apps/sched/metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "sched",
"name": "Scheduler",
"version": "0.03",
"description": "Scheduling library for alarms and timers",
"icon": "app.png",
"type": "scheduler",
"tags": "tool,system,alarm",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"sched.boot.js","url":"boot.js"},
{"name":"sched.js","url":"sched.js"},
{"name":"sched.img","url":"app-icon.js","evaluate":true},
{"name":"sched","url":"lib.js"}
],
"data": [{"name":"sched.json"}]
}

74
apps/sched/sched.js Normal file
View File

@ -0,0 +1,74 @@
// Chances are boot0.js got run already and scheduled *another*
// 'load(sched.js)' - so let's remove it first!
if (Bangle.SCHED) {
clearInterval(Bangle.SCHED);
delete Bangle.SCHED;
}
// time in ms -> { hrs, mins }
function decodeTime(t) {
t = 0|t; // sanitise
var hrs = 0|(t/3600000);
return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) };
}
function formatTime(t) {
var o = decodeTime(t);
return o.hrs+":"+("0"+o.mins).substr(-2);
}
function showAlarm(alarm) {
var msg = "";
msg += alarm.timer ? formatTime(alarm.timer) : formatTime(alarm.t);
if (alarm.msg) {
msg += "\n"+alarm.msg;
} else {
if (alarm.timer)
msg = atob("ACQswgD//33vRcGHIQAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAABVVVAAAAAAAAAP/wAAAAAAAAAP/wAAAAAAAAAqqoAPAAAAAAqqqqoP8AAAAKqqqqqv/AAACqqqqqqq/wAAKqqqlWqqvwAAqqqqlVaqrAACqqqqlVVqqAAKqqqqlVVaqgAKqaqqlVVWqgAqpWqqlVVVqoAqlWqqlVVVaoCqlV6qlVVVaqCqVVfqlVVVWqCqVVf6lVVVWqKpVVX/lVVVVqqpVVV/+VVVVqqpVVV//lVVVqqpVVVfr1VVVqqpVVVfr1VVVqqpVVVb/lVVVqqpVVVW+VVVVqqpVVVVVVVVVqiqVVVVVVVVWqCqVVVVVVVVWqCqlVVVVVVVaqAqlVVVVVVVaoAqpVVVVVVVqoAKqVVVVVVWqgAKqlVVVVVaqgACqpVVVVVqqAAAqqlVVVaqoAAAKqqVVWqqgAAACqqqqqqqAAAAAKqqqqqgAAAAAAqqqqoAAAAAAAAqqoAAAAA==")+" "+msg;
else
msg = atob("AC0swgF97///RcEpMlVVVVVVf9VVVVVVVVX/9VVf9VVf/1VVV///1Vf9VX///VVX///VWqqlV///1Vf//9aqqqqpf//9V///2qqqqqqn///V///6qqqqqqr///X//+qqoAAKqqv//3//6qoAAAAKqr//3//qqAAAAAAqq//3/+qoAADwAAKqv/3/+qgAADwAACqv/3/aqAAADwAAAqp/19qoAAADwAAAKqfV1qgAAADwAAACqXVWqgAAADwAAACqlVWqAAAADwAAAAqlVWqAAAADwAAAAqlVWqAAAADwAAAAqlVaoAAAADwAAAAKpVaoAAAADwAAAAKpVaoAAAADwAAAAKpVaoAAAAOsAAAAKpVaoAAAAOsAAAAKpVaoAAAAL/AAAAKpVaoAAAAgPwAAAKpVaoAAACAD8AAAKpVWqAAAIAA/AAAqlVWqAAAgAAPwAAqlVWqAACAAADwAAqlVWqgAIAAAAAACqlVVqgAgAAAAAACqVVVqoAAAAAAAAKqVVVaqAAAAAAAAqpVVVWqgAAAAAACqlVVVWqoAAAAAAKqlVVVVqqAAAAAAqqVVVVVaqoAAAAKqpVVVVVeqqoAAKqqtVVVVV/6qqqqqqr/VVVVX/2qqqqqqn/1VVVf/VaqqqqpV/9VVVf9VVWqqlVVf9VVVf1VVVVVVVVX9VQ==")+" "+msg;
}
Bangle.loadWidgets();
Bangle.drawWidgets();
var buzzCount = 10;
E.showPrompt(msg,{
title:alarm.timer ? /*LANG*/"TIMER!" : /*LANG*/"ALARM!",
buttons : {/*LANG*/"Snooze":true,/*LANG*/"Ok":false} // default is sleep so it'll come back in 10 mins
}).then(function(sleep) {
buzzCount = 0;
if (sleep) {
if(alarm.ot===undefined) alarm.ot = alarm.t;
alarm.t += 10*60*1000; // 10 minutes
} else {
if (!alarm.timer) alarm.last = (new Date()).getDate();
if (alarm.ot!==undefined) {
alarm.t = alarm.ot;
delete alarm.ot;
}
if (!alarm.rp) alarm.on = false;
}
// alarm is still a member of 'alarms', so writing to array writes changes back directly
require("sched").setAlarms(alarms);
load();
});
function buzz() {
require("buzz").pattern(alarm.vibrate===undefined?"..":alarm.vibrate).then(function() {
if (buzzCount--)
setTimeout(buzz, 3000);
else if(alarm.as) { // auto-snooze
buzzCount = 10;
setTimeout(buzz, 600000);
}
});
}
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return;
buzz();
}
// Check for alarms
var alarms = require("sched").getAlarms();
var active = require("sched").getActiveAlarms(alarms);
if (active.length) // if there's an alarm, show it
showAlarm(active[0]);
else // otherwise just go back to default app
setTimeout(load, 100);

1
apps/smpltmr/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Release

21
apps/smpltmr/README.md Normal file
View File

@ -0,0 +1,21 @@
# Simple Timer
A simple app to set a timer quickly. Simply tab on top/bottom/left/right
to select the minutes and tab in the middle of the screen to start/stop
the timer. Note that this timer depends on qalarm.
# Overview
If you open the app, you can simply control the timer
by clicking on top, bottom, left or right of the screen.
If you tab at the middle of the screen, the timer is
started / stopped.
![](description.png)
# Creator
[David Peer](https://github.com/peerdavid)
# Thanks to...
Time icon created by <a href="https://www.flaticon.com/free-icons/time" title="time icons">CreativeCons - Flaticon</a>

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwMAg//AAXgApcAvAZBhwCBuAFuGoUeAQM4AQM8AQl8Bwn4AQMPgEB+AFBg+AgZYBgED4AHBAoIPBCYIAC/AfCGwQrCGAQ3CAAMcIYQFCJ4QABnoREvIdE/eeAgUB+fPAoUD8/nIIUHz/zJoUPn/5LIUev/8MoU8//+OIU5XwO8AoN7AoPeAoNzAoPOAsrFKg4QBAAPgApYA=="))

124
apps/smpltmr/app.js Normal file
View File

@ -0,0 +1,124 @@
/*
* SIMPLE TIMER
*
* Creator: David Peer
* Date: 02/2022
*/
Bangle.loadWidgets();
const alarm = require("sched");
const TIMER_IDX = "smpltmr";
const screenWidth = g.getWidth();
const screenHeight = g.getHeight();
const cx = parseInt(screenWidth/2);
const cy = parseInt(screenHeight/2)-12;
var minutes = 5;
var interval; //used for the 1 second interval timer
function isTimerEnabled(){
var alarmObj = alarm.getAlarm(TIMER_IDX);
if(alarmObj===undefined || !alarmObj.on){
return false;
}
return true;
}
function getTimerMin(){
var alarmObj = alarm.getAlarm(TIMER_IDX);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
function setTimer(minutes){
alarm.setAlarm(TIMER_IDX, {
// msg : "Simple Timer",
timer : minutes*60*1000,
});
alarm.reload();
}
function deleteTimer(){
alarm.setAlarm(TIMER_IDX, undefined);
alarm.reload();
}
setWatch(_=>load(), BTN1);
function draw(){
g.clear(1);
Bangle.drawWidgets();
if (interval) {
clearInterval(interval);
}
interval = undefined;
// Write time
g.setFontAlign(0, 0, 0);
g.setFont("Vector", 32).setFontAlign(0,-1);
var started = isTimerEnabled();
var text = minutes + " min.";
if(started){
var min = getTimerMin();
text = min + " min.";
}
var rectWidth = parseInt(g.stringWidth(text) / 2);
if(started){
interval = setInterval(draw, 1000);
g.setColor("#ff0000");
} else {
g.setColor(g.theme.fg);
}
g.fillRect(cx-rectWidth-5, cy-5, cx+rectWidth, cy+30);
g.setColor(g.theme.bg);
g.drawString(text, cx, cy);
}
Bangle.on('touch', function(btn, e){
var left = parseInt(g.getWidth() * 0.25);
var right = g.getWidth() - left;
var upper = parseInt(g.getHeight() * 0.25);
var lower = g.getHeight() - upper;
var isLeft = e.x < left;
var isRight = e.x > right;
var isUpper = e.y < upper;
var isLower = e.y > lower;
var isMiddle = !isLeft && !isRight && !isUpper && !isLower;
var started = isTimerEnabled();
if(isRight && !started){
minutes += 1;
Bangle.buzz(40, 0.3);
} else if(isLeft && !started){
minutes -= 1;
Bangle.buzz(40, 0.3);
} else if(isUpper && !started){
minutes += 5;
Bangle.buzz(40, 0.3);
} else if(isLower && !started){
minutes -= 5;
Bangle.buzz(40, 0.3);
} else if(isMiddle) {
if(!started){
setTimer(minutes);
} else {
deleteTimer();
}
Bangle.buzz(80, 0.6);
}
minutes = Math.max(0, minutes);
draw();
});
g.reset();
draw();

BIN
apps/smpltmr/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,17 @@
{
"id": "smpltmr",
"name": "Simple Timer",
"shortName": "Simple Timer",
"version": "0.01",
"description": "A very simple app to start a timer.",
"icon": "app.png",
"tags": "tool",
"dependencies": {"scheduler":"type"},
"supports": ["BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}, {"url": "screenshot_2.png"}],
"readme": "README.md",
"storage": [
{"name":"smpltmr.app.js","url":"app.js"},
{"name":"smpltmr.img","url":"app-icon.js","evaluate":true}
]
}

BIN
apps/smpltmr/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

14
modules/buzz.js Normal file
View File

@ -0,0 +1,14 @@
/* Call this with a pattern like '.-.', '.. .' or '..' to buzz that pattern
out on the internal vibration motor. use buzz_menu to display a menu
where the patterns can be chosen. */
exports.pattern = pattern => new Promise(resolve => {
function b() {
if (pattern=="") resolve();
var c = pattern[0];
pattern = pattern.substr(1);
if (c==".") Bangle.buzz().then(()=>setTimeout(b,100));
else if (c=="-") Bangle.buzz(500).then(()=>setTimeout(b,100));
else setTimeout(b,100);
}
b();
});

13
modules/buzz_menu.js Normal file
View File

@ -0,0 +1,13 @@
/* Display a menu to select from various vibration patterns for use with buzz.js */
exports.pattern = function(value, callback) {
var vibPatterns = ["", ".", "..", "-", "--", "-.-", "---"];
return {
value: Math.max(0,vibPatterns.indexOf(value)),
min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
callback(vibPatterns[v]);
}
};
}