1
0
Fork 0

Merge remote-tracking branch 'upstream/master'

master
hughbarney 2021-04-28 22:47:09 +01:00
commit 40851ec0c4
17 changed files with 569 additions and 247 deletions

View File

@ -219,8 +219,8 @@
{ "id": "slidingtext", { "id": "slidingtext",
"name": "Sliding Clock", "name": "Sliding Clock",
"icon": "slidingtext.png", "icon": "slidingtext.png",
"version":"0.02", "version":"0.05",
"description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently only English, French and Japanese are supported", "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported",
"tags": "clock", "tags": "clock",
"type":"clock", "type":"clock",
"allow_emulator":true, "allow_emulator":true,
@ -232,8 +232,10 @@
{"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"}, {"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"},
{"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"}, {"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"},
{"name":"slidingtext.utils.en.js","url":"slidingtext.utils.en.js"}, {"name":"slidingtext.utils.en.js","url":"slidingtext.utils.en.js"},
{"name":"slidingtext.locale.es.js","url":"slidingtext.locale.es.js"},
{"name":"slidingtext.locale.fr.js","url":"slidingtext.locale.fr.js"}, {"name":"slidingtext.locale.fr.js","url":"slidingtext.locale.fr.js"},
{"name":"slidingtext.locale.jp.js","url":"slidingtext.locale.jp.js"}, {"name":"slidingtext.locale.jp.js","url":"slidingtext.locale.jp.js"},
{"name":"slidingtext.locale.de.js","url":"slidingtext.locale.de.js"},
{"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"} {"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"}
] ]
}, },
@ -459,7 +461,7 @@
{ "id": "heart", { "id": "heart",
"name": "Heart Rate Recorder", "name": "Heart Rate Recorder",
"icon": "app.png", "icon": "app.png",
"version":"0.04", "version":"0.05",
"interface": "interface.html", "interface": "interface.html",
"description": "Application that allows you to record your heart rate. Can run in background", "description": "Application that allows you to record your heart rate. Can run in background",
"tags": "tool,health,widget", "tags": "tool,health,widget",
@ -3042,7 +3044,7 @@
"name": "Gadgetbridge Music Controls", "name": "Gadgetbridge Music Controls",
"shortName":"Music Controls", "shortName":"Music Controls",
"icon": "icon.png", "icon": "icon.png",
"version":"0.02", "version":"0.03",
"description": "Control the music on your Gadgetbridge-connected phone", "description": "Control the music on your Gadgetbridge-connected phone",
"tags": "tools,bluetooth,gadgetbridge,music", "tags": "tools,bluetooth,gadgetbridge,music",
"type":"app", "type":"app",

View File

@ -1,2 +1,3 @@
0.01: Initial version 0.01: Initial version
0.02: Increase text brightness, improve controls, (try to) reduce memory usage 0.02: Increase text brightness, improve controls, (try to) reduce memory usage
0.03: Only auto-start if active app is a clock, auto close after 1 hour of inactivity

View File

@ -11,7 +11,8 @@ let info = {
n: 0, n: 0,
c: 0, c: 0,
}; };
const TOUT = 300000; // auto close timeout: 5 minutes (in ms) const POUT = 300000; // auto close timeout when paused: 5 minutes (in ms)
const IOUT = 3600000; // auto close timeout for inactivity: 1 hour (in ms)
/////////////////////// ///////////////////////
// Self-repeating timeouts // Self-repeating timeouts
@ -44,7 +45,7 @@ function brightness() {
if (!fade) { if (!fade) {
return 1; return 1;
} }
return Math.max(0, 1-((Date.now()-fade)/TOUT)); return Math.max(0, 1-((Date.now()-fade)/POUT));
} }
// Scroll long track names // Scroll long track names
@ -396,26 +397,50 @@ function musicInfo(e) {
if (Bangle.isLCDOn()) { if (Bangle.isLCDOn()) {
drawMusic(); drawMusic();
} }
if (tIxt) {
clearTimeout(tIxt);
tIxt = null;
}
if (auto && stat==="play") {
// if inactive for double song duration (or an hour if unknown), load the clock
// i.e. phone finished playing without bothering to notify the watch
tIxt = setTimeout(load, (info.dur*2000) || IOUT);
}
} }
let tXit; let tPxt, tIxt;
function musicState(e) { function musicState(e) {
stat = e.state; stat = e.state;
// if paused for five minutes, load the clock // if paused for five minutes, load the clock
// (but timeout resets if we get new info, even while paused) // (but timeout resets if we get new info, even while paused)
if (tXit) { if (tPxt) {
clearTimeout(tXit); clearTimeout(tPxt);
tPxt = null;
}
if (tIxt) {
clearTimeout(tIxt);
tIxt = null;
} }
tXit = null;
fade = null; fade = null;
delete info.track_color; delete info.track_color;
if (stat!=="play" && auto) { if (auto) { // auto opened -> auto close
if (stat==="stop") { // never actually happens with my phone :-( switch(stat) {
load(); case "stop": // never actually happens with my phone :-(
} else { // also quit when paused for a long time load();
tXit = setTimeout(load, TOUT); break;
fade = Date.now(); case "play":
fadeOut(); // if inactive for double song duration (or an hour if unknown), load the clock
// i.e. phone finished playing without bothering to notify the watch
tIxt = setTimeout(load, (info.dur*2000) || IOUT);
break;
case "pause":
default:
// quit when paused for a long time
// also fade out track info while waiting for this
tPxt = setTimeout(load, POUT);
fade = Date.now();
fadeOut();
break;
} }
} }
if (Bangle.isLCDOn()) { if (Bangle.isLCDOn()) {
@ -538,7 +563,7 @@ function startLCDWatch() {
///////////////////// /////////////////////
// check for saved music stat (by widget) to load // check for saved music stat (by widget) to load
g.clear(); g.clear();
global.gbmusic_active = true; // we don't need our widget global.gbmusic_active = true; // we don't need our widget (needed for <2.09 devices)
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
delete (global.gbmusic_active); delete (global.gbmusic_active);

View File

@ -1,38 +1,44 @@
(() => { (() => {
if (global.gbmusic_active || !(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart) { if (global.gbmusic_active || !(require("Storage").readJSON("gbmusic.json", 1) || {}).autoStart) {
return return;
}
if (typeof __FILE__ === 'string') { // only exists since 2v09
const info = require("Storage").readJSON(__FILE__.split(".")[0]+".info", 1) || false;
if (info && info.type!=="clock") { // info can have no type (but then it isn't a clock)
return;
}
} }
let state, info let state, info;
function checkMusic() { function checkMusic() {
if (state!=="play" || !info) { if (state!=="play" || !info) {
return return;
} }
// playing music: launch music app // playing music: launch music app
require("Storage").writeJSON("gbmusic.load.json", { require("Storage").writeJSON("gbmusic.load.json", {
state: state, state: state,
info: info, info: info,
}) });
load("gbmusic.app.js") load("gbmusic.app.js");
} }
const _GB = global.GB const _GB = global.GB;
global.GB = (event) => { global.GB = (event) => {
// we eat music events! // we eat music events!
switch(event.t) { switch(event.t) {
case "musicinfo": case "musicinfo":
info = event info = event;
delete(info.t) delete (info.t);
checkMusic() checkMusic();
break break;
case "musicstate": case "musicstate":
state = event.state state = event.state;
checkMusic() checkMusic();
break break;
default: default:
if (_GB) { if (_GB) {
setTimeout(_GB, 0, event) setTimeout(_GB, 0, event);
} }
} }
} };
})() })();

View File

@ -3,3 +3,11 @@
Clean up recordings on app removal Clean up recordings on app removal
0.03: added graphing feature of 164 latest measurements 0.03: added graphing feature of 164 latest measurements
0.04: Fix memory usage when viewing HRM traces 0.04: Fix memory usage when viewing HRM traces
0.05: Add loading screen for viewRecord
List average, minimum & maximum measurement in viewRecord
Disable recording only when current recording file is erased
Fix timezone offset
Draw chart based on height and width of display instead of hard-coded limits
Reduce memory usage by ~30%
Generate scale based on defined minimum and maximum measurement
Added background line on 50% to ease estimation of drawn values

View File

@ -1,25 +1,17 @@
const GraphXZero = 40; E.setFlags({pretokenise:1});
const GraphYZero = 200;
const GraphY100 = 80;
const GraphMarkerOffset = 5; function log(msg) {
const MaxValueCount = 164; console.log("heart: " + msg + "; mem used: " + process.memory().usage / process.memory().blocksize);
const GraphXMax = GraphXZero + MaxValueCount; return;
}
log("start");
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
var settings = require("Storage").readJSON("heart.json",1)||{}; var settings = require("Storage").readJSON("heart.json",1)||{};
var globalSettings = require('Storage').readJSON('setting.json', true) || {timezone: 0};
require('DateExt').locale({
str: "0D.0M. 0h:0m",
offset: [
globalSettings.timezone * 60,
globalSettings.timezone * 60
]
});
function getFileNbr(n) { function getFileNbr(n) {
return ".heart"+n.toString(36); return ".heart"+n.toString(36);
} }
@ -28,6 +20,7 @@ function updateSettings() {
require("Storage").write("heart.json", settings); require("Storage").write("heart.json", settings);
if (WIDGETS["heart"]) if (WIDGETS["heart"])
WIDGETS["heart"].reload(); WIDGETS["heart"].reload();
return;
} }
function showMainMenu() { function showMainMenu() {
@ -52,214 +45,232 @@ function showMainMenu() {
updateSettings(); updateSettings();
} }
}, },
'View Records': ()=>{viewRecords()}, 'View Records': ()=>{createRecordMenu(viewRecord.bind());},
'Graph Records': ()=>{graphRecords()}, 'Graph Records': ()=>{createRecordMenu(graphRecord.bind());},
'< Back': ()=>{load();} '< Back': ()=>{load();}
}; };
return E.showMenu(mainMenu); return E.showMenu(mainMenu);
} }
function viewRecords() { // Date().as().str cannot be used as it always returns UTC time
function getDateString(timestamp) {
var date = new Date(timestamp);
var day = date.getDate() < 10 ? "0" + date.getDate().toString() : date.getDate().toString();
var month = date.getMonth() < 10 ? "0" + date.getMonth().toString() : date.getMonth().toString();
return day + "." + month + "." + date.getFullYear();
}
// Date().as().str cannot be used as it always returns UTC time
function getTimeString(timestamp) {
var date = new Date(timestamp);
var hour = date.getHours() < 10 ? '0' + date.getHours().toString() : date.getHours().toString();
var minute = date.getMinutes() < 10 ? '0' + date.getMinutes().toString() : date.getMinutes().toString();
return hour + ':' + minute;
}
function createRecordMenu(func) {
const menu = { const menu = {
'': { 'title': 'Heart Records' } '': { 'title': 'Heart Records' }
}; };
var found = false; var found = false;
for (var n=0;n<36;n++) { for (var n=0;n<36;n++) {
var f = require("Storage").open(getFileNbr(n),"r"); var line = require("Storage").open(getFileNbr(n),"r").readLine();
if (f.readLine()!==undefined) { if (line!==undefined) {
menu["Record "+n] = viewRecord.bind(null,n); menu["#" + n + " " + getDateString(line.split(",")[0]*1000) + " " + getTimeString(line.split(",")[0]*1000)] = func.bind(null, n);
found = true; found = true;
} }
} }
if (!found) if (!found)
menu["No Records Found"] = function(){}; menu["No Records Found"] = function(){};
menu['< Back'] = ()=>{showMainMenu()}; menu['< Back'] = ()=>{showMainMenu();};
return E.showMenu(menu); return E.showMenu(menu);
} }
function viewRecord(n) { function viewRecord(n) {
E.showMenu({'': 'Heart Record '+n});
E.showMessage(
"Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.",
'Heart Record '+n
);
const menu = { const menu = {
'': { 'title': 'Heart Record '+n } '': { 'title': 'Heart Record '+n }
}; };
var heartCount = 0;
var heartTime; var heartTime;
var f = require("Storage").open(getFileNbr(n),"r"); var f = require("Storage").open(getFileNbr(n),"r");
var l = f.readLine(); var l = f.readLine();
if (l!==undefined) { // using arrays for memory optimization
var c = l.split(","); var limits = Uint8Array(2);
heartTime = new Date(c[0]*1000); // using arrays for memory optimization
} var avg = Uint32Array(2);
// minimum
limits[0] = 2000;
// maximum
limits[1] = 0;
// count
avg[0] = 0;
// average sum
avg[1] = 0;
var count = 0;
var value = 0;
if (l!==undefined)
heartTime = new Date(l.split(",")[0]*1000);
log("parsing records");
while (l!==undefined) { while (l!==undefined) {
heartCount++; count++;
// TODO: min/max/average of heart rate? if (parseInt(l.split(',')[2]) >= 70) {
avg[0]++;
value = parseInt(l.split(',')[1]);
if (value < limits[0]) {
limits[0] = value;
} else if (value > limits[1]) {
limits[1] = value;
}
avg[1] += value;
}
l = f.readLine(); l = f.readLine();
} }
l = undefined;
value = undefined;
log("finished parsing");
if (heartTime) if (heartTime)
menu[" "+heartTime.toString().substr(4,17)] = function(){}; menu[" "+heartTime.toString().substr(4,17)] = function(){};
menu[heartCount+" records"] = function(){}; menu[count + " records"] = function(){};
// TODO: option to draw it? Just scan through, project using min/max menu["Min: " + limits[0]] = function(){};
menu['Erase'] = function() { menu["Max: " + limits[1]] = function(){};
menu["Avg: " + Math.round(avg[1] / avg[0])] = function(){};
menu["Erase"] = function() {
E.showPrompt("Delete Record?").then(function(v) { E.showPrompt("Delete Record?").then(function(v) {
if (v) { if (v) {
settings.isRecording = false; if (n == settings.fileNbr) {
updateSettings(); settings.isRecording = false;
var f = require("Storage").open(getFileNbr(n),"r"); updateSettings();
f.erase(); }
viewRecords(); require("Storage").open(getFileNbr(n),"r").erase();
E.showMenu();
createRecordMenu(viewRecord.bind());
} else } else
viewRecord(n); return viewRecord(n);
}); });
}; };
menu['< Back'] = ()=>{viewRecords()}; menu['< Back'] = ()=>{createRecordMenu(viewRecord.bind());};
print(menu); Bangle.buzz(200, 0.3);
return E.showMenu(menu); return E.showMenu(menu);
} }
function graphRecords() {
const menu = {
'': { 'title': 'Heart Records' }
};
var found = false;
for (var n=0;n<36;n++) {
var f = require("Storage").open(getFileNbr(n),"r");
var line = f.readLine();
if (line!==undefined) {
menu["#"+n+" "+Date(line.split(",")[0]*1000).as().str] = graphRecord.bind(null,n);
found = true;
}
}
if (!found)
menu["No Records Found"] = function(){};
menu['< Back'] = ()=>{showMainMenu()};
return E.showMenu(menu);
}
// based on batchart
function renderHomeIcon() {
//Home for Btn2
g.setColor(1, 1, 1);
g.drawLine(220, 118, 227, 110);
g.drawLine(227, 110, 234, 118);
g.drawPoly([222,117,222,125,232,125,232,117], false);
g.drawRect(226,120,229,125);
}
function renderChart() {
// Left Y axis (Battery)
g.setColor(1, 1, 0);
g.drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100);
g.setFontAlign(1, -1, 0);
g.drawString("150", 35, GraphY100 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100);
g.drawString("125", 35, GraphYZero - 110 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("100", 35, GraphYZero - 100 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("90", 35, GraphYZero - 90 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("80", 35, GraphYZero - 70 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("70", 35, GraphYZero - 50 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("60", 35, GraphYZero - 30 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("50", 35, GraphYZero - 20 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("40", 35, GraphYZero - 10 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("30", 35, GraphYZero - GraphMarkerOffset);
g.setColor(1, 1, 1);
g.drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero);
console.log("Finished drawing chart");
}
// as drawing starts at 30 HRM decreasing measrure by 30
// recalculate for range 110-150 as only 20 pixels are available
function getY(measure) {
positionY = GraphYZero - measure + 30;
if (100 < measure < 150) {
positionY = GraphYZero - ( 100 + Math.round((measure - 100)/2) ) + 30;
g.setColor(1, 0, 0);
} else if (60 < measrure < 100) {
positionY = GraphYZero - ( 30 + Math.round((measure - 30)/2) ) + 30;
g.setColor(0, 1, 0);
}
if (positionY > GraphYZero) {
positionY = GraphYZero;
g.setColor(1, 0, 0);
}
if (positionY < GraphY100) {
positionY = GraphY100;
g.setColor(1, 0, 0);
}
return positionY;
}
function stop() { function stop() {
E.showMenu(); E.showMenu();
load(); load();
} }
function graphRecord(n) { function graphRecord(n) {
E.showMenu({'': 'Heart Record '+n}); var headline = "Heart Record " + n;
E.showMenu({'': headline});
E.showMessage( E.showMessage(
"Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.", "Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.",
'Heart Record '+n headline
); );
g.setFont("Vector", 10);
var lastPixel; const MinMeasurement = 30;
const MaxMeasurement = 150;
const GraphXLabel = 35;
const GraphXZero = 40;
const GraphY100 = 60;
const GraphMarkerOffset = 5;
// calculate number of pixels based on display width
const MaxValueCount = g.getWidth() - GraphXZero - ( g.getWidth() - 220 ) - GraphMarkerOffset;
// calculate Y axis "0" pixel
const GraphYZero = g.getHeight() - g.setFont("Vector", 10).getFontHeight() - GraphMarkerOffset * 2;
// calculate X axis max drawable pixel
const GraphXMax = GraphXZero + MaxValueCount;
// calculate space between labels of scale
const LabelOffset = (GraphYZero - GraphY100) / (MaxMeasurement - MinMeasurement);
var lineCount = 0; var lineCount = 0;
var positionX = GraphXZero;
var positionY = GraphYZero;
var startLine = 1; var startLine = 1;
var tempCount = 0;
var f = require("Storage").open(getFileNbr(n),"r"); var f = require("Storage").open(getFileNbr(n),"r");
var line = f.readLine(); var line = f.readLine();
var times = Array(2);
console.log("Counting lines"); log("Counting lines");
while (line !== undefined) { while (line !== undefined) {
lineCount++; lineCount++;
line = f.readLine(); line = f.readLine();
} }
console.log(`Line count: ${lineCount}`);
if (lineCount > MaxValueCount) { log(`lineCount: ${lineCount}`);
if (lineCount > MaxValueCount)
startLine = lineCount - MaxValueCount; startLine = lineCount - MaxValueCount;
} f = undefined;
console.log(`start: ${startLine}`); line = undefined;
lineCount = undefined;
log(`startLine: ${startLine}`);
f = require("Storage").open(getFileNbr(n),"r"); f = require("Storage").open(getFileNbr(n),"r");
line = f.readLine(); line = f.readLine();
var times = Uint32Array(2);
var tempCount = 0;
var positionX = GraphXZero;
var positionY = GraphYZero;
var measure;
while (line !== undefined) { while (line !== undefined) {
currentLine = line; currentLine = line;
line = f.readLine(); line = f.readLine();
tempCount++; tempCount++;
if (tempCount == startLine) { if (tempCount == startLine) {
g.clear(); // generating rgaph in loop when reaching startLine to keep loading
Bangle.loadWidgets(); // message on screen until graph can be drawn
Bangle.drawWidgets(); g.clear().
renderHomeIcon(); // Home for Btn2
renderChart(); setColor(1, 1, 1).
drawLine(220, 118, 227, 110).
drawLine(227, 110, 234, 118).
drawPoly([222,117,222,125,232,125,232,117], false).
drawRect(226,120,229,125).
// headline
setFontAlign(0, -1, 0).
setFont("6x8", 2).
drawString(headline, g.getWidth()/2 - headline.length/2, GraphY100 - g.getFontHeight() - GraphMarkerOffset).
// Chart
setColor(1, 1, 0).
// horizontal bottom line
drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100).
// vertical left line
drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero).
// scale indicator line for 100%
drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100).
// scale indicator line for 50%
drawLine(GraphXZero - GraphMarkerOffset, GraphY100 + (GraphYZero - GraphY100)/2, GraphXZero, GraphY100 + (GraphYZero - GraphY100)/2).
// background line for 50%
setColor(1, 1, 1).
drawLine(GraphXZero + 1, GraphY100 + (GraphYZero - GraphY100)/2, GraphXMax, GraphY100 + (GraphYZero - GraphY100)/2).
setFontAlign(1, -1, 0).
setFont("Vector", 10);
// scale text
for (var i = MaxMeasurement; i >= MinMeasurement; i-=10) {
g.drawString(i, GraphXLabel, GraphY100 + LabelOffset * ( MaxMeasurement - i ) - GraphMarkerOffset);
}
log("Finished drawing chart");
} else if (tempCount > startLine) { } else if (tempCount > startLine) {
positionX++; positionX++;
if (parseInt(currentLine.split(",")[2]) >= 70) { if (parseInt(currentLine.split(",")[2]) >= 70) {
g.setColor(1, 1, 1); g.setColor(1, 0.3, 0.3);
oldPositionY = positionY; oldPositionY = positionY;
positionY = getY(parseInt(currentLine.split(",")[1])); measure = parseInt(currentLine.split(",")[1]);
if (times[0] === undefined) { positionY = GraphYZero - measure + MinMeasurement;
if (positionY > GraphYZero) {
positionY = GraphYZero;
}
if (positionY < GraphY100) {
positionY = GraphY100;
}
if (times[0] === 0) {
times[0] = parseInt(currentLine.split(",")[0]); times[0] = parseInt(currentLine.split(",")[0]);
} }
if (tempCount == startLine + 1) { if (tempCount == startLine + 1) {
@ -270,26 +281,30 @@ function graphRecord(n) {
} }
} }
} }
g.flip();
} }
g.setColor(1, 1, 0); g.setColor(1, 1, 0).setFont("Vector", 10);
g.setFont("Vector", 10); log('startTime: ' + times[0]);
console.log('start: ' + times[0]); log('endTime: ' + times[1]);
console.log('end: ' + times[1]);
if (times[0] !== undefined) { if (times[0] !== 0) {
g.setFontAlign(-1, -1, 0); g.setFontAlign(-1, -1, 0).
var startdate = new Date(times[0]*1000); drawString(getTimeString(times[0]*1000), 15, GraphYZero + 12);
g.drawString(startdate.local().as("0h:0m").str, 15, GraphYZero + 12);
} }
if (times[1] !== undefined) {
g.setFontAlign(1, -1, 0); if (times[1] !== 0) {
var enddate = new Date(times[1]*1000); var dateStr = getDateString(times[1]*1000);
g.drawString(enddate.local().as().str, GraphXMax, GraphYZero + 12); g.setFontAlign(-1, -1, 0).
drawString(dateStr, GraphXMax/2 - dateStr.length/2 - GraphMarkerOffset, GraphYZero + 12).
setFontAlign(1, -1, 0).
drawString(getTimeString(times[1]*1000), GraphXMax, GraphYZero + 12);
} }
console.log("Finished rendering data");
log("Finished rendering data");
Bangle.buzz(200, 0.3); Bangle.buzz(200, 0.3);
g.flip();
setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false}); setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false});
return;
} }
showMainMenu(); showMainMenu();

