Merge remote-tracking branch 'upstream/master'

master
Hugh Barney 2022-12-05 20:32:37 +00:00
commit cdfe284987
62 changed files with 1242 additions and 322 deletions

View File

@ -2,3 +2,4 @@
0.02: Design improvements and fixes. 0.02: Design improvements and fixes.
0.03: Indicate battery level through line occurrence. 0.03: Indicate battery level through line occurrence.
0.04: Use widget_utils module. 0.04: Use widget_utils module.
0.05: Support for clkinfo.

View File

@ -10,7 +10,9 @@ The original output of stable diffusion is shown here:
My implementation is shown below. Note that horizontal lines occur randomly, but the My implementation is shown below. Note that horizontal lines occur randomly, but the
probability is correlated with the battery level. So if your screen contains only probability is correlated with the battery level. So if your screen contains only
a few lines its time to charge your bangle again ;) a few lines its time to charge your bangle again ;) Also note that the upper text
implementes the clkinfo module and can be configured via touch left/right/up/down.
Touch at the center to trigger the selected action.
![](impl.png) ![](impl.png)

View File

@ -1,6 +1,14 @@
/** /************************************************
* AI Clock * AI Clock
*/ */
const storage = require('Storage');
const clock_info = require("clock_info");
/************************************************
* Assets
*/
require("Font7x11Numeric7Seg").add(Graphics); require("Font7x11Numeric7Seg").add(Graphics);
Graphics.prototype.setFontGochiHand = function(scale) { Graphics.prototype.setFontGochiHand = function(scale) {
// Actual height 27 (29 - 3) // Actual height 27 (29 - 3)
@ -13,7 +21,7 @@ Graphics.prototype.setFontGochiHand = function(scale) {
return this; return this;
} }
/* /************************************************
* Set some important constants such as width, height and center * Set some important constants such as width, height and center
*/ */
var W = g.getWidth(),R=W/2; var W = g.getWidth(),R=W/2;
@ -21,6 +29,120 @@ var H = g.getHeight();
var cx = W/2; var cx = W/2;
var cy = H/2; var cy = H/2;
var drawTimeout; var drawTimeout;
var lock_input = false;
/************************************************
* SETTINGS
*/
const SETTINGS_FILE = "aiclock.setting.json";
let settings = {
menuPosX: 0,
menuPosY: 0,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
}
/************************************************
* Menu
*/
function getDate(){
var date = new Date();
return ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2)
}
// Custom clockItems menu - therefore, its added here and not in a clkinfo.js file.
var clockItems = {
name: getDate(),
img: null,
items: [
{ name: "Week",
get: () => ({ text: "Week " + weekOfYear(), img: null}),
show: function() { clockItems.items[0].emit("redraw"); },
hide: function () {}
},
]
};
function weekOfYear() {
var date = new Date();
date.setHours(0, 0, 0, 0);
// Thursday in current week decides the year.
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
// January 4 is always in week 1.
var week1 = new Date(date.getFullYear(), 0, 4);
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
- 3 + (week1.getDay() + 6) % 7) / 7);
}
// Load menu
var menu = clock_info.load();
menu = menu.concat(clockItems);
// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
settings.menuPosX = 0;
settings.menuPosY = 0;
}
// Set draw functions for each item
menu.forEach((menuItm, x) => {
menuItm.items.forEach((item, y) => {
function drawItem() {
// For the clock, we have a special case, as we don't wanna redraw
// immediately when something changes. Instead, we update data each minute
// to save some battery etc. Therefore, we hide (and disable the listener)
// immedeately after redraw...
item.hide();
// After drawing the item, we enable inputs again...
lock_input = false;
var info = item.get();
drawMenuItem(info.text, info.img);
}
item.on('redraw', drawItem);
})
});
function canRunMenuItem(){
if(settings.menuPosY == 0){
return false;
}
var menuEntry = menu[settings.menuPosX];
var item = menuEntry.items[settings.menuPosY-1];
return item.run !== undefined;
}
function runMenuItem(){
if(settings.menuPosY == 0){
return;
}
var menuEntry = menu[settings.menuPosX];
var item = menuEntry.items[settings.menuPosY-1];
try{
var ret = item.run();
if(ret){
Bangle.buzz(300, 0.6);
}
} catch (ex) {
// Simply ignore it...
}
}
/* /*
* Based on the great multi clock from https://github.com/jeffmer/BangleApps/ * Based on the great multi clock from https://github.com/jeffmer/BangleApps/
@ -76,7 +198,50 @@ function toAngle(a){
return a return a
} }
function drawMenuItem(text, image){
if(text == null){
drawTime();
return
}
// image = atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==");
text = String(text);
g.reset().setBgColor("#fff").setColor("#000");
g.setFontAlign(0,0);
g.setFont("Vector", 20);
var imgWidth = image == null ? 0 : 24;
var strWidth = g.stringWidth(text);
var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2);
var w = imgWidth + strWidth;
g.clearRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2)
// Draw right line as designed by stable diffusion
g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2);
g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2);
g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2);
// And finally the text
g.drawString(text, cx+imgWidth/2, 42);
g.drawString(text, cx+1+imgWidth/2, 41);
if(image != null) {
var scale = image.width ? imgWidth / image.width : 1;
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale});
}
drawTime();
}
function drawTime(){ function drawTime(){
// Draw digital time first
drawDigits();
// And now the analog time
var drawHourHand = g.drawRotRect.bind(g,8,12,R-38); var drawHourHand = g.drawRotRect.bind(g,8,12,R-38);
var drawMinuteHand = g.drawRotRect.bind(g,6,12,R-12 ); var drawMinuteHand = g.drawRotRect.bind(g,6,12,R-12 );
@ -90,13 +255,6 @@ function drawTime(){
h += date.getMinutes()/60.0; h += date.getMinutes()/60.0;
h = parseInt(h*360/12); h = parseInt(h*360/12);
// Draw minute and hour bg
g.setColor(g.theme.bg);
drawHourHand(toAngle(h-3));
drawHourHand(toAngle(h+3));
drawMinuteHand(toAngle(m-2));
drawMinuteHand(toAngle(m+3));
// Draw minute and hour fg // Draw minute and hour fg
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
drawHourHand(h); drawHourHand(h);
@ -104,28 +262,6 @@ function drawTime(){
} }
function drawDate(){
var date = new Date();
g.setFontAlign(0,0);
g.setFontGochiHand();
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2);
var w = g.stringWidth(text);
g.setColor(g.theme.bg);
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
g.setColor(g.theme.fg);
// Draw right line as designed by stable diffusion
g.drawLine(cx+w/2+5, 20, cx+w/2+5, 40+12);
g.drawLine(cx+w/2+6, 20, cx+w/2+6, 40+12);
g.drawLine(cx+w/2+7, 20, cx+w/2+7, 40+12);
// And finally the text
g.drawString(text, cx, 40);
}
function drawDigits(){ function drawDigits(){
var date = new Date(); var date = new Date();
@ -156,20 +292,35 @@ function drawDigits(){
} }
function drawDate(){
var menuEntry = menu[settings.menuPosX];
// The first entry is the overview...
if(settings.menuPosY == 0){
drawMenuItem(menuEntry.name, menuEntry.img);
return;
}
// Draw item if needed
lock_input = true;
var item = menuEntry.items[settings.menuPosY-1];
item.show();
}
function draw(){ function draw(){
// Queue draw in one minute // Queue draw in one minute
queueDraw(); queueDraw();
g.reset(); g.reset();
g.clearRect(0, 0, g.getWidth(), g.getHeight()); g.clearRect(0, 0, g.getWidth(), g.getHeight());
g.setColor(1,1,1); g.setColor(1,1,1);
drawBackground(); drawBackground();
drawDate(); drawDate();
drawDigits();
drawTime();
drawCircle(Bangle.isLocked()); drawCircle(Bangle.isLocked());
} }
@ -190,6 +341,68 @@ Bangle.on('lock', function(isLocked) {
drawCircle(isLocked); drawCircle(isLocked);
}); });
Bangle.on('touch', function(btn, e){
var left = parseInt(g.getWidth() * 0.22);
var right = g.getWidth() - left;
var upper = parseInt(g.getHeight() * 0.22);
var lower = g.getHeight() - upper;
var is_upper = e.y < upper;
var is_lower = e.y > lower;
var is_left = e.x < left && !is_upper && !is_lower;
var is_right = e.x > right && !is_upper && !is_lower;
var is_center = !is_upper && !is_lower && !is_left && !is_right;
if(lock_input){
return;
}
if(is_lower){
Bangle.buzz(40, 0.6);
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
draw();
}
if(is_upper){
Bangle.buzz(40, 0.6);
settings.menuPosY = settings.menuPosY-1;
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
draw();
}
if(is_right){
Bangle.buzz(40, 0.6);
settings.menuPosX = (settings.menuPosX+1) % menu.length;
settings.menuPosY = 0;
draw();
}
if(is_left){
Bangle.buzz(40, 0.6);
settings.menuPosY = 0;
settings.menuPosX = settings.menuPosX-1;
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
draw();
}
if(is_center){
if(canRunMenuItem()){
runMenuItem();
}
}
});
E.on("kill", function(){
try{
storage.write(SETTINGS_FILE, settings);
} catch(ex){
// If this fails, we still kill the app...
}
});
/* /*
* Some helpers * Some helpers
@ -203,7 +416,6 @@ function queueDraw() {
} }
/* /*
* Lets start widgets, listen for btn etc. * Lets start widgets, listen for btn etc.
*/ */
@ -216,6 +428,7 @@ Bangle.loadWidgets();
* area to the top bar doesn't get cleared. * area to the top bar doesn't get cleared.
*/ */
require('widget_utils').hide(); require('widget_utils').hide();
// Clear the screen once, at startup and draw clock // Clear the screen once, at startup and draw clock
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear(); g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
draw(); draw();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
apps/aiclock/impl_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
apps/aiclock/impl_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -3,7 +3,7 @@
"name": "AI Clock", "name": "AI Clock",
"shortName":"AI Clock", "shortName":"AI Clock",
"icon": "aiclock.png", "icon": "aiclock.png",
"version":"0.04", "version":"0.05",
"readme": "README.md", "readme": "README.md",
"supports": ["BANGLEJS2"], "supports": ["BANGLEJS2"],
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.", "description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
@ -11,7 +11,9 @@
"tags": "clock", "tags": "clock",
"screenshots": [ "screenshots": [
{"url":"orig.png"}, {"url":"orig.png"},
{"url":"impl.png"} {"url":"impl.png"},
{"url":"impl_2.png"},
{"url":"impl_3.png"}
], ],
"storage": [ "storage": [
{"name":"aiclock.app.js","url":"aiclock.app.js"}, {"name":"aiclock.app.js","url":"aiclock.app.js"},

View File

@ -16,4 +16,5 @@
0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152) 0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152)
0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge 0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge
0.18: Use new message library 0.18: Use new message library
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged) If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
0.19: Add automatic translation for a couple of strings.

View File