View File

@ -549,7 +549,26 @@ var locales = {
abday: "ned.,pon.,tor.,sre.,čet.,pet.,sob.", abday: "ned.,pon.,tor.,sre.,čet.,pet.,sob.",
day: "nedelja,ponedeljek,torek,sreda,četrtek,petek,sobota", day: "nedelja,ponedeljek,torek,sreda,četrtek,petek,sobota",
trans: { yes: "da", Yes: "Da", no: "ne", No: "Ne", ok: "ok", on: "Vklj.", off: "Izklj.", "< Back": "< Nazaj" } trans: { yes: "da", Yes: "Da", no: "ne", No: "Ne", ok: "ok", on: "Vklj.", off: "Izklj.", "< Back": "< Nazaj" }
}/*, },
"pt_PT": {
lang: "pt_PT",
decimal_point: ",",
thousands_sep: " ",
currency_symbol: "€",
int_curr_symbol: "EUR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: { 0: "am", 1: "pm" },
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%d %b %Y", 1: "%d/%m/%y" },
abmonth: "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez",
month: "Janeiro,Fevereiro,Março,Abril,Maio,Junho,Julho,Agosto,Setembro,Outubro,Novembro,Dezembro",
abday: "Dom,Seg,Ter,Qua,Qui,Sex,Sab",
day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado",
trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "ok", on: "on", off: "off" }
},
/*,
"he_IL": { // This won't work until we get a font - see https://github.com/espruino/BangleApps/issues/399 "he_IL": { // This won't work until we get a font - see https://github.com/espruino/BangleApps/issues/399
codePage : "ISO8859-8", codePage : "ISO8859-8",
lang: "he_IL", lang: "he_IL",

View File

@ -1,2 +1,5 @@
0.01: Initial Release 0.01: Initial Release
0.02: Color Themes, Smoother scrolling 0.02: Color Themes, Smoother scrolling
0.03: Added Spanish Language
0.04: Added German Language
0.05: BUGFIX: pedometer widget interfered with the clock Font Alignment

View File

@ -13,6 +13,8 @@ Use Button 1 (the top right button) to change the language
| English | English (Traditional) | French | Japanese (Romanji) | | English | English (Traditional) | French | Japanese (Romanji) |
| ---- | ---- | ---- | ---- | | ---- | ---- | ---- | ---- |
| ![](./format-01.jpg) | ![](format-02.jpg) | ![](format-03.jpg) |![](format-04.jpg) | | ![](./format-01.jpg) | ![](format-02.jpg) | ![](format-03.jpg) |![](format-04.jpg) |
| **German** | **Spanish** | | |
| ![](./format-05.jpg) | ![](format-06.jpg) | | |
### Button 3 ### Button 3
Button 3 (bottom right button) is used to change the colour Button 3 (bottom right button) is used to change the colour

View File

@ -4,7 +4,7 @@
</head> </head>
<body> <body>
<p>Please select watch languages</p> <p>Please select watch languages (Max 3, only the first 3 selected will be loaded)</p>
<table id="language_selection"> <table id="language_selection">
<tr> <tr>
@ -23,9 +23,11 @@
{name:"English", shortname:"en"}, {name:"English", shortname:"en"},
{name:"English(Traditional)",shortname:"en2"}, {name:"English(Traditional)",shortname:"en2"},
{name:"French",shortname:"fr"}, {name:"French",shortname:"fr"},
{name:"Japanese",shortname:"jp"} {name:"Japanese",shortname:"jp"},
{name:"Spanish",shortname:"es"},
{name:"German",shortname:"de"}
]; ];
var selected_languages = ["en","fr","jp"]; var selected_languages = ["en","es","jp"];
try{ try{
var stored = localStorage.getItem('slidingtext_stored') var stored = localStorage.getItem('slidingtext_stored')
if(stored) selected_languages = JSON.parse(stored); if(stored) selected_languages = JSON.parse(stored);
@ -50,7 +52,7 @@
for (var i=0; i<slidingtext_languages.length; i++) { for (var i=0; i<slidingtext_languages.length; i++) {
var curr_language = slidingtext_languages[i]; var curr_language = slidingtext_languages[i];
var checked=document.getElementById("enabled_"+i).checked; var checked=document.getElementById("enabled_"+i).checked;
if (checked) { if (checked && new_selected_languages.length < 3 ) {
new_selected_languages.push(curr_language.shortname); new_selected_languages.push(curr_language.shortname);
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -121,23 +121,27 @@ class ShiftText {
setBgColor(bg_color){ setBgColor(bg_color){
this.bg_color = bg_color; this.bg_color = bg_color;
} }
reset(){ reset(hard_reset) {
//console.log("reset"); //console.log("reset");
this.hide(); this.hide();
this.x = this.init_x; this.x = this.init_x;
this.y = this.init_y; this.y = this.init_y;
this.txt = this.init_txt; if (hard_reset) {
this.txt = this.init_txt;
}
this.show(); this.show();
if(this.timeoutId != null){ if(this.timeoutId != null){
clearTimeout(this.timeoutId); clearTimeout(this.timeoutId);
} }
} }
show() { show() {
g.setFontAlign(-1,-1,0);
g.setFont(this.font_name,this.font_size); g.setFont(this.font_name,this.font_size);
g.setColor(this.color[0],this.color[1],this.color[2]); g.setColor(this.color[0],this.color[1],this.color[2]);
g.drawString(this.txt, this.x, this.y); g.drawString(this.txt, this.x, this.y);
} }
hide(){ hide(){
g.setFontAlign(-1,-1,0);
g.setFont(this.font_name,this.font_size); g.setFont(this.font_name,this.font_size);
//console.log("bgcolor:" + this.bg_color); //console.log("bgcolor:" + this.bg_color);
g.setColor(this.bg_color[0],this.bg_color[1],this.bg_color[2]); g.setColor(this.bg_color[0],this.bg_color[1],this.bg_color[2]);
@ -249,12 +253,15 @@ function nextColorTheme(){
if(color_scheme_index >= row_displays.length){ if(color_scheme_index >= row_displays.length){
color_scheme_index = 0; color_scheme_index = 0;
} }
var color_scheme = color_schemes[color_scheme_index]; setColorScheme(color_schemes[color_scheme_index]);
reset_clock(true);
draw_clock();
}
function setColorScheme(color_scheme){
setColor(color_scheme.main_bar, setColor(color_scheme.main_bar,
color_scheme.other_bars, color_scheme.other_bars,
color_scheme.background); color_scheme.background);
reset_clock();
draw_clock();
} }
function setColor(main_color,other_color,bg_color){ function setColor(main_color,other_color,bg_color){
@ -274,8 +281,7 @@ function setColor(main_color,other_color,bg_color){
]); ]);
} }
// load the date formats required // load the date formats and laguages required
LANGUAGES_FILE = "slidingtext.languages.json"; LANGUAGES_FILE = "slidingtext.languages.json";
var LANGUAGES_DEFAULT = ["en","en2"]; var LANGUAGES_DEFAULT = ["en","en2"];
var locales = null; var locales = null;
@ -313,7 +319,7 @@ function changeFormatter(){
} }
console.log("changing to formatter " + date_formatter_idx); console.log("changing to formatter " + date_formatter_idx);
date_formatter = date_formatters[date_formatter_idx]; date_formatter = date_formatters[date_formatter_idx];
reset_clock(); reset_clock(true);
draw_clock(); draw_clock();
command_stack_high_priority.unshift( command_stack_high_priority.unshift(
function() { function() {
@ -340,23 +346,64 @@ function changeFormatter(){
} }
var DISPLAY_TEXT_X = 20;
function reset_clock(hard_reset){
console.log("reset_clock hard_reset:" + hard_reset);
function reset_clock(){ setColorScheme(color_schemes[color_scheme_index]);
//console.log("reset_clock"); if(!hard_reset && last_draw_time != null){
for (var i = 0; i < row_displays.length; i++) { // If its not a hard reset then we want to reset the
row_displays[i].speed_x = CLOCK_TEXT_SPEED_X; // rows set to the last time. If the last time is too long
row_displays[i].reset(); // ago then we fast forward to 1 min ago.
// In this way the watch wakes by scrolling
// off the last time and scroll on the new time
var reset_time = last_draw_time;
var last_minute_millis = Date.now() - 60000;
if(reset_time.getTime() < last_minute_millis){
reset_time = display_time(new Date(last_minute_millis));
}
var rows = date_formatter.formatDate(reset_time);
for (var i = 0; i < rows.length; i++) {
row_displays[i].hide();
row_displays[i].speed_x = CLOCK_TEXT_SPEED_X;
row_displays[i].x = DISPLAY_TEXT_X;
row_displays[i].y = row_displays[i].init_y;
if(row_displays[i].timeoutId != null){
clearTimeout(row_displays[i].timeoutId);
}
row_displays[i].setText(rows[i]);
row_displays[i].show();
}
} else {
// do a hard reset and clear everything out
for (var i = 0; i < row_displays.length; i++) {
row_displays[i].speed_x = CLOCK_TEXT_SPEED_X;
row_displays[i].reset(hard_reset);
}
} }
reset_commands(); reset_commands();
} }
let last_draw_time = null; let last_draw_time = null;
const next_minute_boundary_secs = 7.5; const next_minute_boundary_secs = 10;
function display_time(date){
if(date.getSeconds() > 60 - next_minute_boundary_secs){
console.log("forwarding to next minute");
return new Date(date.getTime() + next_minute_boundary_secs * 1000);
} else {
return date;
}
}
function draw_clock(){ function draw_clock(){
var date = new Date(); var date = new Date();
// we don't want the time to be displayed
// and then immediately be trigger another time
if(last_draw_time != null && if(last_draw_time != null &&
date.getTime() - last_draw_time.getTime() < next_minute_boundary_secs * 1000 && Date.now() - last_draw_time.getTime() < next_minute_boundary_secs * 1000 &&
has_commands() ){ has_commands() ){
console.log("skipping draw clock"); console.log("skipping draw clock");
return; return;
@ -364,13 +411,9 @@ function draw_clock(){
last_draw_time = date; last_draw_time = date;
} }
reset_commands(); reset_commands();
console.log("draw_clock:" + date.toISOString()); date = display_time(date);
// we don't want the time to be displayed console.log("draw_clock:" + last_draw_time.toISOString() + " display:" + date.toISOString());
// and then immediately be trigger another time // for debugging only
if(date.getSeconds() > 60 - next_minute_boundary_secs){
console.log("forwarding to next minute");
date = new Date(date.getTime() + next_minute_boundary_secs * 1000);
}
//date.setMinutes(37); //date.setMinutes(37);
var rows = date_formatter.formatDate(date); var rows = date_formatter.formatDate(date);
var display; var display;
@ -404,7 +447,7 @@ function display_row(display,txt){
//console.log("move in new:" + txt); //console.log("move in new:" + txt);
display.onFinished(next_command); display.onFinished(next_command);
display.setTextXPosition(txt, 240); display.setTextXPosition(txt, 240);
display.moveToX(20); display.moveToX(DISPLAY_TEXT_X);
} }
); );
} }
@ -421,14 +464,14 @@ function display_row(display,txt){
//console.log("move in:" + txt); //console.log("move in:" + txt);
display.onFinished(next_command); display.onFinished(next_command);
display.setTextXPosition(txt,240); display.setTextXPosition(txt,240);
display.moveToX(20); display.moveToX(DISPLAY_TEXT_X);
} }
); );
} else { } else {
command_stack_high_priority.push( command_stack_high_priority.push(
function(){ function(){
//console.log("move in2:" + txt); //console.log("move in2:" + txt);
display.setTextXPosition(txt,20); display.setTextXPosition(txt,DISPLAY_TEXT_X);
next_command(); next_command();
} }
); );
@ -445,10 +488,7 @@ function set_colorscheme(colorscheme_name){
if(color_schemes[i].name == colorscheme_name){ if(color_schemes[i].name == colorscheme_name){
color_scheme_index = i; color_scheme_index = i;
console.log("match"); console.log("match");
var color_scheme = color_schemes[color_scheme_index]; setColorScheme(color_schemes[color_scheme_index]);
setColor(color_scheme.main_bar,
color_scheme.other_bars,
color_scheme.background);
break; break;
} }
} }
@ -508,7 +548,7 @@ function button1pressed() {
function button3pressed() { function button3pressed() {
console.log("button3pressed"); console.log("button3pressed");
nextColorTheme(); nextColorTheme();
reset_clock(); reset_clock(true);
draw_clock(); draw_clock();
save_settings(); save_settings();
} }
@ -517,7 +557,7 @@ function button3pressed() {
let intervalRef = null; let intervalRef = null;
function clearTimers(){ function clearTimers(){
if(intervalRef) { if(intervalRef != null) {
clearInterval(intervalRef); clearInterval(intervalRef);
intervalRef = null; intervalRef = null;
} }
@ -532,22 +572,50 @@ function startTimers(){
draw_clock(); draw_clock();
} }
/**
* confirms that a redraw is needed by checking the last redraw time and
* the lcd state of the UI
* @returns {boolean|*}
*/
function shouldRedraw(){
return last_draw_time != null &&
Date.now() - last_draw_time.getTime() > next_minute_boundary_secs * 1000
&& Bangle.isLCDOn();
}
function scheduleDrawClock(){ function scheduleDrawClock(){
//console.log("scheduleDrawClock"); clearTimers();
if(intervalRef) clearTimers(); if (Bangle.isLCDOn()) {
intervalRef = setInterval(draw_clock, 60*1000); console.log("schedule draw of clock");
draw_clock(); intervalRef = setInterval(() => {
if (!shouldRedraw()) {
console.log("draw clock callback - skipped redraw");
} else {
console.log("draw clock callback");
draw_clock()
}
}, 60 * 1000
);
if (shouldRedraw()) {
draw_clock();
} else {
console.log("scheduleDrawClock - skipped redraw");
}
} else {
console.log("scheduleDrawClock - skipped not visible");
}
} }
Bangle.on('lcdPower', (on) => { Bangle.on('lcdPower', (on) => {
if (on) { if (on) {
console.log("lcdPower: on"); console.log("lcdPower: on");
Bangle.drawWidgets(); Bangle.drawWidgets();
reset_clock(); reset_clock(false);
startTimers(); startTimers();
} else { } else {
console.log("lcdPower: off"); console.log("lcdPower: off");
reset_clock(); reset_clock(false);
clearTimers(); clearTimers();
} }
}); });