@ -57,7 +57,7 @@
t:event.cmd=="incoming"?"add":"remove", t:event.cmd=="incoming"?"add":"remove",
id:"call", src:"Phone", id:"call", src:"Phone",
positive:true, negative:true, positive:true, negative:true,
title:event.name||"Call", body:"Incoming call\n"+event.number}); title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
require("messages").pushMessage(event); require("messages").pushMessage(event);
}, },
"alarm" : function() { "alarm" : function() {
@ -148,7 +148,7 @@
Bangle.http = (url,options)=>{ Bangle.http = (url,options)=>{
options = options||{}; options = options||{};
if (!NRF.getSecurityStatus().connected) if (!NRF.getSecurityStatus().connected)
return Promise.reject("Not connected to Bluetooth"); return Promise.reject(/*LANG*/"Not connected to Bluetooth");
if (Bangle.httpRequest === undefined) if (Bangle.httpRequest === undefined)
Bangle.httpRequest={}; Bangle.httpRequest={};
if (options.id === undefined) { if (options.id === undefined) {

View File

@ -2,7 +2,7 @@
"id": "android", "id": "android",
"name": "Android Integration", "name": "Android Integration",
"shortName": "Android", "shortName": "Android",
"version": "0.18", "version": "0.19",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge", "tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -61,3 +61,6 @@
0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass 0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass
0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock 0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock
0.54: Fix for invalid version comparison in polyfill 0.54: Fix for invalid version comparison in polyfill
0.55: Add toLocalISOString polyfill for pre-2v15 firmwares
Only add boot info comments if settings.bootDebug was set
If settings.bootDebug is set, output timing for each section of .boot0

View File

@ -1,16 +1,22 @@
/* This rewrites boot0.js based on current settings. If settings changed then it /* This rewrites boot0.js based on current settings. If settings changed then it
recalculates, but this avoids us doing a whole bunch of reconfiguration most recalculates, but this avoids us doing a whole bunch of reconfiguration most
of the time. */ of the time. */
{ // execute in our own scope so we don't have to free variables...
E.showMessage(/*LANG*/"Updating boot0..."); E.showMessage(/*LANG*/"Updating boot0...");
var s = require('Storage').readJSON('setting.json',1)||{}; let s = require('Storage').readJSON('setting.json',1)||{};
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 const BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
var FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1")); const FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1"));
var boot = "", bootPost = ""; const DEBUG = s.bootDebug; // we can set this to enable debugging output in boot0
let boot = "", bootPost = "";
if (DEBUG) {
boot += "var _tm=Date.now()\n";
bootPost += "delete _tm;";
}
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
} else { } else {
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT); let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
} }
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
@ -88,14 +94,25 @@ delete Bangle.showClock;
if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`; if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`;
delete Bangle.load; delete Bangle.load;
if (!Bangle.load) boot += `Bangle.load = load;\n`; if (!Bangle.load) boot += `Bangle.load = load;\n`;
let date = new Date();
delete date.toLocalISOString; // toLocalISOString was only introduced in 2v15
if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function() {
var o = this.getTimezoneOffset();
var d = new Date(this.getTime() - o*60000);
var sign = o>0?"-":"+";
o = Math.abs(o);
return d.toISOString().slice(0,-1)+sign+Math.floor(o/60).toString().padStart(2,0)+(o%60).toString().padStart(2,0);
};\n`;
// show timings
if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
// ================================================== BOOT.JS // ================================================== BOOT.JS
// Append *.boot.js files // Append *.boot.js files
// These could change bleServices/bleServiceOptions if needed // These could change bleServices/bleServiceOptions if needed
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
var getPriority = /.*\.(\d+)\.boot\.js$/; let getPriority = /.*\.(\d+)\.boot\.js$/;
var aPriority = a.match(getPriority); let aPriority = a.match(getPriority);
var bPriority = b.match(getPriority); let bPriority = b.match(getPriority);
if (aPriority && bPriority){ if (aPriority && bPriority){
return parseInt(aPriority[1]) - parseInt(bPriority[1]); return parseInt(aPriority[1]) - parseInt(bPriority[1]);
} else if (aPriority && !bPriority){ } else if (aPriority && !bPriority){
@ -106,14 +123,16 @@ var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
return a==b ? 0 : (a>b ? 1 : -1); return a==b ? 0 : (a>b ? 1 : -1);
}); });
// precalculate file size // precalculate file size
var fileSize = boot.length + bootPost.length; let fileSize = boot.length + bootPost.length;
bootFiles.forEach(bootFile=>{ bootFiles.forEach(bootFile=>{
// match the size of data we're adding below in bootFiles.forEach // match the size of data we're adding below in bootFiles.forEach
fileSize += 2+bootFile.length+1+require('Storage').read(bootFile).length+2; if (DEBUG) fileSize += 2+bootFile.length+1; // `//${bootFile}\n` comment
fileSize += require('Storage').read(bootFile).length+2; // boot code plus ";\n"
if (DEBUG) fileSize += 48+E.toJS(bootFile).length; // `print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
}); });
// write file in chunks (so as not to use up all RAM) // write file in chunks (so as not to use up all RAM)
require('Storage').write('.boot0',boot,0,fileSize); require('Storage').write('.boot0',boot,0,fileSize);
var fileOffset = boot.length; let fileOffset = boot.length;
bootFiles.forEach(bootFile=>{ bootFiles.forEach(bootFile=>{
// we add a semicolon so if the file is wrapped in (function(){ ... }() // we add a semicolon so if the file is wrapped in (function(){ ... }()
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
@ -122,16 +141,18 @@ bootFiles.forEach(bootFile=>{
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n"; // "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
// but we need to do this without ever loading everything into RAM as some // but we need to do this without ever loading everything into RAM as some
// boot files seem to be getting pretty big now. // boot files seem to be getting pretty big now.
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset); if (DEBUG) {
fileOffset+=2+bootFile.length+1; require('Storage').write('.boot0',`//${bootFile}\n`,fileOffset);
var bf = require('Storage').read(bootFile); fileOffset+=2+bootFile.length+1;
}
let bf = require('Storage').read(bootFile);
// we can't just write 'bf' in one go because at least in 2v13 and earlier // we can't just write 'bf' in one go because at least in 2v13 and earlier
// Espruino wants to read the whole file into RAM first, and on Bangle.js 1 // Espruino wants to read the whole file into RAM first, and on Bangle.js 1
// it can be too big (especially BTHRM). // it can be too big (especially BTHRM).
var bflen = bf.length; let bflen = bf.length;
var bfoffset = 0; let bfoffset = 0;
while (bflen) { while (bflen) {
var bfchunk = Math.min(bflen, 2048); let bfchunk = Math.min(bflen, 2048);
require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset); require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
fileOffset+=bfchunk; fileOffset+=bfchunk;
bfoffset+=bfchunk; bfoffset+=bfchunk;
@ -139,15 +160,14 @@ bootFiles.forEach(bootFile=>{
} }
require('Storage').write('.boot0',";\n",fileOffset); require('Storage').write('.boot0',";\n",fileOffset);
fileOffset+=2; fileOffset+=2;
if (DEBUG) {
require('Storage').write('.boot0',`print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset);
fileOffset += 48+E.toJS(bootFile).length
}
}); });
require('Storage').write('.boot0',bootPost,fileOffset); require('Storage').write('.boot0',bootPost,fileOffset);
delete boot;
delete bootPost;
delete bootFiles;
delete fileSize;
delete fileOffset;
E.showMessage(/*LANG*/"Reloading..."); E.showMessage(/*LANG*/"Reloading...");
eval(require('Storage').read('.boot0')); }
// .bootcde should be run automatically after if required, since // .bootcde should be run automatically after if required, since
// we normally get called automatically from '.boot0' // we normally get called automatically from '.boot0'
eval(require('Storage').read('.boot0'));

View File

@ -1,7 +1,7 @@
{ {
"id": "boot", "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"version": "0.54", "version": "0.55",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png", "icon": "bootloader.png",
"type": "bootloader", "type": "bootloader",

View File

@ -40,3 +40,4 @@
0.16: Set powerdownRequested correctly on BTHRM power on 0.16: Set powerdownRequested correctly on BTHRM power on
Additional logging on errors Additional logging on errors
Add debug option for disabling active scanning Add debug option for disabling active scanning
0.17: New GUI based on layout library

View File

@ -1,5 +1,5 @@
var intervalInt; const BPM_FONT_SIZE="19%";
var intervalBt; const VALUE_TIMEOUT=3000;
var BODY_LOCS = { var BODY_LOCS = {
0: 'Other', 0: 'Other',
@ -7,46 +7,119 @@ var BODY_LOCS = {
2: 'Wrist', 2: 'Wrist',
3: 'Finger', 3: 'Finger',
4: 'Hand', 4: 'Hand',
5: 'Ear Lobe', 5: 'Earlobe',
6: 'Foot', 6: 'Foot',
};
var Layout = require("Layout");
function border(l,c) {
g.setColor(c).drawLine(l.x+l.w*0.05, l.y-4, l.x+l.w*0.95, l.y-4);
} }
function clear(y){ function getRow(id, text, additionalInfo){
g.reset(); let additional = [];
g.clearRect(0,y,g.getWidth(),y+75); let l = {
} type:"h", c: [
{
function draw(y, type, event) { type:"v",
clear(y); width: g.getWidth()*0.4,
var px = g.getWidth()/2; c: [
var str = event.bpm + ""; {type:"txt", halign:1, font:"8%", label:text, id:id+"text" },
g.reset(); {type:"txt", halign:1, font:BPM_FONT_SIZE, label:"--", id:id, bgCol: g.theme.bg }
g.setFontAlign(0,0); ]
g.setFontVector(40).drawString(str,px,y+20); },{
str = "Event: " + type; type:undefined, fillx:1
if (type === "HRM") { },{
str += " Confidence: " + event.confidence; type:"v",
g.setFontVector(12).drawString(str,px,y+40); valign: -1,
str = " Source: " + (event.src ? event.src : "internal"); width: g.getWidth()*0.45,
g.setFontVector(12).drawString(str,px,y+50); c: additional
},{
type:undefined, width:g.getWidth()*0.05
}
]
};
for (let i of additionalInfo){
let label = {type:"txt", font:"6x8", label:i + ":" };
let value = {type:"txt", font:"6x8", label:"--", id:id + i };
additional.push({type:"h", halign:-1, c:[ label, {type:undefined, fillx:1}, value ]});
} }
if (type === "BTHRM"){
if (event.battery) str += " Bat: " + (event.battery ? event.battery : ""); return l;
g.setFontVector(12).drawString(str,px,y+40); }
str= "";
if (event.location) str += "Loc: " + BODY_LOCS[event.location]; var layout = new Layout( {
if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(","); type:"v", c: [
g.setFontVector(12).drawString(str,px,y+50); getRow("int", "INT", ["Confidence"]),
str= ""; getRow("agg", "HRM", ["Confidence", "Source"]),
if (event.contact) str += " Contact: " + event.contact; getRow("bt", "BT", ["Battery","Location","Contact", "RR", "Energy"]),
if (event.energy) str += " kJoule: " + event.energy.toFixed(0); { type:undefined, height:8 } //dummy to protect debug output
g.setFontVector(12).drawString(str,px,y+60); ]
}, {
lazy:true
});
var int,agg,bt;
var firstEvent = true;
function draw(){
if (!(int || agg || bt)) return;
if (firstEvent) {
g.clearRect(Bangle.appRect);
firstEvent = false;
}
let now = Date.now();
if (int && int.time > (now - VALUE_TIMEOUT)){
layout.int.label = int.bpm;
if (!isNaN(int.confidence)) layout.intConfidence.label = int.confidence;
} else {
layout.int.label = "--";
layout.intConfidence.label = "--";
}
if (agg && agg.time > (now - VALUE_TIMEOUT)){
layout.agg.label = agg.bpm;
if (!isNaN(agg.confidence)) layout.aggConfidence.label = agg.confidence;
if (agg.src) layout.aggSource.label = agg.src;
} else {
layout.agg.label = "--";
layout.aggConfidence.label = "--";
layout.aggSource.label = "--";
}
if (bt && bt.time > (now - VALUE_TIMEOUT)) {
layout.bt.label = bt.bpm;
if (!isNaN(bt.battery)) layout.btBattery.label = bt.battery + "%";
if (bt.rr) layout.btRR.label = bt.rr.join(",");
if (!isNaN(bt.location)) layout.btLocation.label = BODY_LOCS[bt.location];
if (bt.contact !== undefined) layout.btContact.label = bt.contact ? "Yes":"No";
if (!isNaN(bt.energy)) layout.btEnergy.label = bt.energy.toFixed(0) + "kJ";
} else {
layout.bt.label = "--";
layout.btBattery.label = "--";
layout.btRR.label = "--";
layout.btLocation.label = "--";
layout.btContact.label = "--";
layout.btEnergy.label = "--";
}
layout.update();
layout.render();
let first = true;
for (let c of layout.l.c){
if (first) {
first = false;
continue;
}
if (c.type && c.type == "h")
border(c,g.theme.fg);
} }
} }
var firstEventBt = true;
var firstEventInt = true;
// This can get called for the boot code to show what's happening // This can get called for the boot code to show what's happening
function showStatusInfo(txt) { function showStatusInfo(txt) {
@ -57,41 +130,26 @@ function showStatusInfo(txt) {
} }
function onBtHrm(e) { function onBtHrm(e) {
if (firstEventBt){ bt = e;
clear(24); bt.time = Date.now();
firstEventBt = false;
}
draw(100, "BTHRM", e);
if (e.bpm === 0){
Bangle.buzz(100,0.2);
}
if (intervalBt){
clearInterval(intervalBt);
}
intervalBt = setInterval(()=>{
clear(100);
}, 2000);
} }
function onHrm(e) { function onInt(e) {
if (firstEventInt){ int = e;
clear(24); int.time = Date.now();
firstEventInt = false;
}
draw(24, "HRM", e);
if (intervalInt){
clearInterval(intervalInt);
}
intervalInt = setInterval(()=>{
clear(24);
}, 2000);
} }
function onAgg(e) {
agg = e;
agg.time = Date.now();
}
var settings = require('Storage').readJSON("bthrm.json", true) || {}; var settings = require('Storage').readJSON("bthrm.json", true) || {};
Bangle.on('BTHRM', onBtHrm); Bangle.on('BTHRM', onBtHrm);
Bangle.on('HRM', onHrm); Bangle.on('HRM_int', onInt);
Bangle.on('HRM', onAgg);
Bangle.setHRMPower(1,'bthrm'); Bangle.setHRMPower(1,'bthrm');
if (!(settings.startWithHrm)){ if (!(settings.startWithHrm)){
@ -103,10 +161,11 @@ Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
if (Bangle.setBTHRMPower){ if (Bangle.setBTHRMPower){
g.reset().setFont("6x8",2).setFontAlign(0,0); g.reset().setFont("6x8",2).setFontAlign(0,0);
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 24); g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2);
setInterval(draw, 1000);
} else { } else {
g.reset().setFont("6x8",2).setFontAlign(0,0); g.reset().setFont("6x8",2).setFontAlign(0,0);
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2 + 32); g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2);
} }
E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrm')); E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrm'));

View File

@ -553,14 +553,15 @@ exports.enable = () => {
if (settings.replace){ if (settings.replace){
// register a listener for original HRM events and emit as HRM_int // register a listener for original HRM events and emit as HRM_int
Bangle.on("HRM", (e) => { Bangle.on("HRM", (o) => {
e.modified = true; let e = Object.assign({},o);
log("Emitting HRM_int", e); log("Emitting HRM_int", e);
Bangle.emit("HRM_int", e); Bangle.emit("HRM_int", e);
if (fallbackActive){ if (fallbackActive){
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens // if fallback to internal HRM is active, emit as HRM_R to which everyone listens
log("Emitting HRM_R(int)", e); o.src = "int";
Bangle.emit("HRM_R", e); log("Emitting HRM_R(int)", o);
Bangle.emit("HRM_R", o);
} }
}); });
@ -576,6 +577,13 @@ exports.enable = () => {
if (name == "HRM") o("HRM_R", cb); if (name == "HRM") o("HRM_R", cb);
else o(name, cb); else o(name, cb);
})(Bangle.removeListener); })(Bangle.removeListener);
} else {
Bangle.on("HRM", (o)=>{
o.src = "int";
let e = Object.assign({},o);
log("Emitting HRM_int", e);
Bangle.emit("HRM_int", e);
});
} }
Bangle.origSetHRMPower = Bangle.setHRMPower; Bangle.origSetHRMPower = Bangle.setHRMPower;

View File

@ -2,9 +2,10 @@
"id": "bthrm", "id": "bthrm",
"name": "Bluetooth Heart Rate Monitor", "name": "Bluetooth Heart Rate Monitor",
"shortName": "BT HRM", "shortName": "BT HRM",
"version": "0.16", "version": "0.17",
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screen.png"}],
"type": "app", "type": "app",
"tags": "health,bluetooth,hrm,bthrm", "tags": "health,bluetooth,hrm,bthrm",
"supports": ["BANGLEJS","BANGLEJS2"], "supports": ["BANGLEJS","BANGLEJS2"],

BIN
apps/bthrm/screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -7,3 +7,4 @@
0.25: Fixed a bug that would let widgets change the color of the clock. 0.25: Fixed a bug that would let widgets change the color of the clock.
0.26: Time formatted to locale 0.26: Time formatted to locale
0.27: Fixed the timing code, which sometimes did not update for one minute 0.27: Fixed the timing code, which sometimes did not update for one minute
0.28: More config options for cleaner look, enabled fast loading

View File

@ -0,0 +1,6 @@
# New Features:
- Fast load! (only works if your launcher uses widgets)
- widgets, date and weekday are individually configurable
- you can hide widgets, date and weekday for a cleaner look when the watch is locked
Contact me for bug reports or feature requests: ContourClock@gmx.de

View File

@ -1,35 +1,64 @@
var digits = []; {
var drawTimeout; let digits = [];
var fontName=""; let drawTimeout;
var settings = require('Storage').readJSON("contourclock.json", true) || {}; let fontName="";
if (settings.fontIndex==undefined) { let settings = require('Storage').readJSON("contourclock.json", true) || {};
settings.fontIndex=0; if (settings.fontIndex==undefined) {
require('Storage').writeJSON("myapp.json", settings); settings.fontIndex=0;
} settings.widgets=true;
settings.hide=false;
settings.weekday=true;
settings.hideWhenLocked=false;
settings.date=true; require('Storage').writeJSON("myapp.json", settings);
}
function queueDraw() { let queueDraw = function() {
setTimeout(function() { if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
queueDraw();
}, 60000 - (Date.now() % 60000));
};
let draw = function() {
var date = new Date();
// Draw day of the week
g.reset();
if ((!settings.hideWhenLocked) || (!Bangle.isLocked())) {
// Draw day of the week
g.setFont("Teletext10x18Ascii");
g.clearRect(0,138,g.getWidth()-1,176);
if (settings.weekday) g.setFontAlign(0,1).drawString(require("locale").dow(date).toUpperCase(),g.getWidth()/2,g.getHeight()-18);
// Draw Date
if (settings.date) g.setFontAlign(0,1).drawString(require('locale').date(new Date(),1),g.getWidth()/2,g.getHeight());
}
require('contourclock').drawClock(settings.fontIndex);
};
require("FontTeletext10x18Ascii").add(Graphics);
g.clear();
draw();
if (settings.hideWhenLocked) Bangle.on('lock', function (locked) {
if (!locked) require("widget_utils").show();
else {
g.clear();
if (settings.hide) require("widget_utils").swipeOn();
else require("widget_utils").hide();
}
draw(); draw();
queueDraw(); });
}, 60000 - (Date.now() % 60000)); Bangle.setUI({mode:"clock", remove:function() {
if (drawTimeout) clearTimeout(drawTimeout);
if (settings.widgets && settings.hide) require("widget_utils").show();
g.reset();
g.clear();
}});
if (settings.widgets) {
Bangle.loadWidgets();
if (settings.hide) require("widget_utils").swipeOn();
else Bangle.drawWidgets();
}
queueDraw();
} }
function draw() {
var date = new Date();
// Draw day of the week
g.reset();
g.setFont("Teletext10x18Ascii");
g.clearRect(0,138,g.getWidth()-1,176);
g.setFontAlign(0,1).drawString(require("locale").dow(date).toUpperCase(),g.getWidth()/2,g.getHeight()-18);
// Draw Date
g.setFontAlign(0,1).drawString(require('locale').date(new Date(),1),g.getWidth()/2,g.getHeight());
require('contourclock').drawClock(settings.fontIndex);
}
require("FontTeletext10x18Ascii").add(Graphics);
Bangle.setUI("clock");
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
queueDraw();
draw();

View File

@ -1,43 +1,73 @@
(function(back) { (function(back) {
Bangle.removeAllListeners('drag');
Bangle.setUI(""); Bangle.setUI("");
var settings = require('Storage').readJSON('contourclock.json', true) || {}; var settings = require('Storage').readJSON('contourclock.json', true) || {};
if (settings.fontIndex==undefined) { if (settings.fontIndex==undefined) {
settings.fontIndex=0; settings.fontIndex=0;
settings.widgets=true;
settings.hide=false;
settings.weekday=true;
settings.date=true;
settings.hideWhenLocked=false;
require('Storage').writeJSON("myapp.json", settings); require('Storage').writeJSON("myapp.json", settings);
} }
savedIndex=settings.fontIndex; function mainMenu() {
saveListener = setWatch(function() { //save changes and return to settings menu E.showMenu({
require('Storage').writeJSON('contourclock.json', settings); "" : { "title" : "ContourClock" },
Bangle.removeAllListeners('swipe'); "< Back" : () => back(),
Bangle.removeAllListeners('lock'); 'Widgets': {
clearWatch(saveListener); value: (settings.widgets !== undefined ? settings.widgets : true),
g.clear(); onchange : v => {settings.widgets=v; require('Storage').writeJSON('contourclock.json', settings);}
back(); },
}, BTN, { repeat:false, edge:'falling' }); 'hide Widgets': {
lockListener = Bangle.on('lock', function () { //discard changes and return to clock value: (settings.hide !== undefined ? settings.hide : false),
settings.fontIndex=savedIndex; onchange : v => {settings.hide=v; require('Storage').writeJSON('contourclock.json', settings);}
require('Storage').writeJSON('contourclock.json', settings); },
Bangle.removeAllListeners('swipe'); 'Weekday': {
Bangle.removeAllListeners('lock'); value: (settings.weekday !== undefined ? settings.weekday : true),
clearWatch(saveListener); onchange : v => {settings.weekday=v; require('Storage').writeJSON('contourclock.json', settings);}
g.clear(); },
load(); 'Date': {
}); value: (settings.date !== undefined ? settings.date : true),
swipeListener = Bangle.on('swipe', function (direction) { onchange : v => {settings.date=v; require('Storage').writeJSON('contourclock.json', settings);}
var fontName = require('contourclock').drawClock(settings.fontIndex+direction); },
if (fontName) { 'Hide when locked': {
settings.fontIndex+=direction; value: (settings.hideWhenLocked !== undefined ? settings.hideWhenLocked : false),
g.clearRect(0,0,g.getWidth()-1,16); onchange : v => {settings.hideWhenLocked=v; require('Storage').writeJSON('contourclock.json', settings);}
g.setFont('6x8:2x2').setFontAlign(0,-1).drawString(fontName,g.getWidth()/2,0); },
} else { 'set Font': () => fontMenu()
require('contourclock').drawClock(settings.fontIndex); });
} }
}); function fontMenu() {
g.reset(); Bangle.setUI("");
g.clear(); savedIndex=settings.fontIndex;
g.setFont('6x8:2x2').setFontAlign(0,-1); saveListener = setWatch(function() { //save changes and return to settings menu
g.drawString(require('contourclock').drawClock(settings.fontIndex),g.getWidth()/2,0); require('Storage').writeJSON('contourclock.json', settings);
g.drawString('Swipe - change',g.getWidth()/2,g.getHeight()-36); Bangle.removeAllListeners('swipe');
g.drawString('BTN - save',g.getWidth()/2,g.getHeight()-18); Bangle.removeAllListeners('lock');
mainMenu();
}, BTN, { repeat:false, edge:'falling' });
lockListener = Bangle.on('lock', function () { //discard changes and return to clock
settings.fontIndex=savedIndex;
require('Storage').writeJSON('contourclock.json', settings);
Bangle.removeAllListeners('swipe');
Bangle.removeAllListeners('lock');
mainMenu();
});
swipeListener = Bangle.on('swipe', function (direction) {
var fontName = require('contourclock').drawClock(settings.fontIndex+direction);
if (fontName) {
settings.fontIndex+=direction;
g.clearRect(0,g.getHeight()-36,g.getWidth()-1,g.getHeight()-36+16);
g.setFont('6x8:2x2').setFontAlign(0,-1).drawString(fontName,g.getWidth()/2,g.getHeight()-36);
} else {
require('contourclock').drawClock(settings.fontIndex);
}
});
g.reset();
g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1);
g.setFont('6x8:2x2').setFontAlign(0,-1);
g.drawString(require('contourclock').drawClock(settings.fontIndex),g.getWidth()/2,g.getHeight()-36);
g.drawString('Button to save',g.getWidth()/2,g.getHeight()-18);
}
mainMenu();
}) })

View File

@ -1,9 +1,10 @@
{ "id": "contourclock", { "id": "contourclock",
"name": "Contour Clock", "name": "Contour Clock",
"shortName" : "Contour Clock", "shortName" : "Contour Clock",
"version":"0.27", "version":"0.28",
"icon": "app.png", "icon": "app.png",
"description": "A Minimalist clockface with large Digits. Now with more fonts!", "readme": "README.md",
"description": "A Minimalist clockface with large Digits.",
"screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}], "screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}],
"tags": "clock", "tags": "clock",
"custom": "custom.html", "custom": "custom.html",

View File

@ -3,4 +3,5 @@
0.04: Add functionality to sort apps manually or alphabetically ascending/descending. 0.04: Add functionality to sort apps manually or alphabetically ascending/descending.
0.05: Tweaks to help with memory usage 0.05: Tweaks to help with memory usage
0.06: Reduce memory usage 0.06: Reduce memory usage
0.07: Allow negative numbers when manual-sorting 0.07: Allow negative numbers when manual-sorting
0.08: Automatic translation of strings.

View File

@ -3,20 +3,20 @@ const store = require('Storage');
function showMainMenu() { function showMainMenu() {
const mainmenu = { const mainmenu = {
'': { '': {
'title': 'App Manager', 'title': /*LANG*/'App Manager',
}, },
'< Back': ()=> {load();}, '< Back': ()=> {load();},
'Sort Apps': () => showSortAppsMenu(), /*LANG*/'Sort Apps': () => showSortAppsMenu(),
'Manage Apps': ()=> showApps(), /*LANG*/'Manage Apps': ()=> showApps(),
'Compact': () => { /*LANG*/'Compact': () => {
E.showMessage('Compacting...'); E.showMessage(/*LANG*/'Compacting...');
try { try {
store.compact(); store.compact();
} catch (e) { } catch (e) {
} }
showMainMenu(); showMainMenu();
}, },
'Free': { /*LANG*/'Free': {
value: undefined, value: undefined,
format: (v) => { format: (v) => {
return store.getFree(); return store.getFree();
@ -65,13 +65,13 @@ function eraseData(info) {
}); });
} }
function eraseApp(app, files,data) { function eraseApp(app, files,data) {
E.showMessage('Erasing\n' + app.name + '...'); E.showMessage(/*LANG*/'Erasing\n' + app.name + '...');
var info = store.readJSON(app.id + ".info", 1)||{}; var info = store.readJSON(app.id + ".info", 1)||{};
if (files) eraseFiles(info); if (files) eraseFiles(info);
if (data) eraseData(info); if (data) eraseData(info);
} }
function eraseOne(app, files,data){ function eraseOne(app, files,data){
E.showPrompt('Erase\n'+app.name+'?').then((v) => { E.showPrompt(/*LANG*/'Erase\n'+app.name+'?').then((v) => {
if (v) { if (v) {
Bangle.buzz(100, 1); Bangle.buzz(100, 1);
eraseApp(app, files, data); eraseApp(app, files, data);
@ -82,7 +82,7 @@ function eraseOne(app, files,data){
}); });
} }
function eraseAll(apps, files,data) { function eraseAll(apps, files,data) {
E.showPrompt('Erase all?').then((v) => { E.showPrompt(/*LANG*/'Erase all?').then((v) => {
if (v) { if (v) {
Bangle.buzz(100, 1); Bangle.buzz(100, 1);
apps.forEach(app => eraseApp(app, files, data)); apps.forEach(app => eraseApp(app, files, data));
@ -99,11 +99,11 @@ function showAppMenu(app) {
'< Back': () => showApps(), '< Back': () => showApps(),
}; };
if (app.hasData) { if (app.hasData) {
appmenu['Erase Completely'] = () => eraseOne(app, true, true); appmenu[/*LANG*/'Erase Completely'] = () => eraseOne(app, true, true);
appmenu['Erase App,Keep Data'] = () => eraseOne(app, true, false); appmenu[/*LANG*/'Erase App,Keep Data'] = () => eraseOne(app, true, false);
appmenu['Only Erase Data'] = () => eraseOne(app, false, true); appmenu[/*LANG*/'Only Erase Data'] = () => eraseOne(app, false, true);
} else { } else {
appmenu['Erase'] = () => eraseOne(app, true, false); appmenu[/*LANG*/'Erase'] = () => eraseOne(app, true, false);
} }
E.showMenu(appmenu); E.showMenu(appmenu);
} }
@ -111,7 +111,7 @@ function showAppMenu(app) {
function showApps() { function showApps() {
const appsmenu = { const appsmenu = {
'': { '': {
'title': 'Apps', 'title': /*LANG*/'Apps',
}, },
'< Back': () => showMainMenu(), '< Back': () => showMainMenu(),
}; };
@ -128,17 +128,17 @@ function showApps() {
menu[app.name] = () => showAppMenu(app); menu[app.name] = () => showAppMenu(app);
return menu; return menu;
}, appsmenu); }, appsmenu);
appsmenu['Erase All'] = () => { appsmenu[/*LANG*/'Erase All'] = () => {
E.showMenu({ E.showMenu({
'': {'title': 'Erase All'}, '': {'title': /*LANG*/'Erase All'},
'Erase Everything': () => eraseAll(list, true, true), /*LANG*/'Erase Everything': () => eraseAll(list, true, true),
'Erase Apps,Keep Data': () => eraseAll(list, true, false), /*LANG*/'Erase Apps,Keep Data': () => eraseAll(list, true, false),
'Only Erase Data': () => eraseAll(list, false, true), /*LANG*/'Only Erase Data': () => eraseAll(list, false, true),
'< Back': () => showApps(), '< Back': () => showApps(),
}); });
}; };
} else { } else {
appsmenu['...No Apps...'] = { appsmenu[/*LANG*/'...No Apps...'] = {
value: undefined, value: undefined,
format: ()=> '', format: ()=> '',
onchange: ()=> {} onchange: ()=> {}
@ -150,16 +150,16 @@ function showApps() {
function showSortAppsMenu() { function showSortAppsMenu() {
const sorterMenu = { const sorterMenu = {
'': { '': {
'title': 'App Sorter', 'title': /*LANG*/'App Sorter',
}, },
'< Back': () => showMainMenu(), '< Back': () => showMainMenu(),
'Sort: manually': ()=> showSortAppsManually(), /*LANG*/'Sort: manually': ()=> showSortAppsManually(),
'Sort: alph. ASC': () => { /*LANG*/'Sort: alph. ASC': () => {
E.showMessage('Sorting:\nAlphabetically\nascending ...'); E.showMessage(/*LANG*/'Sorting:\nAlphabetically\nascending ...');
sortAlphabet(false); sortAlphabet(false);
}, },
'Sort: alph. DESC': () => { 'Sort: alph. DESC': () => {
E.showMessage('Sorting:\nAlphabetically\ndescending ...'); E.showMessage(/*LANG*/'Sorting:\nAlphabetically\ndescending ...');
sortAlphabet(true); sortAlphabet(true);
} }
}; };
@ -169,7 +169,7 @@ function showSortAppsMenu() {
function showSortAppsManually() { function showSortAppsManually() {
const appsSorterMenu = { const appsSorterMenu = {
'': { '': {
'title': 'Sort: manually', 'title': /*LANG*/'Sort: manually',
}, },
'< Back': () => showSortAppsMenu(), '< Back': () => showSortAppsMenu(),
}; };
@ -186,7 +186,7 @@ function showSortAppsManually() {
return menu; return menu;
}, appsSorterMenu); }, appsSorterMenu);
} else { } else {
appsSorterMenu['...No Apps...'] = { appsSorterMenu[/*LANG*/'...No Apps...'] = {
value: undefined, value: undefined,
format: ()=> '', format: ()=> '',
onchange: ()=> {} onchange: ()=> {}

View File

@ -1,7 +1,7 @@
{ {
"id": "files", "id": "files",
"name": "App Manager", "name": "App Manager",
"version": "0.07", "version": "0.08",
"description": "Show currently installed apps, free space, and allow their deletion from the watch", "description": "Show currently installed apps, free space, and allow their deletion from the watch",
"icon": "files.png", "icon": "files.png",
"tags": "tool,system,files", "tags": "tool,system,files",

View File

@ -13,3 +13,4 @@
Reconstruct battery voltage by using calibrated batFullVoltage Reconstruct battery voltage by using calibrated batFullVoltage
Averaging for smoothing compass headings Averaging for smoothing compass headings
Save state if route or waypoint has been chosen Save state if route or waypoint has been chosen
0.09: Workaround a minifier issue allowing to install gpstrek with minification enabled

View File

@ -259,7 +259,8 @@ let getCompassSlice = function(compassDataSource){
if (compassDataSource.getPoints){ if (compassDataSource.getPoints){
for (let p of compassDataSource.getPoints()){ let points = compassDataSource.getPoints(); //storing this in a variable works around a minifier bug causing a problem in the next line: for(let a of a.getPoints())
for (let p of points){
g.reset(); g.reset();
var bpos = p.bearing - lastDrawnValue; var bpos = p.bearing - lastDrawnValue;
if (bpos>180) bpos -=360; if (bpos>180) bpos -=360;
@ -285,7 +286,8 @@ let getCompassSlice = function(compassDataSource){
} }
} }
if (compassDataSource.getMarkers){ if (compassDataSource.getMarkers){
for (let m of compassDataSource.getMarkers()){ let markers = compassDataSource.getMarkers(); //storing this in a variable works around a minifier bug causing a problem in the next line: for(let a of a.getMarkers())
for (let m of markers){
g.reset(); g.reset();
g.setColor(m.fillcolor); g.setColor(m.fillcolor);
let mpos = m.xpos * width; let mpos = m.xpos * width;

View File

@ -1,7 +1,7 @@
{ {
"id": "gpstrek", "id": "gpstrek",
"name": "GPS Trekking", "name": "GPS Trekking",
"version": "0.08", "version": "0.09",
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!", "description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
"icon": "icon.png", "icon": "icon.png",
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}], "screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],

View File

@ -2,4 +2,5 @@
0.02: Includeas the ha.lib.js library that can be used by other apps or clocks. 0.02: Includeas the ha.lib.js library that can be used by other apps or clocks.
0.03: Added clkinfo for clocks. 0.03: Added clkinfo for clocks.
0.04: Feedback if clkinfo run is called. 0.04: Feedback if clkinfo run is called.
0.05: Clkinfo improvements. 0.05: Clkinfo improvements.
0.06: Updated clkinfo icon.

View File

@ -4,7 +4,7 @@
var haItems = { var haItems = {
name: "Home", name: "Home",
img: atob("GBiBAf/////////n///D//+B//8A//48T/wkD/gkD/A8D+AYB8AYA4eZ4QyZMOyZN+fb5+D/B+B+B+A8B+AYB+AYB+AYB+AYB+A8Bw=="), img: atob("GBiBAAAAAAAAAAAAAAAYAAA+AAB+AADD4AHb4APD4Afn8A/n+BxmOD0mnA0ksAwAMA+B8A/D8A/n8A/n8A/n8A/n8AAAAAAAAAAAAA=="),
items: [] items: []
}; };

View File

@ -1,7 +1,7 @@
{ {
"id": "ha", "id": "ha",
"name": "HomeAssistant", "name": "HomeAssistant",
"version": "0.05", "version": "0.06",
"description": "Integrates your BangleJS into HomeAssistant.", "description": "Integrates your BangleJS into HomeAssistant.",
"icon": "ha.png", "icon": "ha.png",
"type": "app", "type": "app",

View File

@ -8,3 +8,4 @@
0.08: Don't force backlight on/watch unlocked on Bangle 2 0.08: Don't force backlight on/watch unlocked on Bangle 2
0.09: Grey out BPM until confidence is over 50% 0.09: Grey out BPM until confidence is over 50%
0.10: Autoscale raw graph to maximum value seen 0.10: Autoscale raw graph to maximum value seen
0.11: Automatic translation of strings.

View File

@ -37,7 +37,7 @@ function updateHrm(){
var px = g.getWidth()/2; var px = g.getWidth()/2;
g.setFontAlign(0,-1); g.setFontAlign(0,-1);
g.clearRect(0,24,g.getWidth(),80); g.clearRect(0,24,g.getWidth(),80);
g.setFont("6x8").drawString("Confidence "+(hrmInfo.confidence || "--")+"%", px, 70); g.setFont("6x8").drawString(/*LANG*/"Confidence "+(hrmInfo.confidence || "--")+"%", px, 70);
updateScale(); updateScale();
@ -46,7 +46,7 @@ function updateHrm(){
g.setFontVector(40).setColor(hrmInfo.confidence > 50 ? g.theme.fg : "#888").drawString(str,px,45); g.setFontVector(40).setColor(hrmInfo.confidence > 50 ? g.theme.fg : "#888").drawString(str,px,45);
px += g.stringWidth(str)/2; px += g.stringWidth(str)/2;
g.setFont("6x8").setColor(g.theme.fg); g.setFont("6x8").setColor(g.theme.fg);
g.drawString("BPM",px+15,45); g.drawString(/*LANG*/"BPM",px+15,45);
} }
function updateScale(){ function updateScale(){
@ -101,7 +101,7 @@ Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
g.reset().setFont("6x8",2).setFontAlign(0,-1); g.reset().setFont("6x8",2).setFontAlign(0,-1);
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); g.drawString(/*LANG*/"Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
countDown(); countDown();

View File

@ -1,7 +1,7 @@
{ {
"id": "hrm", "id": "hrm",
"name": "Heart Rate Monitor", "name": "Heart Rate Monitor",
"version": "0.10", "version": "0.11",
"description": "Measure your heart rate and see live sensor data", "description": "Measure your heart rate and see live sensor data",
"icon": "heartrate.png", "icon": "heartrate.png",
"tags": "health", "tags": "health",

View File

@ -627,7 +627,7 @@ s.pl = {};
Bangle.drawWidgets = ()=>{}; Bangle.drawWidgets = ()=>{};
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets = orig; Bangle.drawWidgets = orig;
require("widget_utils").swipeOn(); require("widget_utils").swipeOn(0);
Bangle.drawWidgets(); Bangle.drawWidgets();
} }
}).catch((e)=>{ }).catch((e)=>{

View File

@ -3,3 +3,4 @@
0.03: Positioning of marker now takes the height of the widget field into account. 0.03: Positioning of marker now takes the height of the widget field into account.
0.04: Fix issue if going back without typing. 0.04: Fix issue if going back without typing.
0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat. 0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat.
0.06: Support input of numbers and uppercase characters.

View File

@ -4,6 +4,10 @@ A library that provides the ability to input text by swiping PalmOS Graffiti-sty
To get a legend of available characters, just tap the screen. To get a legend of available characters, just tap the screen.
To switch between the input of alphabetic and numeric characters tap the widget which displays either "123" or "ABC".
To switch between lowercase and uppercase characters do an up swipe.
![](key.png) ![](key.png)
## Usage ## Usage

View File

@ -1,47 +1,68 @@
exports.INPUT_MODE_ALPHA = 0;
exports.INPUT_MODE_NUM = 1;
/* To make your own strokes, type: /* To make your own strokes, type:
Bangle.on('stroke',print) Bangle.on('stroke',print)
on the left of the IDE, then do a stroke and copy out the Uint8Array line on the left of the IDE, then do a stroke and copy out the Uint8Array line
*/ */
exports.getStrokes = function(cb) { exports.getStrokes = function(mode, cb) {
cb("a", new Uint8Array([58, 159, 58, 155, 62, 144, 69, 127, 77, 106, 86, 90, 94, 77, 101, 68, 108, 62, 114, 59, 121, 59, 133, 61, 146, 70, 158, 88, 169, 107, 176, 124, 180, 135, 183, 144, 185, 152])); if (mode === exports.INPUT_MODE_ALPHA) {
cb("b", new Uint8Array([51, 47, 51, 77, 56, 123, 60, 151, 65, 163, 68, 164, 68, 144, 67, 108, 67, 76, 72, 43, 104, 51, 121, 74, 110, 87, 109, 95, 131, 117, 131, 140, 109, 152, 88, 157])); cb("a", new Uint8Array([58, 159, 58, 155, 62, 144, 69, 127, 77, 106, 86, 90, 94, 77, 101, 68, 108, 62, 114, 59, 121, 59, 133, 61, 146, 70, 158, 88, 169, 107, 176, 124, 180, 135, 183, 144, 185, 152]));
cb("c", new Uint8Array([153, 62, 150, 62, 145, 62, 136, 62, 123, 62, 106, 65, 85, 70, 65, 75, 50, 82, 42, 93, 37, 106, 36, 119, 36, 130, 40, 140, 49, 147, 61, 153, 72, 156, 85, 157, 106, 158, 116, 158])); cb("b", new Uint8Array([51, 47, 51, 77, 56, 123, 60, 151, 65, 163, 68, 164, 68, 144, 67, 108, 67, 76, 72, 43, 104, 51, 121, 74, 110, 87, 109, 95, 131, 117, 131, 140, 109, 152, 88, 157]));
cb("d", new Uint8Array([57, 178, 57, 176, 55, 171, 52, 163, 50, 154, 49, 146, 47, 135, 45, 121, 44, 108, 44, 97, 44, 85, 44, 75, 44, 66, 44, 58, 44, 48, 44, 38, 46, 31, 48, 26, 58, 21, 75, 20, 99, 26, 120, 35, 136, 51, 144, 70, 144, 88, 137, 110, 124, 131, 106, 145, 88, 153])); cb("c", new Uint8Array([153, 62, 150, 62, 145, 62, 136, 62, 123, 62, 106, 65, 85, 70, 65, 75, 50, 82, 42, 93, 37, 106, 36, 119, 36, 130, 40, 140, 49, 147, 61, 153, 72, 156, 85, 157, 106, 158, 116, 158]));
cb("e", new Uint8Array([150, 72, 141, 69, 114, 68, 79, 69, 48, 77, 32, 81, 31, 85, 46, 91, 73, 95, 107, 100, 114, 103, 83, 117, 58, 134, 66, 143, 105, 148, 133, 148, 144, 148])); cb("d", new Uint8Array([57, 178, 57, 176, 55, 171, 52, 163, 50, 154, 49, 146, 47, 135, 45, 121, 44, 108, 44, 97, 44, 85, 44, 75, 44, 66, 44, 58, 44, 48, 44, 38, 46, 31, 48, 26, 58, 21, 75, 20, 99, 26, 120, 35, 136, 51, 144, 70, 144, 88, 137, 110, 124, 131, 106, 145, 88, 153]));
cb("f", new Uint8Array([157, 52, 155, 52, 148, 52, 137, 52, 124, 52, 110, 52, 96, 52, 83, 52, 74, 52, 67, 52, 61, 52, 57, 52, 55, 52, 52, 52, 52, 54, 52, 58, 52, 64, 54, 75, 58, 97, 59, 117, 60, 130])); cb("e", new Uint8Array([150, 72, 141, 69, 114, 68, 79, 69, 48, 77, 32, 81, 31, 85, 46, 91, 73, 95, 107, 100, 114, 103, 83, 117, 58, 134, 66, 143, 105, 148, 133, 148, 144, 148]));
cb("g", new Uint8Array([160, 66, 153, 62, 129, 58, 90, 56, 58, 57, 38, 65, 31, 86, 43, 125, 69, 152, 116, 166, 145, 154, 146, 134, 112, 116, 85, 108, 97, 106, 140, 106, 164, 106])); cb("f", new Uint8Array([157, 52, 155, 52, 148, 52, 137, 52, 124, 52, 110, 52, 96, 52, 83, 52, 74, 52, 67, 52, 61, 52, 57, 52, 55, 52, 52, 52, 52, 54, 52, 58, 52, 64, 54, 75, 58, 97, 59, 117, 60, 130]));
cb("h", new Uint8Array([58, 50, 58, 55, 58, 64, 58, 80, 58, 102, 58, 122, 58, 139, 58, 153, 58, 164, 58, 171, 58, 177, 58, 179, 58, 181, 58, 180, 58, 173, 58, 163, 59, 154, 61, 138, 64, 114, 68, 95, 72, 84, 80, 79, 91, 79, 107, 82, 123, 93, 137, 111, 145, 130, 149, 147, 150, 154, 150, 159])); cb("g", new Uint8Array([160, 66, 153, 62, 129, 58, 90, 56, 58, 57, 38, 65, 31, 86, 43, 125, 69, 152, 116, 166, 145, 154, 146, 134, 112, 116, 85, 108, 97, 106, 140, 106, 164, 106]));
cb("i", new Uint8Array([89, 48, 89, 49, 89, 51, 89, 55, 89, 60, 89, 68, 89, 78, 89, 91, 89, 103, 89, 114, 89, 124, 89, 132, 89, 138, 89, 144, 89, 148, 89, 151, 89, 154, 89, 156, 89, 157, 89, 158])); cb("h", new Uint8Array([58, 50, 58, 55, 58, 64, 58, 80, 58, 102, 58, 122, 58, 139, 58, 153, 58, 164, 58, 171, 58, 177, 58, 179, 58, 181, 58, 180, 58, 173, 58, 163, 59, 154, 61, 138, 64, 114, 68, 95, 72, 84, 80, 79, 91, 79, 107, 82, 123, 93, 137, 111, 145, 130, 149, 147, 150, 154, 150, 159]));
cb("j", new Uint8Array([130, 57, 130, 61, 130, 73, 130, 91, 130, 113, 130, 133, 130, 147, 130, 156, 130, 161, 130, 164, 130, 166, 129, 168, 127, 168, 120, 168, 110, 168, 91, 167, 81, 167, 68, 167])); cb("i", new Uint8Array([89, 48, 89, 49, 89, 51, 89, 55, 89, 60, 89, 68, 89, 78, 89, 91, 89, 103, 89, 114, 89, 124, 89, 132, 89, 138, 89, 144, 89, 148, 89, 151, 89, 154, 89, 156, 89, 157, 89, 158]));
cb("k", new Uint8Array([149, 63, 147, 68, 143, 76, 136, 89, 126, 106, 114, 123, 100, 136, 86, 147, 72, 153, 57, 155, 45, 152, 36, 145, 29, 131, 26, 117, 26, 104, 27, 93, 30, 86, 35, 80, 45, 77, 62, 80, 88, 96, 113, 116, 130, 131, 140, 142, 145, 149, 148, 153])); cb("j", new Uint8Array([130, 57, 130, 61, 130, 73, 130, 91, 130, 113, 130, 133, 130, 147, 130, 156, 130, 161, 130, 164, 130, 166, 129, 168, 127, 168, 120, 168, 110, 168, 91, 167, 81, 167, 68, 167]));
cb("l", new Uint8Array([42, 55, 42, 59, 42, 69, 44, 87, 44, 107, 44, 128, 44, 143, 44, 156, 44, 163, 44, 167, 44, 169, 45, 170, 49, 170, 59, 169, 76, 167, 100, 164, 119, 162, 139, 160, 163, 159])); cb("k", new Uint8Array([149, 63, 147, 68, 143, 76, 136, 89, 126, 106, 114, 123, 100, 136, 86, 147, 72, 153, 57, 155, 45, 152, 36, 145, 29, 131, 26, 117, 26, 104, 27, 93, 30, 86, 35, 80, 45, 77, 62, 80, 88, 96, 113, 116, 130, 131, 140, 142, 145, 149, 148, 153]));
cb("m", new Uint8Array([49, 165, 48, 162, 46, 156, 44, 148, 42, 138, 42, 126, 42, 113, 43, 101, 45, 91, 47, 82, 49, 75, 51, 71, 54, 70, 57, 70, 61, 74, 69, 81, 75, 91, 84, 104, 94, 121, 101, 132, 103, 137, 106, 130, 110, 114, 116, 92, 125, 75, 134, 65, 139, 62, 144, 66, 148, 83, 151, 108, 155, 132, 157, 149])); cb("l", new Uint8Array([42, 55, 42, 59, 42, 69, 44, 87, 44, 107, 44, 128, 44, 143, 44, 156, 44, 163, 44, 167, 44, 169, 45, 170, 49, 170, 59, 169, 76, 167, 100, 164, 119, 162, 139, 160, 163, 159]));
cb("n", new Uint8Array([50, 165, 50, 160, 50, 153, 50, 140, 50, 122, 50, 103, 50, 83, 50, 65, 50, 52, 50, 45, 50, 43, 52, 52, 57, 67, 66, 90, 78, 112, 93, 131, 104, 143, 116, 152, 127, 159, 135, 160, 141, 150, 148, 125, 154, 96, 158, 71, 161, 56, 162, 49])); cb("m", new Uint8Array([49, 165, 48, 162, 46, 156, 44, 148, 42, 138, 42, 126, 42, 113, 43, 101, 45, 91, 47, 82, 49, 75, 51, 71, 54, 70, 57, 70, 61, 74, 69, 81, 75, 91, 84, 104, 94, 121, 101, 132, 103, 137, 106, 130, 110, 114, 116, 92, 125, 75, 134, 65, 139, 62, 144, 66, 148, 83, 151, 108, 155, 132, 157, 149]));
cb("o", new Uint8Array([107, 58, 104, 58, 97, 61, 87, 68, 75, 77, 65, 88, 58, 103, 54, 116, 53, 126, 55, 135, 61, 143, 75, 149, 91, 150, 106, 148, 119, 141, 137, 125, 143, 115, 146, 104, 146, 89, 142, 78, 130, 70, 116, 65, 104, 62])); cb("n", new Uint8Array([50, 165, 50, 160, 50, 153, 50, 140, 50, 122, 50, 103, 50, 83, 50, 65, 50, 52, 50, 45, 50, 43, 52, 52, 57, 67, 66, 90, 78, 112, 93, 131, 104, 143, 116, 152, 127, 159, 135, 160, 141, 150, 148, 125, 154, 96, 158, 71, 161, 56, 162, 49]));
cb("p", new Uint8Array([52, 59, 52, 64, 54, 73, 58, 88, 61, 104, 65, 119, 67, 130, 69, 138, 71, 145, 71, 147, 71, 148, 71, 143, 70, 133, 68, 120, 67, 108, 67, 97, 67, 89, 68, 79, 72, 67, 83, 60, 99, 58, 118, 58, 136, 63, 146, 70, 148, 77, 145, 84, 136, 91, 121, 95, 106, 97, 93, 97, 82, 97])); cb("o", new Uint8Array([107, 58, 104, 58, 97, 61, 87, 68, 75, 77, 65, 88, 58, 103, 54, 116, 53, 126, 55, 135, 61, 143, 75, 149, 91, 150, 106, 148, 119, 141, 137, 125, 143, 115, 146, 104, 146, 89, 142, 78, 130, 70, 116, 65, 104, 62]));
cb("q", new Uint8Array([95, 59, 93, 59, 88, 59, 79, 59, 68, 61, 57, 67, 50, 77, 48, 89, 48, 103, 50, 117, 55, 130, 65, 140, 76, 145, 85, 146, 94, 144, 101, 140, 105, 136, 106, 127, 106, 113, 100, 98, 92, 86, 86, 79, 84, 75, 84, 72, 91, 69, 106, 67, 126, 67, 144, 67, 158, 67, 168, 67, 173, 67, 177, 67])); cb("p", new Uint8Array([29, 47, 29, 55, 29, 75, 29, 110, 29, 145, 29, 165, 29, 172, 29, 164, 30, 149, 37, 120, 50, 91, 61, 74, 72, 65, 85, 61, 103, 61, 118, 63, 126, 69, 129, 76, 130, 87, 126, 98, 112, 108, 97, 114, 87, 116]));
cb("r", new Uint8Array([53, 49, 53, 62, 53, 91, 53, 127, 53, 146, 53, 147, 53, 128, 53, 94, 53, 69, 62, 44, 82, 42, 94, 50, 92, 68, 82, 85, 77, 93, 80, 102, 95, 119, 114, 134, 129, 145, 137, 150])); cb("q", new Uint8Array([95, 59, 93, 59, 88, 59, 79, 59, 68, 61, 57, 67, 50, 77, 48, 89, 48, 103, 50, 117, 55, 130, 65, 140, 76, 145, 85, 146, 94, 144, 101, 140, 105, 136, 106, 127, 106, 113, 100, 98, 92, 86, 86, 79, 84, 75, 84, 72, 91, 69, 106, 67, 126, 67, 144, 67, 158, 67, 168, 67, 173, 67, 177, 67]));
cb("s", new Uint8Array([159, 72, 157, 70, 155, 68, 151, 66, 145, 63, 134, 60, 121, 58, 108, 56, 96, 55, 83, 55, 73, 55, 64, 56, 57, 60, 52, 65, 49, 71, 49, 76, 50, 81, 55, 87, 71, 94, 94, 100, 116, 104, 131, 108, 141, 114, 145, 124, 142, 135, 124, 146, 97, 153, 70, 157, 52, 158])); cb("r", new Uint8Array([53, 49, 53, 62, 53, 91, 53, 127, 53, 146, 53, 147, 53, 128, 53, 94, 53, 69, 62, 44, 82, 42, 94, 50, 92, 68, 82, 85, 77, 93, 80, 102, 95, 119, 114, 134, 129, 145, 137, 150]));
cb("t", new Uint8Array([45, 55, 48, 55, 55, 55, 72, 55, 96, 55, 120, 55, 136, 55, 147, 55, 152, 55, 155, 55, 157, 55, 158, 56, 158, 60, 156, 70, 154, 86, 151, 102, 150, 114, 148, 125, 148, 138, 148, 146])); cb("s", new Uint8Array([159, 72, 157, 70, 155, 68, 151, 66, 145, 63, 134, 60, 121, 58, 108, 56, 96, 55, 83, 55, 73, 55, 64, 56, 57, 60, 52, 65, 49, 71, 49, 76, 50, 81, 55, 87, 71, 94, 94, 100, 116, 104, 131, 108, 141, 114, 145, 124, 142, 135, 124, 146, 97, 153, 70, 157, 52, 158]));
cb("u", new Uint8Array([35, 52, 35, 59, 35, 73, 35, 90, 36, 114, 38, 133, 42, 146, 49, 153, 60, 157, 73, 158, 86, 156, 100, 152, 112, 144, 121, 131, 127, 114, 132, 97, 134, 85, 135, 73, 136, 61, 136, 56])); cb("t", new Uint8Array([45, 55, 48, 55, 55, 55, 72, 55, 96, 55, 120, 55, 136, 55, 147, 55, 152, 55, 155, 55, 157, 55, 158, 56, 158, 60, 156, 70, 154, 86, 151, 102, 150, 114, 148, 125, 148, 138, 148, 146]));
cb("v", new Uint8Array([36, 55, 37, 59, 40, 68, 45, 83, 51, 100, 58, 118, 64, 132, 69, 142, 71, 149, 73, 156, 76, 158, 77, 160, 77, 159, 80, 151, 82, 137, 84, 122, 86, 111, 90, 91, 91, 78, 91, 68, 91, 63, 92, 61, 97, 61, 111, 61, 132, 61, 150, 61, 162, 61])); cb("u", new Uint8Array([35, 52, 35, 59, 35, 73, 35, 90, 36, 114, 38, 133, 42, 146, 49, 153, 60, 157, 73, 158, 86, 156, 100, 152, 112, 144, 121, 131, 127, 114, 132, 97, 134, 85, 135, 73, 136, 61, 136, 56]));
cb("w", new Uint8Array([33, 58, 34, 81, 39, 127, 44, 151, 48, 161, 52, 162, 57, 154, 61, 136, 65, 115, 70, 95, 76, 95, 93, 121, 110, 146, 119, 151, 130, 129, 138, 84, 140, 56, 140, 45])); cb("v", new Uint8Array([36, 55, 37, 59, 40, 68, 45, 83, 51, 100, 58, 118, 64, 132, 69, 142, 71, 149, 73, 156, 76, 158, 77, 160, 77, 159, 80, 151, 82, 137, 84, 122, 86, 111, 90, 91, 91, 78, 91, 68, 91, 63, 92, 61, 97, 61, 111, 61, 132, 61, 150, 61, 162, 61]));
cb("x", new Uint8Array([56, 63, 56, 67, 57, 74, 60, 89, 66, 109, 74, 129, 85, 145, 96, 158, 107, 164, 117, 167, 128, 164, 141, 155, 151, 140, 159, 122, 166, 105, 168, 89, 170, 81, 170, 73, 169, 66, 161, 63, 141, 68, 110, 83, 77, 110, 55, 134, 47, 145])); cb("w", new Uint8Array([25, 46, 25, 82, 25, 119, 33, 143, 43, 153, 60, 147, 73, 118, 75, 91, 76, 88, 85, 109, 96, 134, 107, 143, 118, 137, 129, 112, 134, 81, 134, 64, 134, 55]));
cb("y", new Uint8Array([42, 56, 42, 70, 48, 97, 62, 109, 85, 106, 109, 90, 126, 65, 134, 47, 137, 45, 137, 75, 127, 125, 98, 141, 70, 133, 65, 126, 92, 137, 132, 156, 149, 166])); cb("x", new Uint8Array([56, 63, 56, 67, 57, 74, 60, 89, 66, 109, 74, 129, 85, 145, 96, 158, 107, 164, 117, 167, 128, 164, 141, 155, 151, 140, 159, 122, 166, 105, 168, 89, 170, 81, 170, 73, 169, 66, 161, 63, 141, 68, 110, 83, 77, 110, 55, 134, 47, 145]));
cb("z", new Uint8Array([29, 62, 35, 62, 43, 62, 63, 62, 87, 62, 110, 62, 125, 62, 134, 62, 138, 62, 136, 63, 122, 68, 103, 77, 85, 91, 70, 107, 59, 120, 50, 132, 47, 138, 43, 143, 41, 148, 42, 151, 53, 155, 80, 157, 116, 158, 146, 158, 163, 158])); cb("y", new Uint8Array([30, 41, 30, 46, 30, 52, 30, 63, 30, 79, 33, 92, 38, 100, 47, 104, 54, 107, 66, 105, 79, 94, 88, 82, 92, 74, 94, 77, 96, 98, 96, 131, 94, 151, 91, 164, 85, 171, 75, 171, 71, 162, 74, 146, 84, 130, 95, 119, 106, 113]));
cb("z", new Uint8Array([29, 62, 35, 62, 43, 62, 63, 62, 87, 62, 110, 62, 125, 62, 134, 62, 138, 62, 136, 63, 122, 68, 103, 77, 85, 91, 70, 107, 59, 120, 50, 132, 47, 138, 43, 143, 41, 148, 42, 151, 53, 155, 80, 157, 116, 158, 146, 158, 163, 158]));
cb("SHIFT", new Uint8Array([100, 160, 100, 50]));
} else if (mode === exports.INPUT_MODE_NUM) {
cb("0", new Uint8Array([82, 50, 76, 50, 67, 50, 59, 50, 50, 51, 43, 57, 38, 68, 34, 83, 33, 95, 33, 108, 34, 121, 42, 136, 57, 148, 72, 155, 85, 157, 98, 155, 110, 149, 120, 139, 128, 127, 134, 119, 137, 114, 138, 107, 138, 98, 138, 88, 138, 77, 137, 71, 134, 65, 128, 60, 123, 58]));
cb("1", new Uint8Array([100, 50, 100, 160]));
cb("2", new Uint8Array([40, 79, 46, 74, 56, 66, 68, 58, 77, 49, 87, 45, 100, 45, 111, 46, 119, 50, 128, 58, 133, 71, 130, 88, 120, 106, 98, 128, 69, 150, 50, 162, 42, 167, 43, 168, 58, 169, 78, 170, 93, 170, 103, 170, 109, 170]));
cb("3", new Uint8Array([47, 65, 51, 60, 57, 56, 65, 51, 74, 47, 84, 45, 93, 45, 102, 45, 109, 46, 122, 51, 129, 58, 130, 65, 127, 74, 120, 85, 112, 92, 107, 96, 112, 101, 117, 105, 125, 113, 128, 123, 127, 134, 122, 145, 108, 156, 91, 161, 70, 163, 55, 163]));
cb("4", new Uint8Array([37, 58, 37, 60, 37, 64, 37, 69, 37, 75, 37, 86, 37, 96, 37, 105, 37, 112, 37, 117, 37, 122, 37, 126, 37, 128, 38, 129, 40, 129, 45, 129, 48, 129, 53, 129, 67, 129, 85, 129, 104, 129, 119, 129, 129, 129, 136, 129]));
cb("5", new Uint8Array([142, 60, 119, 60, 79, 60, 45, 60, 37, 64, 37, 86, 37, 103, 47, 107, 66, 106, 81, 103, 97, 103, 116, 103, 129, 108, 131, 130, 122, 152, 101, 168, 85, 172, 70, 172, 59, 172]));
cb("6", new Uint8Array([136, 54, 135, 49, 129, 47, 114, 47, 89, 54, 66, 66, 50, 81, 39, 95, 35, 109, 34, 128, 38, 145, 52, 158, 81, 164, 114, 157, 133, 139, 136, 125, 132, 118, 120, 115, 102, 117, 85, 123]));
cb("7", new Uint8Array([47, 38, 48, 38, 53, 38, 66, 38, 85, 38, 103, 38, 117, 38, 125, 38, 129, 38, 134, 41, 135, 47, 135, 54, 135, 66, 131, 93, 124, 126, 116, 149, 109, 161, 105, 168]));
cb("8", new Uint8Array([122, 61, 102, 61, 83, 61, 60, 61, 47, 62, 45, 78, 58, 99, 84, 112, 105, 122, 118, 134, 121, 149, 113, 165, 86, 171, 59, 171, 47, 164, 45, 144, 50, 132, 57, 125, 67, 117, 78, 109, 87, 102, 96, 94, 105, 86, 113, 85]));
cb("9", new Uint8Array([122, 58, 117, 55, 112, 51, 104, 51, 95, 51, 86, 51, 77, 51, 68, 51, 60, 51, 54, 56, 47, 64, 46, 77, 46, 89, 46, 96, 51, 103, 64, 109, 74, 110, 83, 110, 94, 107, 106, 102, 116, 94, 124, 84, 127, 79, 128, 78, 128, 94, 128, 123, 128, 161, 128, 175]));
}
cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103])); cb("\b", new Uint8Array([183, 103, 182, 103, 180, 103, 176, 103, 169, 103, 159, 103, 147, 103, 133, 103, 116, 103, 101, 103, 85, 103, 73, 103, 61, 103, 52, 103, 38, 103, 34, 103, 29, 103, 27, 103, 26, 103, 25, 103, 24, 103]));
cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116])); cb(" ", new Uint8Array([39, 118, 40, 118, 41, 118, 44, 118, 47, 118, 52, 118, 58, 118, 66, 118, 74, 118, 84, 118, 94, 118, 104, 117, 114, 116, 123, 116, 130, 116, 144, 116, 149, 116, 154, 116, 158, 116, 161, 116, 163, 116]));
}; };
exports.input = function(options) { exports.input = function(options) {
options = options||{}; options = options||{};
let input_mode = exports.INPUT_MODE_ALPHA;
var text = options.text; var text = options.text;
if ("string"!=typeof text) text=""; if ("string"!=typeof text) text="";
Bangle.strokes = {}; function setupStrokes() {
exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) ); Bangle.strokes = {};
exports.getStrokes(input_mode, (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
}
setupStrokes();
var flashToggle = false; var flashToggle = false;
const R = Bangle.appRect; const R = Bangle.appRect;
@ -49,6 +70,9 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
var Rx2; var Rx2;
var Ry1; var Ry1;
var Ry2; var Ry2;
let flashInterval;
let shift = false;
let lastDrag;
function findMarker(strArr) { function findMarker(strArr) {
if (strArr.length == 0) { if (strArr.length == 0) {
@ -101,10 +125,13 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
*/ */
function show() { function show() {
if (flashInterval) clearInterval(flashInterval);
flashInterval = undefined;
g.reset(); g.reset();
g.clearRect(R).setColor("#f00"); g.clearRect(R).setColor("#f00");
var n=0; var n=0;
exports.getStrokes((id,s) => { exports.getStrokes(input_mode, (id,s) => {
var x = n%6; var x = n%6;
var y = (n-x)/6; var y = (n-x)/6;
s = g.transformVertices(s, {scale:0.16, x:R.x+x*30-4, y:R.y+y*30-4}); s = g.transformVertices(s, {scale:0.16, x:R.x+x*30-4, y:R.y+y*30-4});
@ -114,39 +141,87 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
}); });
} }
function isInside(rect, e) {
return e.x>=rect.x && e.x<rect.x+rect.w
&& e.y>=rect.y && e.y<=rect.y+rect.h;
}
function isStrokeInside(rect, stroke) {
for(let i=0; i < stroke.length; i+=2) {
if (!isInside(rect, {x: stroke[i], y: stroke[i+1]})) {
return false;
}
}
return true;
}
function strokeHandler(o) { function strokeHandler(o) {
//print(o); //print(o);
if (!flashInterval) if (!flashInterval)
flashInterval = setInterval(() => { flashInterval = setInterval(() => {
flashToggle = !flashToggle; flashToggle = !flashToggle;
draw(); draw(false);
}, 1000); }, 1000);
if (o.stroke!==undefined) { if (o.stroke!==undefined && o.xy.length >= 6 && isStrokeInside(R, o.xy)) {
var ch = o.stroke; var ch = o.stroke;
if (ch=="\b") text = text.slice(0,-1); if (ch=="\b") text = text.slice(0,-1);
else text += ch; else if (ch==="SHIFT") { shift=!shift; Bangle.drawWidgets(); }
g.clearRect(R); else text += shift ? ch.toUpperCase() : ch;
} }
lastDrag = undefined;
g.clearRect(R);
flashToggle = true; flashToggle = true;
draw(); draw(false);
} }
// Switches between alphabetic and numeric input
function cycleInput() {
input_mode++;
if (input_mode > exports.INPUT_MODE_NUM) input_mode = 0;
shift = false;
setupStrokes();
show();
Bangle.drawWidgets();
}
Bangle.on('stroke',strokeHandler); Bangle.on('stroke',strokeHandler);
g.reset().clearRect(R); g.reset().clearRect(R);
show(); show();
draw(false); draw(false);
var flashInterval;
// Create Widget to switch between alphabetic and numeric input
WIDGETS.kbswipe={
area:"tl",
width: 36, // 3 chars, 6*2 px/char
draw: function() {
g.reset();
g.setFont("6x8:2x3");
g.setColor("#f00");
if (input_mode === exports.INPUT_MODE_ALPHA) {
g.drawString(shift ? "ABC" : "abc", this.x, this.y);
} else if (input_mode === exports.INPUT_MODE_NUM) {
g.drawString("123", this.x, this.y);
}
}
};
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
var l;//last event
Bangle.setUI({mode:"custom", drag:e=>{ Bangle.setUI({mode:"custom", drag:e=>{
"ram"; "ram";
if (l) g.reset().setColor("#f00").drawLine(l.x,l.y,e.x,e.y); if (isInside(R, e)) {
l = e.b ? e : 0; if (lastDrag) g.reset().setColor("#f00").drawLine(lastDrag.x,lastDrag.y,e.x,e.y);
},touch:() => { lastDrag = e.b ? e : 0;
if (flashInterval) clearInterval(flashInterval); }
flashInterval = undefined; },touch:(n,e) => {
show(); if (WIDGETS.kbswipe && isInside({x: WIDGETS.kbswipe.x, y: WIDGETS.kbswipe.y, w: WIDGETS.kbswipe.width, h: 24}, e)) {
// touch inside widget
cycleInput();
} else if (isInside(R, e)) {
// touch inside app area
show();
}
}, back:()=>{ }, back:()=>{
delete WIDGETS.kbswipe;
Bangle.removeListener("stroke", strokeHandler); Bangle.removeListener("stroke", strokeHandler);
if (flashInterval) clearInterval(flashInterval); if (flashInterval) clearInterval(flashInterval);
Bangle.setUI(); Bangle.setUI();

View File

@ -1,6 +1,6 @@
{ "id": "kbswipe", { "id": "kbswipe",
"name": "Swipe keyboard", "name": "Swipe keyboard",
"version":"0.05", "version":"0.06",
"description": "A library for text input via PalmOS style swipe gestures (beta!)", "description": "A library for text input via PalmOS style swipe gestures (beta!)",
"icon": "app.png", "icon": "app.png",
"type":"textinput", "type":"textinput",

View File

@ -1 +1,2 @@
0.01: Moved message icons from messages into standalone library 0.01: Moved message icons from messages into standalone library
0.02: Added several new icons and colors

View File

@ -1,7 +1,7 @@
{ {
"id": "messageicons", "id": "messageicons",
"name": "Message Icons", "name": "Message Icons",
"version": "0.01", "version": "0.02",
"description": "Library containing a list of icons and colors for apps", "description": "Library containing a list of icons and colors for apps",
"icon": "app.png", "icon": "app.png",
"type": "module", "type": "module",

View File

@ -23,3 +23,4 @@
0.17: Use default Bangle formatter for booleans 0.17: Use default Bangle formatter for booleans
0.18: Improve widget load speed, allow currently recording track to be plotted in openstmap 0.18: Improve widget load speed, allow currently recording track to be plotted in openstmap
0.19: Fix track plotting code 0.19: Fix track plotting code
0.20: Automatic translation of some more strings.

View File

@ -61,7 +61,7 @@ function showMainMenu() {
setTimeout(function() { setTimeout(function() {
E.showMenu(); E.showMenu();
WIDGETS["recorder"].setRecording(v).then(function() { WIDGETS["recorder"].setRecording(v).then(function() {
print("Complete"); print(/*LANG*/"Complete");
loadSettings(); loadSettings();
print(settings.recording); print(settings.recording);
showMainMenu(); showMainMenu();
@ -96,7 +96,7 @@ function showMainMenu() {
}; };
var recorders = WIDGETS["recorder"].getRecorders(); var recorders = WIDGETS["recorder"].getRecorders();
Object.keys(recorders).forEach(id=>{ Object.keys(recorders).forEach(id=>{
mainmenu["Log "+recorders[id]().name] = menuRecord(id); mainmenu[/*LANG*/"Log "+recorders[id]().name] = menuRecord(id);
}); });
delete recorders; delete recorders;
return E.showMenu(mainmenu); return E.showMenu(mainmenu);
@ -404,7 +404,7 @@ function viewTrack(filename, info) {
title: title, title: title,
miny: min, miny: min,
maxy: max, maxy: max,
xlabel : x=>Math.round(x*dur/(60*infn.length))+" min" // minutes xlabel : x=>Math.round(x*dur/(60*infn.length))+/*LANG*/" min" // minutes
}); });
g.setFont("6x8",2); g.setFont("6x8",2);
g.setFontAlign(0,0,3); g.setFontAlign(0,0,3);

View File

@ -2,7 +2,7 @@
"id": "recorder", "id": "recorder",
"name": "Recorder", "name": "Recorder",
"shortName": "Recorder", "shortName": "Recorder",
"version": "0.19", "version": "0.20",
"description": "Record GPS position, heart rate and more in the background, then download to your PC.", "description": "Record GPS position, heart rate and more in the background, then download to your PC.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,outdoors,gps,widget", "tags": "tool,outdoors,gps,widget",

View File

@ -0,0 +1,4 @@
0.01: New App!
0.02: Add "from Consec."-setting
0.03: Correct how to ignore last triggered alarm
0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings

View File

@ -0,0 +1,56 @@
# Sleep Log Alarm
This widget searches for active alarms and raises an own alarm event up to the defined time earlier, if in light sleep or awake phase. Optional the earlier alarm will only be triggered if comming from or in consecutive sleep. The settings of the earlier alarm can be adjusted and it is possible to filter the targeting alarms by time and message. By default the time of the targeting alarm is displayed inside the widget which can be adjusted, too.
_This widget does not detect sleep on its own and can not create alarms. It requires the [sleeplog](/apps/?id=sleeplog) app and any alarm app that uses [sched](/apps/?id=sched) to be installed._
---
### Settings
---
- __earlier__ | duration to trigger alarm earlier
_10min_ / _20min_ / __30min__ / ... / _120min_
- __from Consec.__ | only trigger if comming from consecutive sleep
_on_ / __off__
- __vib pattern__ | vibration pattern for the earlier alarm
__..__ / ...
- __msg__ | customized message for the earlier alarm
__...__ / ...
- __msg as prefix__ | use the customized message as prefix to the original message or replace it comlpetely if disabled
__on__ / _off_
- __disable alarm__ | if enabled the original alarm will be disabled
_on_ / __off__
- __auto snooze__ | auto snooze option for the earlier alarm
__on__ / _off_
- __Filter Alarm__ submenu
- __time from__ | exclude alarms before this time
_0:00_ / _0:15_ / ... / __3:00__ / ... / _24:00_
- __time to__ | exclude alarms after this time
_0:00_ / _0:15_ / ... / __12:00__ / ... / _24:00_
- __msg includes__ | include only alarms including this string in msg
__""__ / ...
- __Widget__ submenu
- __hide__ | completely hide the widget
_on_ / __off__
- __show time__ | show the time of the targeting alarm
__on__ / _off_
- __color__ | color of the widget
_red_ / __yellow__ / ... / _white_
- __Enabled__ | completely en-/disables the background service
__on__ / _off_
---
### Worth Mentioning
---
#### Requests, Bugs and Feedback
Please leave requests and bug reports by raising an issue at [github.com/storm64/BangleApps](https://github.com/storm64/BangleApps) (or send me a [mail](mailto:banglejs@storm64.de)).
#### Creator
Storm64 ([Mail](mailto:banglejs@storm64.de), [github](https://github.com/storm64))
#### Attributions
The app icon is downloaded from [https://icons8.com](https://icons8.com).
#### License
[MIT License](LICENSE)

BIN
apps/sleeplogalarm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

141
apps/sleeplogalarm/lib.js Normal file
View File

@ -0,0 +1,141 @@
// load library
var sched = require("sched");
// find next active alarm in range
function getNextAlarm(allAlarms, fo, withId) {
if (withId) allAlarms = allAlarms.map((a, idx) => {
a.idx = idx;
return a;
});
// return next active alarms in range, filter for
// active && not timer && not own alarm &&
// after from && before to && includes msg
var ret = allAlarms.filter(
a => a.on && !a.timer && a.id !== "sleeplog" &&
a.t >= fo.from && a.t < fo.to && (!fo.msg || a.msg.includes(fo.msg))
).map(a => { // add time to alarm
a.tTo = sched.getTimeToAlarm(a);
return a;
}).filter(a => a.tTo // filter non active alarms
// sort to get next alarm first
).sort((a, b) => a.tTo - b.tTo);
// prevent triggering for an already triggered alarm again if available
if (fo.lastDate) {
var toLast = fo.lastDate - new Date().valueOf() + 1000;
if (toLast > 0) ret = ret.filter(a => a.tTo > toLast);
}
// return first entry
return ret[0] || {};
}
exports = {
// function to read settings with defaults
getSettings: function() {
return Object.assign({
enabled: true,
earlier: 30,
fromConsec: false,
vibrate: "..",
msg: "...\n",
msgAsPrefix: true,
disableOnAlarm: false, // !!! not available if alarm is at the next day
as: true,
filter: {
from: 3 * 36E5,
to: 12 * 36E5,
msg: ""
},
wid: {
hide: false,
time: true,
color: g.theme.dark ? 65504 : 31 // yellow or blue
}
}, require("Storage").readJSON("sleeplogalarm.settings.json", true) || {});
},
// widget reload function
widReload: function() {
// abort if trigger object is not available
if (typeof (global.sleeplog || {}).trigger !== "object") return;
// read settings to calculate alarm range
var settings = exports.getSettings();
// set the alarm time
this.time = getNextAlarm(sched.getAlarms(), settings.filter).t;
// abort if no alarm time could be found inside range
if (!this.time) return;
// set widget width if not hidden
if (!this.hidden) this.width = 8;
// insert sleeplogalarm conditions and function
sleeplog.trigger.sleeplogalarm = {
from: this.time - settings.earlier * 6E4,
to: this.time - 1,
fn: function (data) {
// execute trigger function if on light sleep or awake
// and if set if comming from consecutive
if ((data.status === 3 || data.status === 2) && !settings.fromConsec ||
data.consecutive === 3 || data.prevConsecutive === 3)
require("sleeplogalarm").trigger();
}
};
},
// trigger function
trigger: function() {
// read settings
var settings = exports.getSettings();
// read all alarms
var allAlarms = sched.getAlarms();
// find first active alarm
var alarm = getNextAlarm(sched.getAlarms(), settings.filter, settings.disableOnAlarm);
// return if no alarm is found
if (!alarm) return;
// get now
var now = new Date();
// get date of the alarm
var aDate = new Date(now + alarm.tTo);
// disable earlier triggered alarm if set
if (settings.disableOnAlarm) {
// set alarms last to the day it would trigger
allAlarms[alarm.idx].last = aDate.getDate();
// remove added indexes
allAlarms = allAlarms.map(a => {
delete a.idx;
return a;
});
}
// add new alarm for now with data from found alarm
allAlarms.push({
id: "sleeplog",
appid: "sleeplog",
on: true,
t: ((now.getHours() * 60 + now.getMinutes()) * 60 + now.getSeconds()) * 1000,
dow: 127,
msg: settings.msg + (settings.msgAsPrefix ? alarm.msg || "" : ""),
vibrate: settings.vibrate || alarm.vibrate,
as: settings.as,
del: true
});
// save date of the alarm to prevent triggering for the same alarm again
settings.filter.lastDate = aDate.valueOf();
require("Storage").writeJSON("sleeplogalarm.settings.json", settings);
// write changes
sched.setAlarms(allAlarms);
// trigger sched.js
load("sched.js");
}
};

View File

@ -0,0 +1,21 @@
{
"id":"sleeplogalarm",
"name":"Sleep Log Alarm",
"shortName": "SleepLogAlarm",
"version": "0.04",
"description": "Enhance your morning and let your alarms wake you up when you are in light sleep.",
"icon": "app.png",
"type": "widget",
"tags": "tool,widget",
"supports": ["BANGLEJS2"],
"dependencies": {
"scheduler": "type",
"sleeplog": "app"
},
"readme": "README.md",
"storage": [
{"name": "sleeplogalarm", "url": "lib.js"},
{"name": "sleeplogalarm.settings.js", "url": "settings.js"},
{"name": "sleeplogalarm.wid.js", "url": "widget.js"}
]
}

View File

@ -0,0 +1,192 @@
(function(back) {
// read settings
var settings = require("sleeplogalarm").getSettings();
// write change to storage
function writeSetting() {
require("Storage").writeJSON("sleeplogalarm.settings.json", settings);
}
// read input from keyboard
function readInput(v, cb) {
// setTimeout required to load after menu refresh
setTimeout((v, cb) => {
if (require("Storage").read("textinput")) {
g.clear();
require("textinput").input({text: v}).then(v => cb(v));
} else {
E.showAlert(/*LANG*/"No keyboard app installed").then(() => cb());
}
}, 0, v, cb);
}
// show widget menu
function showFilterMenu() {
// set menu
var filterMenu = {
"": {
title: "Filter Alarm"
},
/*LANG*/"< Back": () => showMain(8),
/*LANG*/"time from": {
value: settings.filter.from / 6E4,
step: 10,
min: 0,
max: 1440,
wrap: true,
noList: true,
format: v => (0|v/60) + ":" + ("" + (v%60)).padStart(2, "0"),
onchange: v => {
settings.filter.from = v * 6E4;
writeSetting();
}
},
/*LANG*/"time to": {
value: settings.filter.to / 6E4,
step: 10,
min: 0,
max: 1440,
wrap: true,
noList: true,
format: v => (0|v/60) + ":" + ("" + (v%60)).padStart(2, "0"),
onchange: v => {
settings.filter.to = v * 6E4;
writeSetting();
}
},
/*LANG*/"msg includes": {
value: settings.filter.msg,
format: v => !v ? "" : v.length > 6 ? v.substring(0, 6)+"..." : v,
onchange: v => readInput(v, v => {
settings.filter.msg = v;
writeSetting();
showFilterMenu(3);
})
}
};
var menu = E.showMenu(filterMenu);
}
// show widget menu
function showWidMenu() {
// define color values and names
var colName = ["red", "yellow", "green", "cyan", "blue", "magenta", "black", "white"];
var colVal = [63488, 65504, 2016, 2047, 31, 63519, 0, 65535];
// set menu
var widgetMenu = {
"": {
title: "Widget Settings"
},
/*LANG*/"< Back": () => showMain(9),
/*LANG*/"hide": {
value: settings.wid.hide,
onchange: v => {
settings.wid.hide = v;
writeSetting();
}
},
/*LANG*/"show time": {
value: settings.wid.time,
onchange: v => {
settings.wid.time = v;
writeSetting();
}
},
/*LANG*/"color": {
value: colVal.indexOf(settings.wid.color),
min: 0,
max: colVal.length -1,
wrap: true,
format: v => colName[v],
onchange: v => {
settings.wid.color = colVal[v];
writeSetting();
}
}
};
var menu = E.showMenu(widgetMenu);
}
// show main menu
function showMain(selected) {
// set menu
var mainMenu = {
"": {
title: "Sleep Log Alarm",
selected: selected
},
/*LANG*/"< Back": () => back(),
/*LANG*/"erlier": {
value: settings.earlier,
step: 10,
min: 10,
max: 120,
wrap: true,
noList: true,
format: v => v + /*LANG*/"min",
onchange: v => {
settings.earlier = v;
writeSetting();
}
},
/*LANG*/"from Consec.": {
value: settings.fromConsec,
onchange: v => {
settings.fromConsec = v;
writeSetting();
}
},
/*LANG*/"vib pattern": require("buzz_menu").pattern(
settings.vibrate,
v => {
settings.vibrate = v;
writeSetting();
}
),
/*LANG*/"msg": {
value: settings.msg,
format: v => !v ? "" : v.length > 6 ? v.substring(0, 6)+"..." : v,
onchange: v => readInput(v, v => {
settings.msg = v;
writeSetting();
showMenu(4);
})
},
/*LANG*/"msg as prefix": {
value: settings.msgAsPrefix,
onchange: v => {
settings.msgAsPrefix = v;
writeSetting();
}
},
/*LANG*/"disable alarm": {
value: settings.disableOnAlarm,
onchange: v => {
settings.disableOnAlarm = v;
writeSetting();
}
},
/*LANG*/"auto snooze": {
value: settings.as,
onchange: v => {
settings.as = v;
writeSetting();
}
},
/*LANG*/"Filter Alarm": () => showFilterMenu(),
/*LANG*/"Widget": () => showWidMenu(),
/*LANG*/"Enabled": {
value: settings.enabled,
onchange: v => {
settings.enabled = v;
writeSetting();
}
}
};
var menu = E.showMenu(mainMenu);
}
// draw main menu
showMain();
})

View File

@ -0,0 +1,32 @@
// check if enabled in settings
if ((require("Storage").readJSON("sleeplogalarm.settings.json", true) || {enabled: true}).enabled) {
// read settings
settings = require("sleeplogalarm").getSettings(); // is undefined if used with var
// insert neccessary settings into widget
WIDGETS.sleeplogalarm = {
area: "tl",
width: 0,
time: 0,
earlier: settings.earlier,
draw: function () {
// draw zzz
g.reset().setColor(settings.wid.color).drawImage(atob("BwoBD8SSSP4EEEDg"), this.x + 1, this.y);
// call function to draw the time of alarm if a alarm is found
if (this.time) this.drawTime(this.time + 1);
},
drawTime: () => {},
reload: require("sleeplogalarm").widReload
};
// add function to draw the time of alarm if enabled
if (settings.wid.time) WIDGETS.sleeplogalarm.drawTime = function(time) {
// directly include Font4x5Numeric
g.setFontCustom(atob("CAZMA/H4PgvXoK1+DhPg7W4P1uCEPg/X4O1+AA=="), 46, atob("AgQEAgQEBAQEBAQE"), 5).setFontAlign(1, 1);
g.drawString(0|(time / 36E5), this.x + this.width + 1, this.y + 17);
g.drawString(0|((time / 36E5)%1 * 60), this.x + this.width + 1, this.y + 23);
};
// load widget
WIDGETS.sleeplogalarm.reload();
}

View File

@ -10,7 +10,7 @@ to change (top right or bottom left). It should change color showing
it is selected. it is selected.
* Swipe up or down to cycle through the info screens that can be displayed * Swipe up or down to cycle through the info screens that can be displayed
when you have finnised tap again towards the centre of the screen to unselect. when you have finished tap again towards the centre of the screen to unselect.
* Swipe left or right to change the type of info screens displayed (by default * Swipe left or right to change the type of info screens displayed (by default
there is only one type of data so this will have no effect) there is only one type of data so this will have no effect)

View File

@ -1,4 +1,5 @@
0.01: Release 0.01: Release
0.02: Rewrite with new interface 0.02: Rewrite with new interface
0.03: Added clock infos to expose timer functionality to clocks. 0.03: Added clock infos to expose timer functionality to clocks.
0.04: Improvements of clock infos. 0.04: Improvements of clock infos.
0.05: Updated clkinfo icon.

View File

@ -63,10 +63,9 @@
} catch(ex){ } } catch(ex){ }
} }
var img = atob("GBiBAeAAB+AAB/v/3/v/3/v/3/v/3/v/n/n/H/z+P/48//85//+b//+b//8p//4E//yCP/kBH/oAn/oAX/oAX/oAX/oAX+AAB+AABw==")
var smpltmrItems = { var smpltmrItems = {
name: "Timer", name: "Timer",
img: img, img: atob("GBiBAAB+AAB+AAAYAAAYAAB+AA3/sA+B8A4AcAwMMBgPGBgPmDAPjDAPzDAPzDP/zDP/zDH/jBn/mBj/GAw8MA4AcAeB4AH/gAB+AA=="),
items: [ items: [
{ {
name: null, name: null,

View File

@ -2,7 +2,7 @@
"id": "smpltmr", "id": "smpltmr",
"name": "Simple Timer", "name": "Simple Timer",
"shortName": "Simple Timer", "shortName": "Simple Timer",
"version": "0.04", "version": "0.05",
"description": "A very simple app to start a timer.", "description": "A very simple app to start a timer.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,alarm,timer,clkinfo", "tags": "tool,alarm,timer,clkinfo",

View File

@ -17,3 +17,6 @@
0.18: Added hasRange to clkinfo. 0.18: Added hasRange to clkinfo.
0.19: Added weather condition to clkinfo. 0.19: Added weather condition to clkinfo.
0.20: Added weather condition with temperature to clkinfo. 0.20: Added weather condition with temperature to clkinfo.
0.21: Updated clkinfo icon.
0.22: Automatic translation of strings, some left untranslated.
>>>>>>> b37fcacd1 (weather - autotranslate strings)

View File

@ -16,10 +16,10 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
{type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"}, {type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"},
]}, ]},
{filly: 1}, {filly: 1},
{type: "txt", font: "6x8", pad: 2, halign: 1, label: "Humidity"}, {type: "txt", font: "6x8", pad: 2, halign: 1, label: /*LANG*/"Humidity"},
{type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"}, {type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"},
{filly: 1}, {filly: 1},
{type: "txt", font: "6x8", pad: 2, halign: -1, label: "Wind"}, {type: "txt", font: "6x8", pad: 2, halign: -1, label: /*LANG*/"Wind"},
{type: "h", halign: -1, c: [ {type: "h", halign: -1, c: [
{type: "txt", font: "9%", pad: 2, id: "wind", label: "00"}, {type: "txt", font: "9%", pad: 2, id: "wind", label: "00"},
{type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"}, {type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"},
@ -27,22 +27,22 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
]}, ]},
]}, ]},
{filly: 1}, {filly: 1},
{type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"}, {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: /*LANG*/"Weather condition"},
{filly: 1}, {filly: 1},
{type: "h", c: [ {type: "h", c: [
{type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"}, {type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"},
{fillx: 1}, {fillx: 1},
{type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"}, {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: /*LANG*/"15 minutes ago"},
]}, ]},
{filly: 1}, {filly: 1},
]}, {lazy: true}); ]}, {lazy: true});
function formatDuration(millis) { function formatDuration(millis) {
let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s");
if (millis < 60000) return "< 1 minute"; if (millis < 60000) return /*LANG*/"< 1 minute";
if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); if (millis < 3600000) return pluralize(Math.floor(millis/60000), /*LANG*/"minute");
if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); if (millis < 86400000) return pluralize(Math.floor(millis/3600000), /*LANG*/"hour");
return pluralize(Math.floor(millis/86400000), "day"); return pluralize(Math.floor(millis/86400000), /*LANG*/"day");
} }
function draw() { function draw() {
@ -57,7 +57,7 @@ function draw() {
layout.windUnit.label = wind[2] + " " + (current.wrose||'').toUpperCase(); layout.windUnit.label = wind[2] + " " + (current.wrose||'').toUpperCase();
layout.cond.label = current.txt.charAt(0).toUpperCase()+(current.txt||'').slice(1); layout.cond.label = current.txt.charAt(0).toUpperCase()+(current.txt||'').slice(1);
layout.loc.label = current.loc; layout.loc.label = current.loc;
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; // How to autotranslate this and similar?
layout.update(); layout.update();
layout.render(); layout.render();
} }
@ -77,9 +77,9 @@ function update() {
} else { } else {
layout.forgetLazyState(); layout.forgetLazyState();
if (NRF.getSecurityStatus().connected) { if (NRF.getSecurityStatus().connected) {
E.showMessage("Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?"); E.showMessage(/*LANG*/"Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?");
} else { } else {
E.showMessage("Weather\nunknown\n\nGadgetbridge\nnot connected"); E.showMessage(/*LANG*/"Weather\nunknown\n\nGadgetbridge\nnot connected");
NRF.on("connect", update); NRF.on("connect", update);
} }
} }

View File

@ -28,7 +28,7 @@
//FIXME ranges are somehow arbitrary //FIXME ranges are somehow arbitrary
var weatherItems = { var weatherItems = {
name: "Weather", name: "Weather",
img: atob("GBiBAf+///u5//n7//8f/9wHP8gDf/gB//AB/7AH/5AcP/AQH/DwD/uAD84AD/4AA/wAAfAAAfAAAfAAAfgAA/////+bP/+zf/+zfw=="), img: atob("GBiBAABAAARGAAYEAADgACP4wDf8gAf+AA/+AE/4AG/jwA/v4A8P8AR/8DH/8AH//AP//g///g///g///gf//AAAAABkwABMgABMgA=="),
items: [ items: [
{ {
name: "conditionWithTemperature", name: "conditionWithTemperature",

View File

@ -1,7 +1,7 @@
{ {
"id": "weather", "id": "weather",
"name": "Weather", "name": "Weather",
"version": "0.20", "version": "0.22",
"description": "Show Gadgetbridge weather report", "description": "Show Gadgetbridge weather report",
"icon": "icon.png", "icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}], "screenshots": [{"url":"screenshot.png"}],

View File

@ -77,7 +77,7 @@ exports.load = function() {
// actual menu // actual menu
var menu = [{ var menu = [{
name: "Bangle", name: "Bangle",
img: atob("GBiBAf8B//4B//4B//4B//4A//x4//n+f/P/P+fPn+fPn+fP3+/Px+/Px+fn3+fzn+f/n/P/P/n+f/x4//4A//4B//4B//4B//8B/w=="), img: atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA=="),
items: [ items: [
{ name : "Battery", { name : "Battery",
hasRange : true, hasRange : true,

View File

@ -29,6 +29,7 @@ exports.show = function() {
/// Remove any intervals/handlers/etc that we might have added. Does NOT re-show widgets that were hidden /// Remove any intervals/handlers/etc that we might have added. Does NOT re-show widgets that were hidden
exports.cleanup = function() { exports.cleanup = function() {
delete exports.autohide;
delete Bangle.appRect; delete Bangle.appRect;
if (exports.swipeHandler) { if (exports.swipeHandler) {
Bangle.removeListener("swipe", exports.swipeHandler); Bangle.removeListener("swipe", exports.swipeHandler);
@ -50,11 +51,13 @@ exports.cleanup = function() {
/** Put widgets offscreen, and allow them to be swiped /** Put widgets offscreen, and allow them to be swiped
back onscreen with a downwards swipe. Use .show to undo. back onscreen with a downwards swipe. Use .show to undo.
First parameter controls automatic hiding time, 0 equals not hiding at all.
Default value is 2000ms until hiding.
Bangle.js 2 only at the moment. */ Bangle.js 2 only at the moment. */
exports.swipeOn = function() { exports.swipeOn = function(autohide) {
exports.cleanup(); exports.cleanup();
if (!global.WIDGETS) return; if (!global.WIDGETS) return;
exports.autohide=autohide===undefined?2000:autohide;
/* TODO: maybe when widgets are offscreen we don't even /* TODO: maybe when widgets are offscreen we don't even
store them in an offscreen buffer? */ store them in an offscreen buffer? */
@ -125,11 +128,13 @@ exports.swipeOn = function() {
clearTimeout(exports.hideTimeout); clearTimeout(exports.hideTimeout);
delete exports.hideTimeout; delete exports.hideTimeout;
} }
if (ud>0 && offset<0) anim(4, function() { let cb;
if (exports.autohide > 0) cb = function() {
exports.hideTimeout = setTimeout(function() { exports.hideTimeout = setTimeout(function() {
anim(-4); anim(-4);
}, 2000); }, exports.autohide);
}); }
if (ud>0 && offset<0) anim(4, cb);
if (ud<0 && offset>-24) anim(-4); if (ud<0 && offset>-24) anim(-4);
}; };