View File

@ -0,0 +1,94 @@
var DateFormatter = require("slidingtext.dtfmt.js");
const germanNumberStr = [ ["ZERO",""], // 0
["EINS",""], // 1
["ZWEI",""], //2
["DREI",''], //3
["VIER",''], //4
["FÜNF",''], //5
["SECHS",''], //6
["SEIBEN",''], //7
["ACHT",''], //8
["NUEN",''], // 9,
["ZEHN",''], // 10
["ELF",''], // 11,
["ZWÖLF",''], // 12
["DREI",'ZEHN'], // 13
["VIER",'ZEHN'], // 14
["FÜNF",'ZEHN'], // 15
["SECH",'ZEHN'], // 16
["SIEB",'ZEHN'], // 17
["ACHT",'ZEHN'], // 18
["NEUN",'ZEHN'], // 19
];
const germanTensStr = ["ZERO",//0
"ZEHN",//10
"ZWANZIG",//20
"DREIßIG",//30
"VIERZIG",//40
"FÜNFZIG",//50
"SECHZIG"//60
]
const germanUnit = ["",//0
"EINUND",//1
"ZWEIUND",//2
"DREIUND",//3
"VIERUND", //4
"FÜNFUND", //5
"SECHSUND", //6
"SEIBENUND", //7
"ACHTUND", //8
"NEUNUND" //9
]
function germanHoursToText(hours){
hours = hours % 12;
if(hours == 0){
hours = 12;
}
return germanNumberStr[hours][0];
}
function germanMinsToText(mins) {
if (mins < 20) {
return germanNumberStr[mins];
} else {
var tens = (mins / 10 | 0);
var word1 = germanTensStr[tens];
var remainder = mins - tens * 10;
var word2 = germanUnit[remainder];
return [word2, word1];
}
}
class GermanDateFormatter extends DateFormatter {
constructor() { super();}
name(){return "German";}
formatDate(date){
var mins = date.getMinutes();
var hourOfDay = date.getHours();
var hours = germanHoursToText(hourOfDay);
//console.log('hourOfDay->' + hourOfDay + ' hours text->' + hours)
// Deal with the special times first
if(mins == 0){
var hours = germanHoursToText(hourOfDay);
return [hours,"UHR", "","",""];
} /*else if(mins == 30){
var hours = germanHoursToText(hourOfDay+1);
return ["", "", "HALB","", hours];
} else if(mins == 15){
var hours = germanHoursToText(hourOfDay);
return ["", "", "VIERTEL", "NACH",hours];
} else if(mins == 45) {
var hours = germanHoursToText(hourOfDay+1);
return ["", "", "VIERTEL", "VOR",hours];
} */ else {
var mins_txt = germanMinsToText(mins);
return [hours, "UHR", mins_txt[0],mins_txt[1]];
}
}
}
module.exports = GermanDateFormatter;

View File

@ -0,0 +1,77 @@
var DateFormatter = require("slidingtext.dtfmt.js");
const spanishNumberStr = [ ["ZERO"], // 0
["UNA",""], // 1
["DOS",""], //2
["TRES",''], //3
["CUATRO",''], //4
["CINCO",''], //5
["SEIS",''], //6
["SEITO",''], //7
["OCHO",''], //8
["NUEVE",''], // 9,
["DIEZ",''], // 10
["ONCE",''], // 11,
["DOCE",''], // 12
["TRECE",''], // 13
["CATORCE",''], // 14
["QUINCE",''], // 15
["DIECI",'SEIS'], // 16
["DIECI",'SIETE'], // 17
["DIECI",'OCHO'], // 18
["DIECI",'NEUVE'], // 19
["VEINTA",''], // 20
["VEINTI",'UNO'], // 21
["VEINTI",'DOS'], // 22
["VEINTI",'TRES'], // 23
["VEINTI",'CUATRO'], // 24
["VEINTI",'CINCO'], // 25
["VEINTI",'SEIS'], // 26
["VEINTI",'SIETE'], // 27
["VEINTI",'OCHO'], // 28
["VEINTI",'NUEVE'] // 29
];
function spanishHoursToText(hours){
hours = hours % 12;
if(hours == 0){
hours = 12;
}
return spanishNumberStr[hours][0];
}
function spanishMinsToText(mins){
return spanishNumberStr[mins];
}
class SpanishDateFormatter extends DateFormatter {
constructor() { super();}
name(){return "Spanish";}
formatDate(date){
var mins = date.getMinutes();
var hourOfDay = date.getHours();
if(mins > 30){
hourOfDay += 1;
}
var hours = spanishHoursToText(hourOfDay);
//console.log('hourOfDay->' + hourOfDay + ' hours text->' + hours)
// Deal with the special times first
if(mins == 0){
return [hours,"", "","",""];
} else if(mins == 30){
return [hours, "Y", "MEDIA",""];
} else if(mins == 15){
return [hours, "Y", "CUARTO",""];
} else if(mins == 45) {
return [hours, "MENOS", "CUARTO",""];
} else if(mins > 30){
var mins_txt = spanishMinsToText(60-mins);
return [hours, "MENOS", mins_txt[0],mins_txt[1]];
} else {
var mins_txt = spanishMinsToText(mins);
return [hours, "Y", mins_txt[0],mins_txt[1]];
}
}
}
module.exports = SpanishDateFormatter;

View File

@ -46,12 +46,12 @@ class FrenchDateFormatter extends DateFormatter {
} else if(mins == 30){ } else if(mins == 30){
return [hours, heures,'ET DEMIE']; return [hours, heures,'ET DEMIE'];
} else if(mins == 15){ } else if(mins == 15){
return [hours, heures,'ET QUERT']; return [hours, heures,'ET QUART'];
} else if(mins == 45){ } else if(mins == 45){
var next_hour = date.getHours() + 1; var next_hour = date.getHours() + 1;
hours = frenchHoursToText(next_hour); hours = frenchHoursToText(next_hour);
heures = frenchHeures(next_hour); heures = frenchHeures(next_hour);
return [hours, heures,"MOINS",'LET QUERT']; return [hours, heures,"MOINS",'LET QUART'];
} }
if(mins > 30){ if(mins > 30){
var to_mins = 60-mins; var to_mins = 60-mins;