Merge branch 'master' into message_buzz_pattern_for_calls

pull/2005/head
Marco Heiming 2022-07-06 11:32:41 +02:00
commit 39e6ccb823
111 changed files with 1677 additions and 390 deletions

View File

@ -11,3 +11,4 @@
0.10: Fix SMS bug
0.12: Use default Bangle formatter for booleans
0.13: Added Bangle.http function (see Readme file for more info)
0.14: Fix timeout of http function not beeing cleaned up

View File

@ -122,14 +122,14 @@
"http":function() {
//get the promise and call the promise resolve
if (Bangle.httpRequest === undefined) return;
var objID=Bangle.httpRequest[event.id];
if (objID === undefined) return; //already timedout or wrong id
var request=Bangle.httpRequest[event.id];
if (request === undefined) return; //already timedout or wrong id
delete Bangle.httpRequest[event.id];
clearInterval(objID.t); //t = timeout variable
clearTimeout(request.t); //t = timeout variable
if(event.err!==undefined) //if is error
objID.j(event.err); //r = reJect function
request.j(event.err); //r = reJect function
else
objID.r(event); //r = resolve function
request.r(event); //r = resolve function
}
};
var h = HANDLERS[event.t];

View File

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

2
apps/bowserWF/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
...
0.02: First update with ChangeLog Added

View File

@ -23,3 +23,9 @@
0.08: Allow scanning for devices in settings
0.09: Misc Fixes and improvements (https://github.com/espruino/BangleApps/pull/1655)
0.10: Use default Bangle formatter for booleans
0.11: App now shows status info while connecting
Fixes to allow cached BluetoothRemoteGATTCharacteristic to work with 2v14.14 onwards (>1 central)
0.12: Fix HRM fallback handling
Use default boolean formatter in custom menu and directly apply config if useful
Allow recording unmodified internal HR
Better connection retry handling

View File

@ -19,7 +19,14 @@ Just install the app, then install an app that uses the heart rate monitor.
Once installed you will have to go into this app's settings while your heart rate monitor
is available for bluetooth pairing and scan for devices.
**To disable this and return to normal HRM, uninstall the app**
**To disable this and return to normal HRM, uninstall the app or change the settings**
### Modes
* Off - Internal HRM is used, no attempt on connecting to BT HRM.
* Default - Replaces internal HRM with BT HRM and falls back to internal HRM if no valid measurements received.
* Both - The BT HRM needs to be started explicitly by an app that wants to use it. BT HRM has its own event and is completely separated from the internal HRM. Apps not supporting the BT HRM will not see the BT HRM measurements.
* Custom - Combine low level settings as you see fit.
## Compatible Heart Rate Monitors
@ -35,6 +42,10 @@ So far it has been tested on:
* Polar OH1
* Wahoo TICKR X 2
## Recorder plugin
The recorder plugin can record the BT HRM event (blue) and the original unchanged HRM event (green). This is mainly useful for debugging purposes or comparing the BT with the internal HRM, as the resulting "merged" HRM can be recordet using the default HRM recorder.
## Internals
This replaces `Bangle.setHRMPower` with its own implementation.

View File

@ -5,11 +5,11 @@
);
var log = function(text, param){
if (global.showStatusInfo)
showStatusInfo(text);
if (settings.debuglog){
var logline = new Date().toISOString() + " - " + text;
if (param){
logline += " " + JSON.stringify(param);
}
if (param) logline += ": " + JSON.stringify(param);
print(logline);
}
};
@ -30,7 +30,7 @@
};
var addNotificationHandler = function(characteristic) {
log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler);
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
};
@ -61,7 +61,8 @@
writeCache(cache);
};
var characteristicsFromCache = function() {
var characteristicsFromCache = function(device) {
var service = { device : device }; // fake a BluetoothRemoteGATTService
log("Read cached characteristics");
var cache = getCache();
if (!cache.characteristics) return [];
@ -75,6 +76,7 @@
r.properties = {};
r.properties.notify = cached.notify;
r.properties.read = cached.read;
r.service = service;
addNotificationHandler(r);
log("Restored characteristic: ", r);
restored.push(r);
@ -92,13 +94,24 @@
"0x180f", // Battery
];
var bpmTimeout;
var supportedCharacteristics = {
"0x2a37": {
//Heart rate measurement
active: false,
handler: function (dv){
var flags = dv.getUint8(0);
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
supportedCharacteristics["0x2a37"].active = bpm > 0;
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
if (supportedCharacteristics["0x2a37"].active) stopFallback();
if (bpmTimeout) clearTimeout(bpmTimeout);
bpmTimeout = setTimeout(()=>{
supportedCharacteristics["0x2a37"].active = false;
startFallback();
}, 3000);
var sensorContact;
@ -141,8 +154,8 @@
src: "bthrm"
};
log("Emitting HRM: ", repEvent);
Bangle.emit("HRM", repEvent);
log("Emitting HRM", repEvent);
Bangle.emit("HRM_int", repEvent);
}
var newEvent = {
@ -155,7 +168,7 @@
if (battery) newEvent.battery = battery;
if (sensorContact) newEvent.contact = sensorContact;
log("Emitting BTHRM: ", newEvent);
log("Emitting BTHRM", newEvent);
Bangle.emit("BTHRM", newEvent);
}
},
@ -200,6 +213,10 @@
};
if (settings.enabled){
Bangle.isBTHRMActive = function (){
return supportedCharacteristics["0x2a37"].active;
};
Bangle.isBTHRMOn = function(){
return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0);
};
@ -210,24 +227,28 @@
}
if (settings.replace){
var origIsHRMOn = Bangle.isHRMOn;
Bangle.origIsHRMOn = Bangle.isHRMOn;
Bangle.isHRMOn = function() {
if (settings.enabled && !settings.replace){
return origIsHRMOn();
return Bangle.origIsHRMOn();
} else if (settings.enabled && settings.replace){
return Bangle.isBTHRMOn();
}
return origIsHRMOn() || Bangle.isBTHRMOn();
return Bangle.origIsHRMOn() || Bangle.isBTHRMOn();
};
}
var clearRetryTimeout = function() {
var clearRetryTimeout = function(resetTime) {
if (currentRetryTimeout){
log("Clearing timeout " + currentRetryTimeout);
clearTimeout(currentRetryTimeout);
currentRetryTimeout = undefined;
}
if (resetTime) {
log("Resetting retry time");
retryTime = initialRetryTime;
}
};
var retry = function() {
@ -236,8 +257,8 @@
if (!currentRetryTimeout){
var clampedTime = retryTime < 100 ? 100 : retryTime;
log("Set timeout for retry as " + clampedTime);
log("Set timeout for retry as " + clampedTime);
clearRetryTimeout();
currentRetryTimeout = setTimeout(() => {
log("Retrying");
@ -257,11 +278,11 @@
var buzzing = false;
var onDisconnect = function(reason) {
log("Disconnect: " + reason);
log("GATT: ", gatt);
log("Characteristics: ", characteristics);
retryTime = initialRetryTime;
clearRetryTimeout();
switchInternalHrm();
log("GATT", gatt);
log("Characteristics", characteristics);
clearRetryTimeout(reason != "Connection Timeout");
supportedCharacteristics["0x2a37"].active = false;
startFallback();
blockInit = false;
if (settings.warnDisconnect && !buzzing){
buzzing = true;
@ -273,13 +294,13 @@
};
var createCharacteristicPromise = function(newCharacteristic) {
log("Create characteristic promise: ", newCharacteristic);
log("Create characteristic promise", newCharacteristic);
var result = Promise.resolve();
// For values that can be read, go ahead and read them, even if we might be notified in the future
// Allows for getting initial state of infrequently updating characteristics, like battery
if (newCharacteristic.readValue){
result = result.then(()=>{
log("Reading data for " + JSON.stringify(newCharacteristic));
log("Reading data", newCharacteristic);
return newCharacteristic.readValue().then((data)=>{
if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
supportedCharacteristics[newCharacteristic.uuid].handler(data);
@ -289,8 +310,8 @@
}
if (newCharacteristic.properties.notify){
result = result.then(()=>{
log("Starting notifications for: ", newCharacteristic);
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started for ", newCharacteristic));
log("Starting notifications", newCharacteristic);
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
if (settings.gracePeriodNotification > 0){
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
startPromise = startPromise.then(()=>{
@ -301,7 +322,7 @@
return startPromise;
});
}
return result.then(()=>log("Handled characteristic: ", newCharacteristic));
return result.then(()=>log("Handled characteristic", newCharacteristic));
};
var attachCharacteristicPromise = function(promise, characteristic) {
@ -312,11 +333,11 @@
};
var createCharacteristicsPromise = function(newCharacteristics) {
log("Create characteristics promise: ", newCharacteristics);
log("Create characteristics promis ", newCharacteristics);
var result = Promise.resolve();
for (var c of newCharacteristics){
if (!supportedCharacteristics[c.uuid]) continue;
log("Supporting characteristic: ", c);
log("Supporting characteristic", c);
characteristics.push(c);
if (c.properties.notify){
addNotificationHandler(c);
@ -328,10 +349,10 @@
};
var createServicePromise = function(service) {
log("Create service promise: ", service);
log("Create service promise", service);
var result = Promise.resolve();
result = result.then(()=>{
log("Handling service: " + service.uuid);
log("Handling service" + service.uuid);
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
});
return result.then(()=>log("Handled service" + service.uuid));
@ -368,7 +389,7 @@
}
promise = promise.then((d)=>{
log("Got device: ", d);
log("Got device", d);
d.on('gattserverdisconnected', onDisconnect);
device = d;
});
@ -379,14 +400,14 @@
});
} else {
promise = Promise.resolve();
log("Reuse device: ", device);
log("Reuse device", device);
}
promise = promise.then(()=>{
if (gatt){
log("Reuse GATT: ", gatt);
log("Reuse GATT", gatt);
} else {
log("GATT is new: ", gatt);
log("GATT is new", gatt);
characteristics = [];
var cachedId = getCache().id;
if (device.id !== cachedId){
@ -404,7 +425,10 @@
promise = promise.then((gatt)=>{
if (!gatt.connected){
var connectPromise = gatt.connect(connectSettings);
log("Connecting...");
var connectPromise = gatt.connect(connectSettings).then(function() {
log("Connected.");
});
if (settings.gracePeriodConnect > 0){
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
connectPromise = connectPromise.then(()=>{
@ -432,7 +456,7 @@
promise = promise.then(()=>{
if (!characteristics || characteristics.length === 0){
characteristics = characteristicsFromCache();
characteristics = characteristicsFromCache(device);
}
});
@ -445,11 +469,11 @@
});
characteristicsPromise = characteristicsPromise.then((services)=>{
log("Got services:", services);
log("Got services", services);
var result = Promise.resolve();
for (var service of services){
if (!(supportedServices.includes(service.uuid))) continue;
log("Supporting service: ", service.uuid);
log("Supporting service", service.uuid);
result = attachServicePromise(result, service);
}
if (settings.gracePeriodService > 0) {
@ -473,7 +497,7 @@
return promise.then(()=>{
log("Connection established, waiting for notifications");
characteristicsToCache(characteristics);
clearRetryTimeout();
clearRetryTimeout(true);
}).catch((e) => {
characteristics = [];
log("Error:", e);
@ -491,12 +515,14 @@
isOn = Bangle._PWR.BTHRM.length;
// so now we know if we're really on
if (isOn) {
switchFallback();
if (!Bangle.isBTHRMConnected()) initBt();
} else { // not on
log("Power off for " + app);
clearRetryTimeout(true);
if (gatt) {
if (gatt.connected){
log("Disconnect with gatt: ", gatt);
log("Disconnect with gatt", gatt);
try{
gatt.disconnect().then(()=>{
log("Successful disconnect");
@ -511,7 +537,33 @@
}
};
var origSetHRMPower = Bangle.setHRMPower;
if (settings.replace){
Bangle.on("HRM", (e) => {
e.modified = true;
Bangle.emit("HRM_int", e);
});
Bangle.origOn = Bangle.on;
Bangle.on = function(name, callback) {
if (name == "HRM") {
Bangle.origOn("HRM_int", callback);
} else {
Bangle.origOn(name, callback);
}
};
Bangle.origRemoveListener = Bangle.removeListener;
Bangle.removeListener = function(name, callback) {
if (name == "HRM") {
Bangle.origRemoveListener("HRM_int", callback);
} else {
Bangle.origRemoveListener(name, callback);
}
};
}
Bangle.origSetHRMPower = Bangle.setHRMPower;
if (settings.startWithHrm){
@ -521,40 +573,54 @@
Bangle.setBTHRMPower(isOn, app);
}
if ((settings.enabled && !settings.replace) || !settings.enabled){
origSetHRMPower(isOn, app);
Bangle.origSetHRMPower(isOn, app);
}
};
}
var fallbackInterval;
var fallbackActive = false;
var inSwitch = false;
var switchInternalHrm = function() {
if (settings.allowFallback && !fallbackInterval){
log("Fallback to HRM enabled");
origSetHRMPower(1, "bthrm_fallback");
fallbackInterval = setInterval(()=>{
if (Bangle.isBTHRMConnected()){
origSetHRMPower(0, "bthrm_fallback");
clearInterval(fallbackInterval);
fallbackInterval = undefined;
log("Fallback to HRM disabled");
}
}, settings.fallbackTimeout);
var stopFallback = function(){
if (fallbackActive){
Bangle.origSetHRMPower(0, "bthrm_fallback");
fallbackActive = false;
log("Fallback to HRM disabled");
}
};
var startFallback = function(){
if (!fallbackActive && settings.allowFallback) {
fallbackActive = true;
Bangle.origSetHRMPower(1, "bthrm_fallback");
log("Fallback to HRM enabled");
}
};
var switchFallback = function() {
log("Check falling back to HRM");
if (!inSwitch){
inSwitch = true;
if (Bangle.isBTHRMActive()){
stopFallback();
} else {
startFallback();
}
}
inSwitch = false;
};
if (settings.replace){
log("Replace HRM event");
if (Bangle._PWR && Bangle._PWR.HRM){
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
var app = Bangle._PWR.HRM[i];
log("Moving app " + app);
origSetHRMPower(0, app);
Bangle.origSetHRMPower(0, app);
Bangle.setBTHRMPower(1, app);
if (Bangle._PWR.HRM===undefined) break;
}
}
switchInternalHrm();
}
E.on("kill", ()=>{

View File

@ -42,12 +42,20 @@ function draw(y, type, event) {
if (event.energy) str += " kJoule: " + event.energy.toFixed(0);
g.setFontVector(12).drawString(str,px,y+60);
}
}
var firstEventBt = true;
var firstEventInt = true;
// This can get called for the boot code to show what's happening
function showStatusInfo(txt) {
var R = Bangle.appRect;
g.reset().clearRect(R.x,R.y2-8,R.x2,R.y2).setFont("6x8");
txt = g.wrapString(txt, R.w)[0];
g.setFontAlign(0,1).drawString(txt, (R.x+R.x2)/2, R.y2);
}
function onBtHrm(e) {
if (firstEventBt){
clear(24);

View File

@ -2,7 +2,7 @@
"id": "bthrm",
"name": "Bluetooth Heart Rate Monitor",
"shortName": "BT HRM",
"version": "0.10",
"version": "0.12",
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
"icon": "app.png",
"type": "app",

View File

@ -32,8 +32,45 @@
Bangle.removeListener('BTHRM', onHRM);
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
},
draw : (x,y) => g.setColor((bpm != "")?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
draw : (x,y) => g.setColor((Bangle.isBTHRMActive && Bangle.isBTHRMActive())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
};
}
};
recorders.hrmint = function() {
var active = false;
var bpmTimeout;
var bpm = "", bpmConfidence = "", src="";
function onHRM(h) {
bpmConfidence = h.confidence;
bpm = h.bpm;
srv = h.src;
if (h.bpm > 0){
active = true;
print("active" + h.bpm);
if (bpmTimeout) clearTimeout(bpmTimeout);
bpmTimeout = setTimeout(()=>{
print("inactive");
active = false;
},3000);
}
}
return {
name : "HR int",
fields : ["Heartrate", "Confidence"],
getValues : () => {
var r = [bpm,bpmConfidence,src];
bpm = ""; bpmConfidence = ""; src="";
return r;
},
start : () => {
Bangle.origOn('HRM', onHRM);
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder");
},
stop : () => {
Bangle.removeListener('HRM', onHRM);
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder");
},
draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
};
};
})

View File

@ -17,6 +17,14 @@
var settings;
readSettings();
function applyCustomSettings(){
writeSettings("enabled",true);
writeSettings("replace",settings.custom_replace);
writeSettings("startWithHrm",settings.custom_startWithHrm);
writeSettings("allowFallback",settings.custom_allowFallback);
writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
}
function buildMainMenu(){
var mainmenu = {
'': { 'title': 'Bluetooth HRM' },
@ -35,7 +43,6 @@
case 1:
writeSettings("enabled",true);
writeSettings("replace",true);
writeSettings("debuglog",false);
writeSettings("startWithHrm",true);
writeSettings("allowFallback",true);
writeSettings("fallbackTimeout",10);
@ -43,17 +50,11 @@
case 2:
writeSettings("enabled",true);
writeSettings("replace",false);
writeSettings("debuglog",false);
writeSettings("startWithHrm",false);
writeSettings("allowFallback",false);
break;
case 3:
writeSettings("enabled",true);
writeSettings("replace",settings.custom_replace);
writeSettings("debuglog",settings.custom_debuglog);
writeSettings("startWithHrm",settings.custom_startWithHrm);
writeSettings("allowFallback",settings.custom_allowFallback);
writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
applyCustomSettings();
break;
}
writeSettings("mode",v);
@ -138,23 +139,23 @@
'< Back': function() { E.showMenu(buildMainMenu()); },
'Replace HRM': {
value: !!settings.custom_replace,
format: v => settings.custom_replace ? "On" : "Off",
onchange: v => {
writeSettings("custom_replace",v);
if (settings.mode == 3) applyCustomSettings();
}
},
'Start w. HRM': {
value: !!settings.custom_startWithHrm,
format: v => settings.custom_startWithHrm ? "On" : "Off",
onchange: v => {
writeSettings("custom_startWithHrm",v);
if (settings.mode == 3) applyCustomSettings();
}
},
'HRM Fallback': {
value: !!settings.custom_allowFallback,
format: v => settings.custom_allowFallback ? "On" : "Off",
onchange: v => {
writeSettings("custom_allowFallback",v);
if (settings.mode == 3) applyCustomSettings();
}
},
'Fallback Timeout': {
@ -165,6 +166,7 @@
format: v=>v+"s",
onchange: v => {
writeSettings("custom_fallbackTimout",v*1000);
if (settings.mode == 3) applyCustomSettings();
}
},
};

View File

@ -8,4 +8,6 @@
0.08: Select the color of widgets correctly. Additional settings to hide colon.
0.09: Larger font size if colon is hidden to improve readability further.
0.10: HomeAssistant integration if HomeAssistant is installed.
0.11: Performance improvements.
0.11: Performance improvements.
0.12: Implements a 2D menu.
0.13: Clicks < 24px are for widgets, if fullscreen mode is disabled.

View File

@ -1,18 +1,46 @@
# BW Clock
A very minimalistic clock with date and time in focus.
![](screenshot.png)
## Features
- Fullscreen on/off
- Tab left/right of screen to show steps, temperature etc.
- Enable / disable lock icon in the settings.
- If the "sched" app is installed tab top / bottom of the screen to set the timer.
- If HomeAssistant is installed, triggers are shown. Simple select the trigger and touch the middle of the screen to send the trigger to HomeAssistant.
- The design is adapted to the theme of your bangle.
- The colon (e.g. 7:35 = 735) can be hidden now in the settings.
The BW clock provides many features as well as 3rd party integrations:
- Bangle data such as steps, heart rate, battery or charging state.
- A timer can be set directly. *Requirement: Scheduler library*
- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden.
## Menu
2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to set a timer or send a HomeAssistant trigger.
Simply click left / right to go through the menu entries such as Bangle, Timer etc.
and click up/down to move into this sub-menu. You can then click in the middle of the screen
to e.g. send a trigger via HomeAssistant once you selected it.
```
+5min
|
Bangle -- Timer[Optional] -- Weather[Optional] -- HomeAssistant [Optional]
| | | |
Bpm -5min Temperature Trigger1
| | |
Steps ... ...
|
Battery
```
## Settings
- Fullscreen on/off (widgets are still loaded).
- Enable/disable lock icon in the settings. Useful if fullscreen is on.
- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further.
- There are no design settings, as your bangle sys settings are used.
## Thanks to
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
## Creator
- [David Peer](https://github.com/peerdavid)
[David Peer](https://github.com/peerdavid)

View File

@ -1,10 +1,10 @@
/*
/************
* Includes
*/
const locale = require('locale');
const storage = require('Storage');
/*
/************
* Statics
*/
const SETTINGS_FILE = "bwclk.setting.json";
@ -12,14 +12,15 @@ const TIMER_IDX = "bwclk";
const W = g.getWidth();
const H = g.getHeight();
/*
/************
* Settings
*/
let settings = {
fullscreen: false,
showLock: true,
hideColon: false,
showInfo: 0,
menuPosX: 0,
menuPosY: 0,
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
@ -28,22 +29,10 @@ for (const key in saved_settings) {
}
/*
/************
* Assets
*/
// Manrope font
Graphics.prototype.setLargeFont = function(scale) {
// Actual height 48 (49 - 2)
this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('AFcH+AHFh/gA4sf4AHFn+AA4t/E43+AwsB/gHFgf4PH4AMgJ9Ngf/Pot//6bF/59F///PokfA4J9DEgIABEwYkB/7DDEgIlFCoRMDEgQsEDoRLEEgpoBA4JhGOIsHZ40PdwwA/L4SjHNAgGCP4cHA4wWDA4aVCA4gGDA4SNBe4IiBA4MPHYRBBEwScCA4d/EQUBaoRKDA4UBLQYECgb+EAgMHYYcHa4MPHoLBCBgMfYgcfBgM/PIc/BgN/A4YECIIQEDHwkDHwQHDGwQHENQUHA4d/QIQnCRIJJCSgYTCA4hqCA4hqCA4hiCA4ZCEA4RFBGYbrFAHxDGSohdDcgagFAAjPCEzicDToU/A4jPCAwbQCBwgrBgIHEFYKrDWoa7DaggA/AC0PAYV+AYSBCgKpCg4DDVIUfAYZ9BToIDDPoKVBAYfARoQDDXgMPFwTIBdYSYCv4LCv7zCXgYKCXAK8CHoUPXgY9Cn/vEYMPEwX/z46Bj4mBgf+n77CDwX4v54EIIIzCOgX/4I+CAQI9BHYQCCQ4I7CRASDBHYQHCv/Aj4+BGYIeBGAI+Bj/8AIIRBQIZjCRIiWBXgYHCPQgHBBgJ6DA4IEBPQaKBGYQ+BbgiCCAGZFDIIUBaAZBCgYHCQAQTBA4SACUwS8DDYQHBQAbVCQAYwBA4SABgYEBPoQCBFgU/CQWACgRDCHwKVCIYX+aYRDCHwMPAgY+Cn4EDHwX/AgY+B8bEFj/HA4RGCn+f94MBv45Cv+fA4J6C//+j5gBGIMBFoJWBQoRMB8E//4DBHIJcBv4HBEwJUCA4ImCj5MBA4KZCPYQHBZgRBCE4LICvwaCXAYA5PgQAEMIQAEUwQADQAJlCAARlBWYIACT4JtDAAMPA4IWESgg8CAwI+EEoPhHwYlCgY+DEoP4g4+DEoPAh4+CEoReBHwUfLYU/CwgMBXARqBHYQCCGoIjBgI+CgZSCHwcHAYY+Ch4lBJ4IbCjhACPwqUBPwqFCPwhQBIQZ+DOAKVFXooHCXop9DFAi8EFAT0GPoYAygwFEgOATISLDwBWDTQc/A4L6CTQKkCVQX+BYIHBDwX+BYIHBVQX8B4KqD+/wA4aBBj/AgK8CQIIJBA4a/BBIMBAgL/BAgUDYgL/BAII7BAQXgAII7BAQXAYQQxBYARrCMwQ0BAgV/HwYECHwgEBgY+EA4MPGwI8BA4UfGwI8BgYHBPofAQYOHPoeAR4QmBHwQHCEwI+CA4RVBHwQHCaggnBDwQHEHoIAEEQIA6v5NFfgSECBwZtEf4IHFOYQHEj4HGDwYHCDwPgv/jA4UHXQS8E/ED/AHDZ4MPSYKlCv+AYwIHDDwL7EgL7DAgTzCEwIpCeYTZBg4CBeYIJBAgICBFgIJBAgICBeYIEDHII0BAgg+EgI5CMocHGwJBCA4MfGwMD/h/BwF/PoQHC451CJIMDSgIjBA4PAA4QmBA4IhBA4JVBgEMA4bUDV4QeCAAf/HoIAENIIApOoIAEW4QAEW4QAEW4QAEWQRSFNIcDfYQMDny8DO4Q7BAQQjCewh+EHwcPToQ+Dv//ewkHUoI+En68DeIS0EHwMf/46CeYYlCHwQ0BKIY+BGgJ4Dh/nGgZZCAwKPEHYLpFDoKuFGgj4JgY0EHwQ0EYhIA6MAkf+BRBLIa5BQAJSCBgP4R4iVB/YHERoIACA4QGDE4SFBAoV/A4MH/ggBWIL7C8EfVoL4DwBHBFYIHBfYIRBAgT7CDgQEBgP4BgUBEIMDDgIMBgYMBg/gBgS5Ch/ABgUPFIMf4EHA4IEBHwUPCgJGCIIM/CgLgCAQJlBFIQFB44HBEIUBQYc/EIIHDAAIuBA4oeBRoSfBLAIHC/gHBEwIXC+AHBZghHBDwQADj4WCAHEPAwpWBKYYOCLwIHELYJUBghlDA4UcQogHBvgeDD4K0DDwIHBWgQeB4CyBh68CUAMf8DeCdIYHDdIfAfYjxCAgj2BAgbHCvwJCIIYCBBIMDHIX4BgUHFwMD+AMCA4Q0BAgg5CHwxICAQY5BdgQHBEgMDIYV/DgR1CA4PwP4KvDRgIACEYIHFWggABMQQHEZwd/Dwq1DHoTFEdooA/ACrBBcAZmC8DTCAATGBaYR+DwDTCRwbYDAASLBCIIGCFgQRBAG4='))),
46,
atob("EhooGyUkJiUnISYnFQ=="),
63+(scale<<8)+(1<<16)
);
return this;
};
Graphics.prototype.setXLargeFont = function(scale) {
// Actual height 53 (55 - 3)
this.setFontCustom(
@ -54,12 +43,26 @@ Graphics.prototype.setXLargeFont = function(scale) {
);
};
Graphics.prototype.setLargeFont = function(scale) {
// Actual height 47 (48 - 2)
this.setFontCustom(
atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAD/AAAAAAAAP/wAAAAAAAf/8AAAAAAB///AAAAAAH///wAAAAAf///8AAAAB/////AAAAH////8AAAAP////wAAAA/////AAAAB////+AAAAA////4AAAAAP///gAAAAAD//+AAAAAAA//4AAAAAAAP/gAAAAAAAD/AAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///+AAAAAB////8AAAAB/////wAAAA/////+AAAA//////wAAAf/////+AAAH//////wAAD//////+AAB/+AAAf/gAAf+AAAA/8AAH/AAAAH/AAD/gAAAA/4AA/wAAAAH+AAP8AAAAB/gAD+AAAAAf4AA/gAAAAH+AAP4AAAAA/gAD+AAAAAf4AA/wAAAAH+AAP8AAAAB/gAD/AAAAA/4AA/4AAAAP+AAH/AAAAH/AAB/4AAAH/wAAP/wAAP/4AAD//////+AAAf//////AAAD//////gAAAf/////wAAAD/////4AAAAf////4AAAAB////4AAAAAB///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAH/AAAAAAAAD/gAAAAAAAA/4AAAAAAAAf8AAAAAAAAH+AAAAAAAAD/gAAAAAAAB/wAAAAAAAAf8AAAAAAAAP///////AAD///////wAA///////8AAP///////AAD///////wAA///////8AAP///////AAD///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAB/AAAA/gAAA/wAAA/4AAAf8AAAf+AAAP/AAAP/gAAH/wAAH/4AAD/8AAD/+AAB//AAA//gAA//wAAf/AAAP/8AAH/AAAH//AAD/gAAD//wAA/wAAB//8AAP8AAA///AAD/AAAf+fwAA/gAAP/n8AAP4AAH/x/AAD+AAD/4fwAA/gAB/8H8AAP8AAf+B/AAD/AAP/AfwAA/4AH/gH8AAH/AH/wB/AAB/8H/4AfwAAP///8AH8AAD////AB/AAAf///gAfwAAD///wAH8AAAf//4AB/AAAD//4AAfwAAAP/8AAH8AAAAf4AAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADgAAAfwAAAB+AAAH8AAAAfwAAB/AAAAH+AAAfwAAAB/wAAH8AAAA/+AAB/AAAAP/gAAfwA4AA/8AAH8AfgAH/AAB/AP8AA/4AAfwD/gAH+AAH8B/4AB/gAB/A/8AAf4AAfwf/AAD+AAH8P/wAA/gAB/H/8AAf4AAfz//gAH+AAH8//4AB/gAB/f//AA/4AAf/+/4Af8AAH//P/AP/AAB//j////gAAf/wf///4AAH/4H///8AAB/8A///+AAAf+AH///AAAH/AA///gAAB/gAD//wAAAfwAAP/wAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAH/wAAAAAAAH/8AAAAAAAH//AAAAAAAH//wAAAAAAH//8AAAAAAH///AAAAAAH///wAAAAAH///8AAAAAP//9/AAAAAP//8fwAAAAP//4H8AAAAP//4B/AAAAP//4AfwAAAP//4AH8AAAD//4AB/AAAA//4AAfwAAAP/4AAH8AAAD/wAAB/AAAA/wAAAfwAAAPwAH////AADwAB////wAAwAAf///8AAAAAH////AAAAAB////wAAAAAf///8AAAAAH////AAAAAA////wAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAGAHwAAAB///gB+AAAH///8AfwAAB////AP+AAAf///wD/wAAH///+A/+AAB////gP/gAAf///4A/8AAH/8P8AH/AAB/AD+AA/4AAfwA/gAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/AAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/gAH+AAH8Af4AB/gAB/AH/AA/wAAfwB/4Af8AAH8AP/AP/AAB/AD////gAAfwAf///wAAH8AD///8AAB/AA///+AAAfwAH///AAAAAAA///gAAAAAAD//gAAAAAAAP/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAAAH////wAAAAH/////AAAAD/////4AAAB//////AAAA//////4AAAf//////AAAP//////4AAD/8D/w/+AAB/4B/wD/wAAf8A/wAf8AAP+AP4AD/gAD/AD+AAf4AA/wB/AAH+AAP4AfwAB/gAD+AH8AAf4AA/gB/AAH+AAP4AfwAB/gAD+AH+AAf4AA/wB/gAH+AAP8Af8AD/gAD/gH/gB/wAAf8A/8A/8AAH/AP///+AAB/gB////gAAPwAP///wAAB4AD///4AAAMAAf//8AAAAAAD//+AAAAAAAP/+AAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAABwAAfwAAAAB8AAH8AAAAD/AAB/AAAAD/wAAfwAAAH/8AAH8AAAH//AAB/AAAP//wAAfwAAP//8AAH8AAf//+AAB/AAf//8AAAfwA///8AAAH8A///4AAAB/A///4AAAAfx///wAAAAH9///wAAAAB////gAAAAAf///gAAAAAH///AAAAAAB///AAAAAAAf/+AAAAAAAH/+AAAAAAAB/8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAf/AAAAAP+Af/8AAAAP/4P//wAAAP//P//+AAAH//////wAAB//////8AAA///////gAAf//////8AAH////gP/AAD/wf/wA/wAA/4D/4AP+AAP8Af8AB/gAD/AH/AAf4AA/gA/wAH+AAP4AP4AA/gAD+AD/AAP4AA/gA/wAH+AAP8Af8AB/gAD/AH/AAf4AA/4D/4AP+AAP/B//AH/AAB////4D/wAAf//////8AAD//////+AAAf//////AAAH//////wAAA//8///4AAAD/+D//8AAAAP+Af/8AAAAAAAB/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAAAAAB//AAAAAAAB//8AAAAAAB///gAAgAAA///8AAcAAAf///gAPAAAH///8AH4AAD////AD/AAB/+H/4B/wAAf+Af+Af8AAP+AB/wD/gAD/gAf8Af4AA/wAD/AH+AAP8AA/wB/gAD+AAH8AP4AA/gAB/AD+AAP4AAfwB/gAD+AAH8Af4AA/wAD/AH+AAP8AA/gD/gAD/gAf4A/wAAf8AP8A/8AAH/gH/Af/AAA///////gAAP//////wAAB//////8AAAP/////+AAAB//////AAAAP/////AAAAA/////gAAAAD////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='),
46,
atob("ExspGyUkJiQnISYnFQ=="),
62+(scale<<8)+(1<<16)
);
return this;
};
Graphics.prototype.setMediumFont = function(scale) {
// Actual height 41 (42 - 2)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16));
return this;
};
Graphics.prototype.setSmallFont = function(scale) {
// Actual height 28 (27 - 0)
this.setFontCustom(
@ -71,6 +74,7 @@ Graphics.prototype.setSmallFont = function(scale) {
return this;
};
function imgLock(){
return {
width : 16, height : 16, bpp : 1,
@ -95,6 +99,14 @@ function imgBattery(){
}
}
function imgCharging() {
return {
width : 24, height : 24, bpp : 1,
transparent : 1,
buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
}
}
function imgBpm() {
return {
width : 24, height : 24, bpp : 1,
@ -111,6 +123,14 @@ function imgTemperature() {
}
}
function imgWeather(){
return {
width : 24, height : 24, bpp : 1,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AAcYAQ0MgEwAQUAngLB/8AgP/wACCgf/4Fz//OAQQICCIoaCEAQpGHA4ACA="))
}
}
function imgWind () {
return {
width : 24, height : 24, bpp : 1,
@ -127,14 +147,6 @@ function imgTimer() {
}
}
function imgCharging() {
return {
width : 24, height : 24, bpp : 1,
transparent : 1,
buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
}
}
function imgWatch() {
return {
width : 24, height : 24, bpp : 1,
@ -143,56 +155,93 @@ function imgWatch() {
}
}
function imgHomeAssistant() {
return {
width : 48, height : 48, bpp : 1,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AD8BwAFDg/gAocP+AFDj4FEn/8Aod//wFD/1+FAf4j+8AoMD+EPDAUH+OPAoUP+fPAoUfBYk/C4l/EYIwC//8n//FwIFEgYFD4EH+E8nkP8BdBAonjjk44/wj/nzk58/4gAFDF4PgCIMHAoPwhkwh4FB/EEkEfIIWAHwIFC4A+BAoXgg4FDL4IFDL4IFDLIYFkAEQA=="))
}
}
/*
* INFO ENTRIES
* List of [Data, Icon, left/right, Function to execute]
/************
* 2D MENU with entries of:
* [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]]
*
*/
var infoArray = [
function(){ return [ null, null, "left", null ] },
function(){ return [ "Bangle", imgWatch(), "right", null ] },
function(){ return [ E.getBattery() + "%", imgBattery(), "left", null ] },
function(){ return [ getSteps(), imgSteps(), "left", null ] },
function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm(), "left", null] },
function(){ return [ getWeather().temp, imgTemperature(), "left", null ] },
function(){ return [ getWeather().wind, imgWind(), "left", null ] },
];
var menu = [
[
function(){ return [ null, null ] },
],
[
function(){ return [ "Bangle", imgWatch() ] },
function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] },
function(){ return [ getSteps(), imgSteps() ] },
function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] },
]
]
/*
* We append the HomeAssistant integrations if HomeAssistant is available
* Timer Menu
*/
try{
require('sched');
menu.push([
function(){
var text = isAlarmEnabled() ? getAlarmMinutes() + " min." : "Timer";
return [text, imgTimer(), () => decreaseAlarm(), () => increaseAlarm(), null ]
},
]);
} catch(ex) {
// If sched is not installed, we hide this menu item
}
/*
* WEATHER MENU
*/
if(storage.readJSON('weather.json') !== undefined){
menu.push([
function(){ return [ "Weather", imgWeather() ] },
function(){ return [ getWeather().temp, imgTemperature() ] },
function(){ return [ getWeather().wind, imgWind() ] },
]);
}
/*
* HOME ASSISTANT MENU
*/
try{
var triggers = require("ha.lib.js").getTriggers();
var haMenu = [
function(){ return [ "Home", imgHomeAssistant() ] },
];
triggers.forEach(trigger => {
infoArray.push(function(){
return [trigger.display, trigger.getIcon(), "left", function(){
haMenu.push(function(){
return [trigger.display, trigger.getIcon(), () => {}, () => {}, function(){
var ha = require("ha.lib.js");
ha.sendTrigger("TRIGGER_BW");
ha.sendTrigger(trigger.trigger);
}]
});
})
menu.push(haMenu);
} catch(ex){
// Nothing to do if HomeAssistant is not available...
}
const NUM_INFO=infoArray.length;
function getInfoEntry(){
if(isAlarmEnabled()){
return [getAlarmMinutes() + " min.", imgTimer(), "left", null]
} else if(Bangle.isCharging()){
return [E.getBattery() + "%", imgCharging(), "left", null]
} else{
// In case the user removes HomeAssistant entries, showInfo
// could be larger than infoArray.length...
settings.showInfo = settings.showInfo % infoArray.length;
return infoArray[settings.showInfo]();
}
// If HomeAssistant is not installed, we hide this item
}
/*
function getMenuEntry(){
// In case the user removes HomeAssistant entries, showInfo
// could be larger than infoArray.length...
settings.menuPosX = settings.menuPosX % menu.length;
settings.menuPosY = settings.menuPosY % menu[settings.menuPosX].length;
return menu[settings.menuPosX][settings.menuPosY]();
}
/************
* Helper
*/
function getSteps() {
@ -229,7 +278,7 @@ function getWeather(){
// Wind
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
weather.wind = Math.round(wind[1]) + " km/h";
weather.wind = Math.round(wind[1]) + "kph";
return weather
@ -238,15 +287,16 @@ function getWeather(){
}
return {
temp: "? °C",
hum: "-",
txt: "-",
wind: "? km/h",
wdir: "-",
wrose: "-"
temp: " ? ",
hum: " ? ",
txt: " ? ",
wind: " ? ",
wdir: " ? ",
wrose: " ? "
};
}
function isAlarmEnabled(){
try{
var alarm = require('sched');
@ -261,6 +311,7 @@ function isAlarmEnabled(){
return false;
}
function getAlarmMinutes(){
if(!isAlarmEnabled()){
return -1;
@ -271,6 +322,7 @@ function getAlarmMinutes(){
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
function increaseAlarm(){
try{
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
@ -282,6 +334,7 @@ function increaseAlarm(){
} catch(ex){ }
}
function decreaseAlarm(){
try{
var minutes = getAlarmMinutes();
@ -301,10 +354,9 @@ function decreaseAlarm(){
}
/*
* DRAW functions
/************
* DRAW
*/
function draw() {
// Queue draw again
queueDraw();
@ -323,8 +375,8 @@ function drawDate(){
g.reset().clearRect(0,0,W,W);
// Draw date
y = parseInt(y/2);
y += settings.fullscreen ? 2 : 15;
y = parseInt(y/2)+4;
y += settings.fullscreen ? 0 : 13;
var date = new Date();
var dateStr = date.getDate();
dateStr = ("0" + dateStr).substr(-2);
@ -338,13 +390,12 @@ function drawDate(){
var fullDateW = dateW + 10 + dayW;
g.setFontAlign(-1,0);
g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
g.setMediumFont();
g.setColor(g.theme.fg);
g.drawString(dateStr, W/2 - fullDateW / 2, y+1);
g.setSmallFont();
g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
}
@ -368,13 +419,13 @@ function drawTime(){
// Set y coordinates correctly
y += parseInt((H - y)/2) + 5;
var infoEntry = getInfoEntry();
var infoStr = infoEntry[0];
var infoImg = infoEntry[1];
var printImgLeft = infoEntry[2] == "left";
var menuEntry = getMenuEntry();
var menuName = menuEntry[0];
var menuImg = menuEntry[1];
var printImgLeft = settings.menuPosY != 0;
// Show large or small time depending on info entry
if(infoStr == null){
if(menuName == null){
if(settings.hideColon){
g.setXLargeFont();
} else {
@ -386,8 +437,8 @@ function drawTime(){
}
g.drawString(timeStr, W/2, y);
// Draw info if set
if(infoStr == null){
// Draw menu if set
if(menuName == null){
return;
}
@ -395,18 +446,18 @@ function drawTime(){
g.setFontAlign(0,0);
g.setSmallFont();
var imgWidth = 0;
if(infoImg !== undefined){
imgWidth = 26.0;
var strWidth = g.stringWidth(infoStr);
var scale = imgWidth / infoImg.width;
if(menuImg !== undefined){
imgWidth = 24.0;
var strWidth = g.stringWidth(menuName);
var scale = imgWidth / menuImg.width;
g.drawImage(
infoImg,
menuImg,
W/2 + (printImgLeft ? -strWidth/2-2 : strWidth/2+2) - parseInt(imgWidth/2),
y - parseInt(imgWidth/2),
{ scale: scale }
);
}
g.drawString(infoStr, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3);
g.drawString(menuName, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3);
}
@ -463,53 +514,78 @@ Bangle.on('lock', function(isLocked) {
Bangle.on('charging',function(charging) {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
// Jump to battery
settings.menuPosX = 1;
settings.menuPosY = 1;
draw();
});
Bangle.on('touch', function(btn, e){
var left = parseInt(g.getWidth() * 0.3);
var widget_size = settings.fullscreen ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better...
var left = parseInt(g.getWidth() * 0.22);
var right = g.getWidth() - left;
var upper = parseInt(g.getHeight() * 0.3);
var upper = parseInt(g.getHeight() * 0.22) + widget_size;
var lower = g.getHeight() - upper;
var is_left = e.x < left;
var is_right = e.x > right;
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(is_upper){
Bangle.buzz(40, 0.6);
increaseAlarm();
drawTime();
}
if(is_lower){
Bangle.buzz(40, 0.6);
decreaseAlarm();
settings.menuPosY = (settings.menuPosY+1) % menu[settings.menuPosX].length;
// Handle custom menu entry function
var menuEntry = getMenuEntry();
if(menuEntry.length > 2){
menuEntry[2]();
}
drawTime();
}
if(is_upper){
if(e.y < widget_size){
return;
}
Bangle.buzz(40, 0.6);
settings.menuPosY = settings.menuPosY-1;
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].length-1 : settings.menuPosY;
// Handle custom menu entry function
var menuEntry = getMenuEntry();
if(menuEntry.length > 3){
menuEntry[3]();
}
drawTime();
}
if(is_right){
Bangle.buzz(40, 0.6);
settings.showInfo = (settings.showInfo+1) % NUM_INFO;
settings.menuPosX = (settings.menuPosX+1) % menu.length;
settings.menuPosY = 0;
drawTime();
}
if(is_left){
Bangle.buzz(40, 0.6);
settings.showInfo = settings.showInfo-1;
settings.showInfo = settings.showInfo < 0 ? NUM_INFO-1 : settings.showInfo;
settings.menuPosY = 0;
settings.menuPosX = settings.menuPosX-1;
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
drawTime();
}
if(is_center){
var infoEntry = getInfoEntry();
var fun = infoEntry[3];
if(fun != null){
var menuEntry = getMenuEntry();
if(menuEntry.length > 4){
Bangle.buzz(80, 0.6).then(()=>{
try{
fun();
menuEntry[4]();
setTimeout(()=>{
Bangle.buzz(80, 0.6);
}, 250);

View File

@ -1,8 +1,8 @@
{
"id": "bwclk",
"name": "BW Clock",
"version": "0.11",
"description": "BW Clock.",
"version": "0.13",
"description": "A very minimalistic clock with date and time in focus.",
"readme": "README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,2 @@
...
0.03: First update with ChangeLog Added

View File

@ -0,0 +1,2 @@
...
0.02: First update with ChangeLog Added

View File

@ -1,3 +1,4 @@
0.01: Core functionnality
0.02: Offer to enable HID if disabled
0.03: Adds Readme and tags to be used by App Loader
0.04: Adds Bangle.js 2 support, Buzz and Touch

View File

@ -7,7 +7,7 @@ Control the camera shutter from your phone using your watch
1. In settings, enable HID for "Keyboard & Media".
2. Pair your watch to your phone.
3. Load your camera app on your phone.
4. There you go, launch the app on your watch and press button 2 to trigger the shutter !
4. There you go, launch the app on your watch and press the button (button 2 on Bangle.js 1) to trigger the shutter !
## How does it work ?

View File

@ -1,25 +1,29 @@
var storage = require('Storage');
const settings = storage.readJSON('setting.json',1) || { HID: false };
const isB2 = process.env.HWVERSION === 2;
var sendHid, camShot, profile;
if (settings.HID=="kbmedia") {
profile = 'camShutter';
sendHid = function (code, cb) {
try {
NRF.sendHIDReport([1,code], () => {
NRF.sendHIDReport([1,0], () => {
if (cb) cb();
});
});
} catch(e) {
print(e);
}
try {
NRF.sendHIDReport([1,code], () => {
NRF.sendHIDReport([1,0], () => {
if (cb) cb();
});
});
} catch(e) {
print(e);
}
};
camShot = function (cb) { sendHid(0x80, cb); };
} else {
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
if (enable) {
settings.HID = "kbmedia";
require("Storage").write('setting.json', settings);
@ -31,10 +35,15 @@ function drawApp() {
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
g.fillCircle(122,127,60);
g.drawImage(storage.read("hidcam.img"),100,105);
const d = g.getWidth() - 18;
if (!isB2) { // Bangle.js 1
g.fillCircle(122,127,60);
g.drawImage(storage.read("hidcam.img"),100,105);
const d = g.getWidth() - 18;
} else {
g.fillCircle(90,95,60);
g.drawImage(storage.read("hidcam.img"),65,70);
const d = g.getWidth() - 18;
}
function c(a) {
return {
width: 8,
@ -46,12 +55,27 @@ function drawApp() {
g.fillRect(180,130, 240, 124);
}
if (camShot) {
setWatch(function(e) {
E.showMessage('camShot !');
setTimeout(drawApp, 1000);
camShot(() => {});
}, BTN2, { edge:"falling",repeat:true,debounce:50});
if (camShot) {
if (!isB2) { // Bangle.js 1
setWatch(function(e) {
E.showMessage('camShot !');
Bangle.buzz(300, 1);
setTimeout(drawApp, 1000);
camShot(() => {});
}, BTN2, { edge:"falling",repeat:true,debounce:50});
} else { // Bangle.js 2
setWatch(function(e) {
E.showMessage('camShot !');
Bangle.buzz(300, 1);
setTimeout(drawApp, 1000);
camShot(() => {});
}, BTN, { edge:"falling",repeat:true,debounce:50});
Bangle.on('touch', function (wat, tap) {
E.showMessage('camShot !');
Bangle.buzz(300, 1);
setTimeout(drawApp, 1000);
camShot(() => {});
});
}
drawApp();
}
}

View File

@ -2,11 +2,11 @@
"id": "hidcam",
"name": "Camera shutter",
"shortName": "Cam shutter",
"version": "0.03",
"version": "0.04",
"description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle",
"icon": "app.png",
"tags": "bluetooth,tool",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"hidcam.app.js","url":"app.js"},

2
apps/homework/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
...
0.10: First update with ChangeLog Added

View File

@ -2,7 +2,7 @@
{ "id": "homework",
"name": "Homework",
"shortName":"Homework",
"version":"0.1",
"version":"0.10",
"description": "A simple app to manage homework",
"icon": "app.png",
"tags": "tool",

View File

@ -1,3 +1,5 @@
0.01: New App!
0.02: Show status info on display
Allow recording to Bangle
0.03: Allow downloading recorded files
Make it work with more BTHRM configs

View File

@ -6,7 +6,7 @@ This app can use [BTHRM](https://banglejs.com/apps/#bthrm) as a reference.
## Steps for usage
* (Optional) Install [BTHRM](https://banglejs.com/apps/#bthrm) as reference (use ECG based sensor for best accuracy).
* Configure BTHRM to "Both"-Mode. This prevents data beeing lost because BTHRM can replace the HRM-events data with BTHRM data.
* Configure BTHRM to "Both"-Mode or use a version >= 0.12. This prevents data beeing lost because BTHRM can replace the HRM-events data with BTHRM data.
* Click "Start" in browser.
* Wait until the "Events" number starts to grow, that means there are events recorded.
* Record for some time, since BTHRM and HRM often need some seconds to start getting useful values. Consider 2000 events a useful minimum.

View File

@ -1,18 +1,42 @@
<html>
<head>
<title>Bangle.js Accelerometer streaming</title>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<script src="https://www.puck-js.com/puck.js"></script>
<p></div><input type="checkbox" id="chkLocal">Store on Bangle (file named log.csv, download with IDE)</input></p>
<p>
<button id="btnConnect">Start</button>
<button id="btnStop">Stop</button>
<button id="btnReset">Reset</button>
<button id="btnSave">Save CSV</button>
<div class="form-group">
<label class="form-switch">
<input type="checkbox" id="chkLocal">
<i class="form-icon"></i> Store on bangle (file named log.csv)
</label>
</div>
</p>
<p>
<button id="btnConnect" class="btn btn-primary">Start</button>
<button id="btnStop" class="btn btn-secondary">Stop</button>
<button id="btnReset" class="btn btn-secondary">Reset</button>
<button id="btnSave" class="btn btn-primary">Save CSV</button>
<button id="btnDownload" class="btn btn-primary">Download CSV</button>
</p>
<p id="result"></p>
<script>
function saveCSV(filename, csvData) {
let a = document.createElement("a"),
file = new Blob([csvData], {type: "Comma-separated value file"});
let url = URL.createObjectURL(file);
a.href = url;
a.download = filename+".csv";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
function createCode(){
//modes: 1 BT, 2 File
return "var method=" + (document.getElementById("chkLocal").checked ? 2 : 1) + ";\n" + String.raw`
@ -32,7 +56,12 @@ function createCode(){
return gotBTHRM && gotHRM && gotHRMraw && gotAcc;
}
let bthrmSettings = (require("Storage").readJSON("bthrm.json",1) || {});
Bangle.setHRMPower(1);
if (bthrmSettings.replace) Bangle.origSetHRMPower(1);
if (Bangle.setBTHRMPower){
Bangle.setBTHRMPower(1);
} else {
@ -99,7 +128,11 @@ function createCode(){
Bangle.on("accel", writeAccDirect);
}
Bangle.on("HRM-raw", writeHRMraw);
Bangle.on("HRM", writeHRM);
if (bthrmSettings.replace){
Bangle.origOn("HRM", writeHRM);
} else {
Bangle.on("HRM", writeHRM);
}
Bangle.on("BTHRM", writeBTHRM);
g.clear();
@ -164,7 +197,6 @@ function createCode(){
var connection;
var lineCount=-1;
function stop (){
connection.reconnect((c)=>{
c.write("load();\n");
@ -173,32 +205,74 @@ function stop (){
});
}
document.getElementById("chkLocal").addEventListener("click", function() {
function updateButtons(){
document.getElementById("btnSave").disabled = document.getElementById("chkLocal").checked;
document.getElementById("btnDownload").disabled = !document.getElementById("chkLocal").checked;
document.getElementById("btnReset").disabled = document.getElementById("chkLocal").checked;
document.getElementById("btnStop").disabled = document.getElementById("chkLocal").checked;
}
updateButtons();
document.getElementById("chkLocal").addEventListener("click", function() {
reset();
updateButtons();
});
window.addEventListener("message", function(event) {
let msg = event.data;
if (msg.type=="readstoragefilersp") {
saveCSV("log.csv", msg.data);
}
}, false);
document.getElementById("btnDownload").addEventListener("click", function() {
if (connection) {
stop();
}
console.log("Loading data from BangleJs...");
try {
window.postMessage({type:"readstoragefile",data:"log.csv",id:0});
} catch(ex) {
console.log("(Warning) Could not load apikey from BangleJs.");
console.log(ex);
}
});
document.getElementById("btnSave").addEventListener("click", function() {
var h = document.createElement('a');
h.href = 'data:text/csv;charset=utf-8,' + encodeURI(localStorage.getItem("data"));
h.target = '_blank';
h.download = "DATA.csv";
h.click();
saveCSV("log.csv", localStorage.getItem("data"));
});
function reset(){
document.getElementById("result").innerText="";
}
document.getElementById("btnReset").addEventListener("click", function() {
if (connection) {
stop();
}
document.getElementById("result").innerText="";
lineCount=-1;
localStorage.removeItem("data");
reset();
});
document.getElementById("btnStop").addEventListener("click", function() {
if (connection) {
stop();
}
});
function connect(connectionHandler){
Puck.connect(function(c) {
if (!c) {
console.log("Couldn't connect!\n");
return;
}
connection = c;
connectionHandler(c);
});
}
document.getElementById("btnConnect").addEventListener("click", function() {
localStorage.setItem("data", "");
lineCount=-1;
@ -206,12 +280,7 @@ document.getElementById("btnConnect").addEventListener("click", function() {
stop();
document.getElementById("result").innerText="0";
}
Puck.connect(function(c) {
if (!c) {
console.log("Couldn't connect!\n");
return;
}
connection = c;
connect(function(connection) {
var buf = "";
connection.on("data", function(d) {
buf += d;
@ -220,7 +289,7 @@ document.getElementById("btnConnect").addEventListener("click", function() {
l.forEach(onLine);
});
connection.write("reset();\n", function() {
setTimeout(function() {
setTimeout(function() {
connection.write("\x03\x10if(1){"+createCode()+"}\n",
function() { console.log("Ready..."); });
}, 1500);

View File

@ -2,14 +2,14 @@
"id": "hrmaccevents",
"name": "HRM Accelerometer event recorder",
"shortName": "HRM ACC recorder",
"version": "0.02",
"version": "0.03",
"type": "RAM",
"description": "Record HRM and accelerometer events in high resolution to CSV files in your browser",
"icon": "app.png",
"tags": "debug",
"supports": ["BANGLEJS","BANGLEJS2"],
"custom": "custom.html",
"customConnect": false,
"customConnect": true,
"readme": "README.md",
"storage": [ ]
}

View File

@ -6,3 +6,4 @@
0.20: Add theme support
0.21: Add Settings
0.22: Use default Bangle formatter for booleans
0.23: Added note to configure position in "my location" if not done yet. Small fixes.

View File

@ -8,6 +8,8 @@ If watch is locked, seconds get refreshed every 10 seconds.
## Usage
Location for sun set / rise set with mylocation app.
Provide names and the UTC offsets for up to three other timezones in the app store. These are stored in a json file on your watch. UTC offsets can be decimal (e.g., 5.5 for India).
The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the app store and update. Currently the clock only supports 24 hour time format for the additional time zones.
@ -21,11 +23,5 @@ Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if y
Created by Hank.
Based on the great work of
=================
World Clock - 4 time zones
Made by [Scott Hale](https://www.github.com/computermacgyver), based upon the [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock).
===== a n d =====
Sun Clock
[Sun Clock](https://github.com/espruino/BangleApps/tree/master/apps/sunclock)
=================
Based on the great work of "World Clock - 4 time zones". Made by [Scott Hale](https://www.github.com/computermacgyver), based upon the [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock).
And Sun Clock [Sun Clock](https://github.com/espruino/BangleApps/tree/master/apps/sunclock)

View File

@ -46,10 +46,10 @@ setting = require("Storage").readJSON("setting.json",1);
E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ
SunCalc = require("hsuncalc.js");
const LOCATION_FILE = "mylocation.json";
var rise = "07:00";
var set = "20:00";
var pos = {altitude: 20, azimuth: 135};
var noonpos = {altitude: 37, azimuth: 180};
var rise = "read";
var set = "...";
//var pos = {altitude: 20, azimuth: 135};
//var noonpos = {altitude: 37, azimuth: 180};
//=======Sun
var ampm = "AM";
@ -113,19 +113,19 @@ g.setBgColor(g.theme.bg);
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
// schedule a draw for the next second
function queueDrawSeconds() {
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
drawTimeoutSeconds = setTimeout(function() {
drawTimeoutSeconds = undefined;
drawSeconds();
//console.log("TO: " + secondsTimeout);
}, secondsTimeout - (Date.now() % secondsTimeout));
drawTimeoutSeconds = undefined;
drawSeconds();
//console.log("TO: " + secondsTimeout);
}, secondsTimeout - (Date.now() % secondsTimeout));
}
function doublenum(x) {
@ -137,12 +137,17 @@ function getCurrentTimeFromOffset(dt, offset) {
}
function updatePos() {
coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":53.3,"lon":10.1,"location":"Pattensen"};
pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon);
coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":0,"lon":0,"location":"-"}; //{"lat":53.3,"lon":10.1,"location":"Pattensen"};
if (coord.lat != 0 && coord.lon != 0) {
//pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon);
times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon);
rise = times.sunrise.toString().split(" ")[4].substr(0,5);
set = times.sunset.toString().split(" ")[4].substr(0,5);
noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon);
rise = "^" + times.sunrise.toString().split(" ")[4].substr(0,5);
set = "v" + times.sunset.toString().split(" ")[4].substr(0,5);
//noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon);
} else {
rise = null;
set = null;
}
}
@ -152,11 +157,7 @@ function drawSeconds() {
var da = d.toString().split(" ");
// default draw styles
g.reset();
g.setBgColor(g.theme.bg);
// drawSting centered
g.setFontAlign(0, 0);
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
// draw time
var time = da[4].split(":");
@ -187,11 +188,7 @@ function draw() {
var da = d.toString().split(" ");
// default draw styles
g.reset();
g.setBgColor(g.theme.bg);
// drawSting centered
g.setFontAlign(0, 0);
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
// draw time
var time = da[4].split(":");
@ -255,35 +252,31 @@ function draw() {
var date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
// For a single secondary timezone, draw it bigger and drop time zone to second line
const xOffset = 30;
g.setFont(font, secondaryTimeFontSize);
g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
g.setFont(font, secondaryTimeZoneFontSize);
g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true);
g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
g.setFont(font, secondaryTimeZoneFontSize).drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true);
// draw Day, name of month, Date
g.setFont(font, secondaryTimeZoneFontSize);
g.drawString(date, xyCenter, yposDate, true);
g.setFont(font, secondaryTimeZoneFontSize).drawString(date, xyCenter, yposDate, true);
} else if (index < 3) {
// For > 1 extra timezones, render as columns / rows
g.setFont(font, secondaryRowColFontSize);
g.setFontAlign(-1, 0);
g.setFont(font, secondaryRowColFontSize).setFontAlign(-1, 0);
g.drawString(
offset[OFFSET_TIME_ZONE],
xcol1,
yposWorld + index * 15,
true
);
g.setFontAlign(1, 0);
g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true);
g.setFontAlign(1, 0).drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true);
}
});
if (showSunInfo) {
g.setFontAlign(-1, 0);
g.setFont("Vector",12);
g.drawString(`^${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw riseset
g.setFontAlign(1, 0);
g.drawString(`v${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw riseset
if (rise != null){
g.setFontAlign(-1, 0).setFont("Vector",12).drawString(`${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw rise
g.setFontAlign(1, 0).drawString(`${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw set
} else {
g.setFontAlign(-1, 0).setFont("Vector",11).drawString("set city in \'my location\' app!", 10, 3 + yposWorld + 3 * 15, true);
}
}
//debug settings
//g.setFontAlign(1, 0);
@ -291,7 +284,6 @@ function draw() {
//g.drawString(showSunInfo, xcol2, 3 + yposWorld + 3 * 15, true);
//g.drawString(colorWhenDark, xcol2, 3 + yposWorld + 3 * 15, true);
queueDraw();
if (secondsMode != "none") queueDrawSeconds();
@ -317,6 +309,7 @@ if (!Bangle.isLocked()) { // Initial state
if (showSunInfo) {
if (PosInterval != 0) clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
updatePos();
}
secondsTimeout = 1000;
@ -326,9 +319,8 @@ if (!Bangle.isLocked()) { // Initial state
}
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
draw(); // draw immediately, queue redraw
if (showSunInfo) updatePos();
}else{
if (secondsMode == "always") secondsTimeout = 1000;
if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000;
@ -343,20 +335,19 @@ if (!Bangle.isLocked()) { // Initial state
if (showSunInfo) {
if (PosInterval != 0) clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
updatePos();
}
draw(); // draw immediately, queue redraw
if (showSunInfo) updatePos();
}
Bangle.on('lock',on=>{
if (!on) { // UNlocked
if (showSunInfo) {
if (PosInterval != 0) clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
updatePos();
}
secondsTimeout = 1000;
@ -368,7 +359,6 @@ Bangle.on('lock',on=>{
drawTimeout = undefined;
draw(); // draw immediately, queue redraw
if (showSunInfo) updatePos();
}else{ // locked
if (secondsMode == "always") secondsTimeout = 1000;
@ -381,9 +371,11 @@ Bangle.on('lock',on=>{
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
if (PosInterval != 0) clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
if (showSunInfo) {
if (PosInterval != 0) clearInterval(PosInterval);
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
updatePos();
}
draw(); // draw immediately, queue redraw
if (showSunInfo) updatePos();
}
});

View File

@ -30,8 +30,8 @@
}
} catch(e){
offsets=[
[true,"London",0],
[true,"NY",-5],
[true,"London",1],
[true,"NY",-4],
[true, "Denver",-6],
];

View File

@ -2,7 +2,7 @@
"id": "hworldclock",
"name": "Hanks World Clock",
"shortName": "Hanks World Clock",
"version": "0.22",
"version": "0.23",
"description": "Current time zone plus up to three others",
"allow_emulator":true,
"icon": "app.png",

View File

@ -1,3 +1,4 @@
0.01: New keyboard
0.02: Introduce setting "Show help button?". Make setting firstLaunch invisible by removing corresponding code from settings.js. Add marker that shows when character selection timeout has run out. Display opened text on launch when editing existing text string. Perfect horizontal alignment of buttons. Tweak help message letter casing.
0.03: Use default Bangle formatter for booleans
0.04: Allow moving the cursor

View File

@ -2,7 +2,7 @@
A library that provides the ability to input text in a style familiar to anyone who had a mobile phone before they went all touchscreen.
Swipe right for Space, left for Backspace, and up/down for Caps lock. Tap the '?' button in the app if you need a reminder!
Swipe right for Space, left for Backspace, down for cursor moving mode, and up for Caps lock. Swipe left and right to move the cursor in moving mode. Tap the '?' button in the app if you need a reminder!
At time of writing, only the [Noteify app](http://microco.sm/out/Ffe9i) uses a keyboard.

View File

@ -17,18 +17,50 @@ exports.input = function(options) {
"4":"GHI4","5":"JKL5","6":"MNO6",
"7":"PQRS7","8":"TUV80","9":"WXYZ9",
};
var helpMessage = 'Swipe:\nRight: Space\nLeft:Backspace\nUp/Down: Caps lock\n';
var helpMessage = 'Swipe:\nRight: Space\nLeft:Backspace\nUp: Caps lock\nDown:Move mode';
var charTimeout; // timeout after a key is pressed
var charCurrent; // current character (index in letters)
var charIndex; // index in letters[charCurrent]
var textIndex = text.length;
var textWidth = settings.showHelpBtn ? 10 : 14;
var caps = true;
var layout;
var btnWidth = g.getWidth()/3
var btnWidth = g.getWidth()/3;
function getMoveChar(){
return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00@\x1F\xE1\x00\x10\x00\x10\x01\x0F\xF0\x04\x01\x00";
}
function getMoreChar(){
return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xDB\x1B`\x00\x00\x00";
}
function getCursorChar(){
return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xAA\xAA\x80"; }
function displayText(hideMarker) {
layout.clear(layout.text);
layout.text.label = text.slice(settings.showHelpBtn ? -11 : -13) + (hideMarker ? " " : "_");
let charsBeforeCursor = textIndex;
let charsAfterCursor = Math.min(text.length - textIndex, (textWidth)/2);
let start = textIndex - Math.ceil(textWidth - charsAfterCursor);
let startMore = false;
if (start > 0) {start++; startMore = true}
if (start < 0) start = 0;
let cursor = textIndex + 1;
let end = cursor + Math.floor(start + textWidth - cursor);
if (end <= text.length) {end--; if (startMore) end--;}
if (end > text.length) end = text.length;
let pre = (start > 0 ? getMoreChar() : "") + text.slice(start, cursor);
let post = text.slice(cursor, end) + (end < text.length - 1 ? getMoreChar() : "");
layout.text.label = pre + (hideMarker ? " " : (moveMode? getMoveChar():getCursorChar())) + post;
layout.render(layout.text);
}
@ -41,8 +73,11 @@ exports.input = function(options) {
function backspace() {
deactivateTimeout(charTimeout);
text = text.slice(0, -1);
newCharacter();
if (textIndex > -1){
text = text.slice(0, textIndex) + text.slice(textIndex + 1);
if (textIndex > -1) textIndex --;
newCharacter();
}
}
function setCaps() {
@ -55,6 +90,7 @@ exports.input = function(options) {
function newCharacter(ch) {
displayText();
if (ch && textIndex < text.length) textIndex ++;
charCurrent = ch;
charIndex = 0;
}
@ -69,7 +105,11 @@ exports.input = function(options) {
newCharacter(key);
}
var newLetter = letters[charCurrent][charIndex];
text += (caps ? newLetter.toUpperCase() : newLetter.toLowerCase());
let pre = text.slice(0, textIndex);
let post = text.slice(textIndex, text.length);
text = pre + (caps ? newLetter.toUpperCase() : newLetter.toLowerCase()) + post;
// set a timeout
charTimeout = setTimeout(function() {
charTimeout = undefined;
@ -78,14 +118,29 @@ exports.input = function(options) {
displayText(charTimeout);
}
var moveMode = false;
function onSwipe(dirLeftRight, dirUpDown) {
if (dirUpDown) {
if (dirUpDown == -1) {
setCaps();
} else if (dirUpDown == 1) {
moveMode = !moveMode;
displayText(false);
} else if (dirLeftRight == 1) {
text += ' ';
newCharacter();
if (!moveMode){
text = text.slice(0, textIndex + 1) + " " + text.slice(++textIndex);
newCharacter();
} else {
if (textIndex < text.length) textIndex++;
displayText(false);
}
} else if (dirLeftRight == -1) {
backspace();
if (!moveMode){
backspace();
} else {
if (textIndex > -1) textIndex--;
displayText(false);
}
}
}

View File

@ -1,6 +1,6 @@
{ "id": "kbmulti",
"name": "Multitap keyboard",
"version":"0.03",
"version":"0.04",
"description": "A library for text input via multitap/T9 style keypad",
"icon": "app.png",
"type":"textinput",

View File

@ -97,6 +97,25 @@ var locales = {
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
// No translation for english...
},
"en_IE": {
lang: "en_IE",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "€",
int_curr_symbol: "EUR",
currency_first: true,
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" }, // 28 Feb 2020" // "28/03/2020"(short)
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
// No translation for english...
},
"en_NAV": { // navigation units nautical miles and knots
lang: "en_NAV",
decimal_point: ".",

View File

@ -54,5 +54,5 @@
0.39: Set default color for message icons according to theme
0.40: Use default Bangle formatter for booleans
0.41: Add notification icons in the widget
0.42: Separate buzz pattern for incoming calls
Fix messages ignoring "Vibrate: Off" setting
0.42: Fix messages ignoring "Vibrate: Off" setting
0.43: Separate buzz pattern for incoming calls

View File

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

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Remove one line of code that didn't do anything other than in some instances hinder the function of the app.

View File

@ -6,9 +6,9 @@ Making the music controls accessible this way lets one start a music stream on t
It is suggested to use Messages Music along side the app Quick Launch.
Messages Music v0.01 has been verified to work with Messages v0.31 on Bangle.js 2 fw2v13.
Messages Music v0.02 has been verified to work with Messages v0.41 on Bangle.js 2 fw2v14.
Music Messages should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much.
Messages Music should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much.
Messages app is created by Gordon Williams with contributions from [Jeroen Peters](https://github.com/jeroenpeters1986).

View File

@ -1,7 +1,6 @@
let showMusic = () => {
Bangle.CLOCK = 1; // To pass condition in messages library
require('messages').pushMessage({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true});
Bangle.CLOCK = undefined;
};
var settings = require('Storage').readJSON('messages.settings.json', true) || {}; //read settings if they exist else set to empty dict

View File

@ -1,7 +1,7 @@
{
"id": "messagesmusic",
"name":"Messages Music",
"version":"0.01",
"version":"0.02",
"description": "Uses Messages library to push a music message which in turn displays Messages app music controls",
"icon":"app.png",
"type": "app",

View File

@ -0,0 +1,2 @@
...
0.03: First update with ChangeLog Added

2
apps/novaclock/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
...
0.10: First update with ChangeLog Added

View File

@ -3,7 +3,7 @@
"shortName":"Nova Clock",
"icon": "app.png",
"type": "clock",
"version":"0.1",
"version":"0.10",
"description": "A clock inspired by the Kirby series",
"tags": "clock",
"supports": ["BANGLEJS2"],

View File

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

13
apps/owmweather/README.md Normal file
View File

@ -0,0 +1,13 @@
# OpenWeatherMap weather provider
This updates [Weather](https://banglejs.com/apps/#weather) with data from the OpenWeatherMap API
## Usage
Just install and configure the app. This needs an internet-enabled Gadgetbridge version.
Install [My Location](https://banglejs.com/apps/#mylocation) to change the location for the weather requests.
Install one of the text input libraries to set the API key in the app settings or use the web interface.
## Creator
[halemmerich](https://github.com/halemmerich)

BIN
apps/owmweather/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

28
apps/owmweather/boot.js Normal file
View File

@ -0,0 +1,28 @@
(function() {
let waiting = false;
let settings = require("Storage").readJSON("owmweather.json", 1) || {
enabled: false
};
function completion(){
waiting = false;
}
if (settings.enabled) {
let weather = require("Storage").readJSON('weather.json') || {};
let lastUpdate;
if (weather && weather.weather && weather.weather.time) lastUpdate = weather.weather.time;
if (!lastUpdate || lastUpdate + settings.refresh * 1000 * 60 < Date.now()){
if (!waiting){
waiting = true;
require("owmweather").pull(completion);
}
}
setInterval(() => {
if (!waiting && NRF.getSecurityStatus().connected){
waiting = true;
require("owmweather").pull(completion);
}
}, settings.refresh * 1000 * 60);
}
})();

View File

@ -0,0 +1 @@
{"enabled":false,"refresh":180}

View File

@ -0,0 +1,63 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<h3>Set OpenWeatherMap (OWM) API key</h3>
<p><input id="apikey" onkeyup="checkInput()" style="width:90%; margin: 3px"></input><button id="upload" class="btn btn-primary">Save key</button></p>
<h4>Where to get your personal API key?</h4>
<p>Go to <a href="https://home.openweathermap.org/users/sign_up">https://home.openweathermap.org/users/sign_up</a> and sign up for a free account.<br>
After registration you can login and optain your personal API key.</p>
<script src="../../core/lib/interface.js"></script>
<script>
function checkInput() {
if(document.getElementById("apikey").value==="") {
document.getElementById('upload').disabled = true;
} else {
document.getElementById('upload').disabled = false;
}
}
checkInput();
var settings = {};
function onInit(){
console.log("Loading settings from BangleJs...");
try {
Util.readStorage("owmweather.json", data=>{
if(data.length > 0){
settings = JSON.parse(data);
console.log("Got settings", settings);
document.getElementById("apikey").value = settings.apikey;
console.log("Loaded apikey from BangleJs.");
checkInput();
}
});
} catch(ex) {
console.log("(Warning) Could not load apikey from BangleJs.");
console.log(ex);
}
}
document.getElementById("upload").addEventListener("click", function() {
try {
settings.apikey = document.getElementById("apikey").value;
Util.showModal("Saving...");
Util.writeStorage("owmweather.json", JSON.stringify(settings), ()=>{
Util.hideModal();
});
console.log("Sent settings!");
} catch(ex) {
console.log("(Warning) Could not write settings to BangleJs.");
console.log(ex);
}
});
</script>
</body>
</html>

53
apps/owmweather/lib.js Normal file
View File

@ -0,0 +1,53 @@
function parseWeather(response) {
let owmData = JSON.parse(response);
let isOwmData = owmData.coord && owmData.weather && owmData.main;
if (isOwmData) {
let json = require("Storage").readJSON('weather.json') || {};
let weather = {};
weather.time = Date.now();
weather.hum = owmData.main.humidity;
weather.temp = owmData.main.temp;
weather.code = owmData.weather[0].id;
weather.wdir = owmData.wind.deg;
weather.wind = owmData.wind.speed;
weather.loc = owmData.name;
weather.txt = owmData.weather[0].main;
if (weather.wdir != null) {
let deg = weather.wdir;
while (deg < 0 || deg > 360) {
deg = (deg + 360) % 360;
}
weather.wrose = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'n'][Math.floor((deg + 22.5) / 45)];
}
json.weather = weather;
require("Storage").writeJSON('weather.json', json);
require("weather").emit("update", json.weather);
return undefined;
} else {
return /*LANG*/"Not OWM data";
}
}
exports.pull = function(completionCallback) {
let location = require("Storage").readJSON("mylocation.json", 1) || {
"lat": 51.50,
"lon": 0.12,
"location": "London"
};
let settings = require("Storage").readJSON("owmweather.json", 1);
let uri = "https://api.openweathermap.org/data/2.5/weather?lat=" + location.lat.toFixed(2) + "&lon=" + location.lon.toFixed(2) + "&exclude=hourly,daily&appid=" + settings.apikey;
if (Bangle.http){
Bangle.http(uri, {timeout:10000}).then(event => {
let result = parseWeather(event.resp);
if (completionCallback) completionCallback(result);
}).catch((e)=>{
if (completionCallback) completionCallback(e);
});
} else {
if (completionCallback) completionCallback(/*LANG*/"No http method found");
}
};

View File

@ -0,0 +1,22 @@
{ "id": "owmweather",
"name": "OpenWeatherMap weather provider",
"shortName":"OWM Weather",
"version":"0.01",
"description": "Pulls weather from OpenWeatherMap (OWM) API",
"icon": "app.png",
"type": "bootloader",
"tags": "boot,tool,weather",
"supports" : ["BANGLEJS2"],
"interface": "interface.html",
"readme": "README.md",
"data": [
{"name":"owmweather.json"},
{"name":"weather.json"}
],
"storage": [
{"name":"owmweather.default.json","url":"default.json"},
{"name":"owmweather.boot.js","url":"boot.js"},
{"name":"owmweather","url":"lib.js"},
{"name":"owmweather.settings.js","url":"settings.js"}
]
}

View File

@ -0,0 +1,84 @@
(function(back) {
function writeSettings(key, value) {
var s = require('Storage').readJSON(FILE, true) || {};
s[key] = value;
require('Storage').writeJSON(FILE, s);
readSettings();
}
function readSettings(){
settings = Object.assign(
require('Storage').readJSON("owmweather.default.json", true) || {},
require('Storage').readJSON(FILE, true) || {}
);
}
var FILE="owmweather.json";
var settings;
readSettings();
function buildMainMenu(){
var mainmenu = {
'': { 'title': 'OWM weather' },
'< Back': back,
"Enabled": {
value: !!settings.enabled,
onchange: v => {
writeSettings("enabled", v);
}
},
"Refresh every": {
value: settings.refresh / 60,
min: 1,
max: 48,
step: 1,
format: v=>v+"h",
onchange: v => {
writeSettings("refresh",Math.round(v * 60));
}
},
"Force refresh": ()=>{
if (!settings.apikey){
E.showAlert("API key is needed","Hint").then(()=>{
E.showMenu(buildMainMenu());
});
} else {
E.showMessage("Reloading weather");
require("owmweather").pull((e)=>{
if (e) {
E.showAlert(e,"Error").then(()=>{
E.showMenu(buildMainMenu());
});
} else {
E.showAlert("Success").then(()=>{
E.showMenu(buildMainMenu());
});
}
});
}
}
};
mainmenu["API key"] = function (){
if (require("textinput")){
require("textinput").input({text:settings.apikey}).then(result => {
if (result != "") {
print("Result is", result);
settings.apikey = result;
writeSettings("apikey",result);
}
E.showMenu(buildMainMenu());
});
} else {
E.showPrompt("Install a text input lib"),then(()=>{
E.showMenu(buildMainMenu());
});
}
};
return mainmenu;
}
E.showMenu(buildMainMenu());
});

View File

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

View File

@ -0,0 +1,49 @@
# Presentation Timer
*Forked from Stopwatch Touch*
Simple application to keep track of slides and
time during a presentation. Useful for conferences,
lectures or any presentation with a somewhat strict timing.
The interface is pretty simple, it shows a stopwatch
and the number of the current slide (based on the time),
when the time for the last slide is approaching,
the button becomes red, when it passed,
the time will go on for another half a minute and stop automatically.
The only way to upload personalized timings is
by uploading a CSV to the bangle (i.e. from the IDE),
in the future I'll possibly figure out a better way.
Each line in the file (which must be called `presentation_timer.csv`)
contains the time in minutes at which the slide
is supposed to finish and the slide number,
separated by a semicolon.
For instance the line `1.5;1` means that slide 1
is lasting until 1 minutes 30 seconds (yes it's decimal),
after another slide will start.
The only requirement is that timings are increasing,
so slides number don't have to be consecutive,
some can be skipped and they can even be short texts
(be careful with that, I didn't test it).
At the moment the app is just quick and dirty
but it should do its job.
## Screenshots
![](screenshot1.png)
![](screenshot2.png)
![](screenshot3.png)
![](screenshot4.png)
## Example configuration file
_presentation_timer.csv_
```csv
1.5;1
2;2
2.5;3
3;4
```

View File

@ -0,0 +1,15 @@
{
"id": "presentation_timer",
"name": "Presentation Timer",
"version": "0.01",
"description": "A touch based presentation timer for Bangle JS 2",
"icon": "presentation_timer.png",
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
"tags": "tools,app",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"presentation_timer.app.js","url":"presentation_timer.app.js"},
{"name":"presentation_timer.img","url":"presentation_timer.icon.js","evaluate":true}
]
}

View File

@ -0,0 +1,272 @@
let w = g.getWidth();
let h = g.getHeight();
let tTotal = Date.now();
let tStart = tTotal;
let tCurrent = tTotal;
let running = false;
let timeY = 2*h/5;
let displayInterval;
let redrawButtons = true;
const iconScale = g.getWidth() / 178; // scale up/down based on Bangle 2 size
// 24 pixel images, scale to watch
// 1 bit optimal, image string, no E.toArrayBuffer()
const pause_img = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w==");
const play_img = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA=");
const reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w==");
const margin = 0.5; //half a minute tolerance
//dummy default values
var slides = [
[0.3, 1],
[0.5, 2],
[0.7, 3],
[1,4]
];
function log_debug(o) {
//console.log(o);
}
//first must be a number
function readSlides() {
let csv = require("Storage").read("presentation_timer.csv");
if(!csv) return;
let lines = csv.split("\n").filter(e=>e);
log_debug("Loading "+lines.length+" slides");
slides = lines.map(line=>{let s=line.split(";");return [+s[0],s[1]];});
}
function timeToText(t) {
let hrs = Math.floor(t/3600000);
let mins = Math.floor(t/60000)%60;
let secs = Math.floor(t/1000)%60;
let tnth = Math.floor(t/100)%10;
let text;
if (hrs === 0)
text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
else
text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
//log_debug(text);
return text;
}
function drawButtons() {
log_debug("drawButtons()");
if (!running && tCurrent == tTotal) {
bigPlayPauseBtn.draw();
} else if (!running && tCurrent != tTotal) {
resetBtn.draw();
smallPlayPauseBtn.draw();
} else {
bigPlayPauseBtn.draw();
}
redrawButtons = false;
}
//not efficient but damn easy
function findSlide(time) {
time /= 60000;
//change colour for the last 30 seconds
if(time > slides[slides.length-1][0] - margin && bigPlayPauseBtn.color!="#f00") {
bigPlayPauseBtn.color="#f00";
drawButtons();
}
for(let i=0; i<slides.length; i++) {
if(slides[i][0] > time)
return slides[i][1];
}
//stop automatically
if(time > slides[slides.length-1][0] + margin) {
bigPlayPauseBtn.color="#0ff"; //restore
stopTimer();
}
return /*LANG*/"end!";
}
function drawTime() {
log_debug("drawTime()");
let Tt = tCurrent-tTotal;
let Ttxt = timeToText(Tt);
Ttxt += "\n"+findSlide(Tt);
// total time
g.setFont("Vector",38); // check
g.setFontAlign(0,0);
g.clearRect(0, timeY - 42, w, timeY + 42);
g.setColor(g.theme.fg);
g.drawString(Ttxt, w/2, timeY);
}
function draw() {
let last = tCurrent;
if (running) tCurrent = Date.now();
g.setColor(g.theme.fg);
if (redrawButtons) drawButtons();
drawTime();
}
function startTimer() {
log_debug("startTimer()");
draw();
displayInterval = setInterval(draw, 100);
}
function stopTimer() {
log_debug("stopTimer()");
if (displayInterval) {
clearInterval(displayInterval);
displayInterval = undefined;
}
}
// BTN stop start
function stopStart() {
log_debug("stopStart()");
if (running)
stopTimer();
running = !running;
Bangle.buzz();
if (running)
tStart = Date.now() + tStart- tCurrent;
tTotal = Date.now() + tTotal - tCurrent;
tCurrent = Date.now();
setButtonImages();
redrawButtons = true;
if (running) {
startTimer();
} else {
draw();
}
}
function setButtonImages() {
if (running) {
bigPlayPauseBtn.setImage(pause_img);
smallPlayPauseBtn.setImage(pause_img);
resetBtn.setImage(reset_img);
} else {
bigPlayPauseBtn.setImage(play_img);
smallPlayPauseBtn.setImage(play_img);
resetBtn.setImage(reset_img);
}
}
// lap or reset
function lapReset() {
log_debug("lapReset()");
if (!running && tStart != tCurrent) {
redrawButtons = true;
Bangle.buzz();
tStart = tCurrent = tTotal = Date.now();
g.clearRect(0,24,w,h);
draw();
}
}
// simple on screen button class
function BUTTON(name,x,y,w,h,c,f,i) {
this.name = name;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = c;
this.callback = f;
this.img = i;
}
BUTTON.prototype.setImage = function(i) {
this.img = i;
}
// if pressed the callback
BUTTON.prototype.check = function(x,y) {
//console.log(this.name + ":check() x=" + x + " y=" + y +"\n");
if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) {
log_debug(this.name + ":callback\n");
this.callback();
return true;
}
return false;
};
BUTTON.prototype.draw = function() {
g.setColor(this.color);
g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h);
g.setColor("#000"); // the icons and boxes are drawn black
if (this.img != undefined) {
let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale
let ix = this.x + ((this.w - iw) /2);
let iy = this.y + ((this.h - iw) /2);
log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})");
g.drawImage(this.img, ix, iy, {scale: iconScale});
}
g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h);
};
var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img);
var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img);
var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img);
bigPlayPauseBtn.setImage(play_img);
smallPlayPauseBtn.setImage(play_img);
resetBtn.setImage(pause_img);
Bangle.on('touch', function(button, xy) {
var x = xy.x;
var y = xy.y;
// adjust for outside the dimension of the screen
// http://forum.espruino.com/conversations/371867/#comment16406025
if (y > h) y = h;
if (y < 0) y = 0;
if (x > w) x = w;
if (x < 0) x = 0;
// not running, and reset
if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
// paused and hit play
if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
// paused and press reset
if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
// must be running
if (running && bigPlayPauseBtn.check(x, y)) return;
});
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (on) {
draw(); // draw immediately, queue redraw
} else { // stop draw timer
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
// Clear the screen once, at startup
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
// above not working, hence using next 2 lines
g.setColor("#000");
g.fillRect(0,0,w,h);
Bangle.loadWidgets();
Bangle.drawWidgets();
draw();
setWatch(() => load(), BTN, { repeat: false, edge: "falling" });
readSlides();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AC1WwIZXACmBF7FWAH4Ae/0WAFiQBF9+sAFgv/AAvXAAgvmFgoyWF6IuLGCIvPFpoxRF5wlIwIKJF8lWwIvjQpIvKGBgv8cpWBF5QwLF/4vrEJQvNGBQv/F5FWSCq/XGAVWB5DviEgRiJF8gxDF9q+SF5owWEJYv9GCggMF5wwSD5ovPGCAeOF6AwODp4vRGJYbRF6YAbF/4v/F8eBAYYECAYYvRACFWqwEGwNWwIeSF7IEFAD5VBGhpekAo6QiEYo1LR0QpGBgyOhAxCQfKIIhFGxpegA44+HF85gRA=="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,2 @@
...
0.02: First update with ChangeLog Added

View File

@ -5,4 +5,5 @@
0.05: BUGFIX: pedometer widget interfered with the clock Font Alignment
0.06: Use Bangle.setUI for button/launcher handling
0.07: Support for Bangle.js 2 and themes
0.08: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".
0.08: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".
0.09: Added button control toggle and other live controls to new settings screen.

View File

@ -6,22 +6,49 @@ Inspired by the Pebble sliding clock, old times are scrolled off the screen and
## Usage
### Button 1
### Bangle 2
The Bangle 2 has Live Controls switched **off** by default so the colour and language have to be changed from the setting Menu.
Please locate the Sliding Text clock under the setting->apps menu.
With the Live Controls switched on:
#### Bottom right hand corner press
press the bottom right hand corner of the screen to change the colour
| White | Black | Gray | Red |
|----------------------|----------------------|----------------------|----------------------|
| ![](b2_color-01.jpg) | ![](b2_color-02.jpg) | ![](b2_color-03.jpg) | ![](b2_color-04.jpg) |
#### Top right hand corner press
press the top right hand corner of the screen to change the language
### Bangle 1
By Default the Live Controls (The side buttons) are switched on, which means the clock face can be controlled dynamically using the 2 side buttons on the right hand side
#### Button 1
Use Button 1 (the top right button) to change the language
| 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) | | |
| ![](format-05.jpg) | ![](format-06.jpg) | | |
### Button 3
#### Button 3
Button 3 (bottom right button) is used to change the colour
| Black | Red | Gray | Purple |
| ---- | ---- | ---- | ---- |
| ![](./color-01.jpg) | ![](color-02.jpg) | ![](color-03.jpg) | ![](color-04.jpg) |
| ![](b1_color-01.jpg) | ![](b1_color-02.jpg) | ![](b1_color-03.jpg) | ![](b1_color-04.jpg) |
#### Settings
To turn off the Live Controls and change the settings statically please visit the settings menu. The settings menu will allow you to:
- Colour Scheme
- Language
- Live Controls (On or Off)
## Further Details

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,7 +1,7 @@
{
"id": "slidingtext",
"name": "Sliding Clock",
"version": "0.08",
"version": "0.09",
"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",
"icon": "slidingtext.png",
"type": "clock",
@ -12,6 +12,7 @@
"allow_emulator": false,
"storage": [
{"name":"slidingtext.app.js","url":"slidingtext.js"},
{"name":"slidingtext.settings.js","url":"slidingtext.settings.js"},
{"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true},
{"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"},
{"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"},
@ -21,5 +22,6 @@
{"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"}
]
],
"data": [{"name": "slidingtext.settings.json"}]
}

View File

@ -7,6 +7,7 @@ class DateFormatter {
* to the lines of text on the screen
*/
name(){return "no name";}
shortName(){return "no short name"}
formatDate(date){
return ["no","date","defined"];
}

View File

@ -5,11 +5,17 @@
*/
const color_schemes = [
{
name: "white",
background : [1.0,1.0,1.0],
main_bar: [0.0,0.0,0.0],
other_bars: [0.1,0.1,0.1],
},
{
name: "black",
background : [0.0,0.0,0.0],
main_bar: [1.0,1.0,1.0],
other_bars: [0.85,0.85,0.85],
other_bars: [0.9,0.9,0.9],
},
{
name: "red",
@ -81,7 +87,7 @@ function reset_commands(){
function has_commands(){
return command_stack_high_priority.length > 0 ||
command_stack_low_priority.lenth > 0;
command_stack_low_priority.length > 0;
}
class ShiftText {
@ -244,15 +250,19 @@ function setRowDisplays(y, heights) {
y += heights[i];
}
}
if (g.getHeight()>200)
function bangleVersion(){
return (g.getHeight()>200)? 1 : 2;
}
if (bangleVersion()<2)
setRowDisplays(50, [40,30,30,30,40]);
else
setRowDisplays(34, [35,25,25,25,35]);
function nextColorTheme(){
//console.log("next color theme");
color_scheme_index += 1;
if(color_scheme_index >= row_displays.length){
if(color_scheme_index > row_displays.length){
color_scheme_index = 0;
}
setColorScheme(color_schemes[color_scheme_index]);
@ -411,8 +421,7 @@ function draw_clock(){
reset_commands();
date = display_time(date);
console.log("draw_clock:" + last_draw_time.toISOString() + " display:" + date.toISOString());
// for debugging only
//date.setMinutes(37);
var rows = date_formatter.formatDate(date);
var display;
for (var i = 0; i < rows.length; i++) {
@ -495,7 +504,7 @@ function set_colorscheme(colorscheme_name){
function set_dateformat(dateformat_name){
console.log("setting date format:" + dateformat_name);
for (var i=0; i < date_formatters.length; i++) {
if(date_formatters[i].name() == dateformat_name){
if(date_formatters[i].shortName() == dateformat_name){
date_formatter_idx = i;
date_formatter = date_formatters[date_formatter_idx];
console.log("match");
@ -503,6 +512,7 @@ function set_dateformat(dateformat_name){
}
}
var enable_live_controls = false;
const PREFERENCE_FILE = "slidingtext.settings.json";
/**
* Called on startup to set the watch to the last preference settings
@ -510,7 +520,7 @@ const PREFERENCE_FILE = "slidingtext.settings.json";
function load_settings(){
var setScheme = false;
try{
settings = require("Storage").readJSON(PREFERENCE_FILE);
var settings = require("Storage").readJSON(PREFERENCE_FILE);
if(settings != null){
console.log("loaded:" + JSON.stringify(settings));
if(settings.color_scheme != null){
@ -520,9 +530,15 @@ function load_settings(){
if(settings.date_format != null){
set_dateformat(settings.date_format);
}
if(settings.enable_live_controls == null){
settings.enable_live_controls = (bangleVersion() <= 1);
}
enable_live_controls = settings.enable_live_controls;
} else {
console.log("no settings to load");
enable_live_controls = (bangleVersion() <= 1);
}
console.log("enable_live_controls=" + enable_live_controls);
} catch(e){
console.log("failed to load settings:" + e);
}
@ -536,24 +552,30 @@ function load_settings(){
*/
function save_settings(){
var settings = {
date_format : date_formatter.name(),
date_format : date_formatter.shortName(),
color_scheme : color_schemes[color_scheme_index].name,
enable_live_controls: enable_live_controls
};
console.log("saving:" + JSON.stringify(settings));
require("Storage").writeJSON(PREFERENCE_FILE,settings);
}
function button1pressed() {
changeFormatter();
save_settings();
console.log("button1pressed");
if (enable_live_controls) {
changeFormatter();
save_settings();
}
}
function button3pressed() {
console.log("button3pressed");
nextColorTheme();
reset_clock(true);
draw_clock();
save_settings();
if (enable_live_controls) {
nextColorTheme();
reset_clock(true);
draw_clock();
save_settings();
}
}
// The interval reference for updating the clock

View File

@ -66,6 +66,7 @@ function germanMinsToText(mins) {
class GermanDateFormatter extends DateFormatter {
constructor() { super();}
name(){return "German";}
shortName(){return "de"}
formatDate(date){
var mins = date.getMinutes();
var hourOfDay = date.getHours();

View File

@ -5,6 +5,7 @@ const numberToText = require("slidingtext.utils.en.js").numberToText;
class EnglishDateFormatter extends DateFormatter {
constructor() { super();}
name(){return "English";}
shortName(){return "en"}
formatDate(date){
var hours_txt = hoursToText(date.getHours());
var mins_txt = numberToText(date.getMinutes());

View File

@ -7,6 +7,7 @@ class EnglishTraditionalDateFormatter extends DateFormatter {
super();
}
name(){return "English (Traditional)";}
shortName(){return "en2"}
formatDate(date){
var mins = date.getMinutes();
var hourOfDay = date.getHours();

View File

@ -47,6 +47,7 @@ function spanishMinsToText(mins){
class SpanishDateFormatter extends DateFormatter {
constructor() { super();}
name(){return "Spanish";}
shortName(){return "es"}
formatDate(date){
var mins = date.getMinutes();
var hourOfDay = date.getHours();

View File

@ -31,6 +31,7 @@ function frenchHeures(hours){
class FrenchDateFormatter extends DateFormatter {
constructor() { super(); }
name(){return "French";}
shortName(){return "fr"}
formatDate(date){
var hours = frenchHoursToText(date.getHours());
var heures = frenchHeures(date.getHours());

View File

@ -61,6 +61,7 @@ function japaneseMinsToText(mins){
class JapaneseDateFormatter extends DateFormatter {
constructor() { super(); }
name(){return "Japanese (Romanji)";}
shortName(){return "jp"}
formatDate(date){
var hours_txt = japaneseHoursToText(date.getHours());
var mins_txt = japaneseMinsToText(date.getMinutes());

View File

@ -0,0 +1,65 @@
(function(back) {
const PREFERENCE_FILE = "slidingtext.settings.json";
var settings = Object.assign({},
require('Storage').readJSON(PREFERENCE_FILE, true) || {});
// the screen controls are defaulted on for a bangle 1 and off for a bangle 2
if(settings.enable_live_controls == null){
settings.enable_live_controls = (g.getHeight()> 200);
}
console.log("loaded:" + JSON.stringify(settings));
const LANGUAGES_FILE = "slidingtext.languages.json";
const LANGUAGES_DEFAULT = ["en","en2"];
var locales = null;
try {
locales = require("Storage").readJSON(LANGUAGES_FILE);
} catch(e) {
console.log("failed to load languages:" + e);
}
if(locales == null || locales.length == 0){
locales = LANGUAGES_DEFAULT;
console.log("defaulting languages to locale:" + locales);
}
function writeSettings() {
console.log("saving:" + JSON.stringify(settings));
require('Storage').writeJSON(PREFERENCE_FILE, settings);
}
// Helper method which uses int-based menu item for set of string values
function stringItems(startvalue, writer, values) {
return {
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
format: v => values[v],
min: 0,
max: values.length - 1,
wrap: true,
step: 1,
onchange: v => {
writer(values[v]);
writeSettings();
}
};
}
// Helper method which breaks string set settings down to local settings object
function stringInSettings(name, values) {
return stringItems(settings[name], v => settings[name] = v, values);
}
// Show the menu
E.showMenu({
"" : { "title" : "Sliding Text" },
"< Back" : () => back(),
"Colour": stringInSettings("color_scheme", ["white", "black", "red","grey","purple","blue"]),
"Languages": stringInSettings("date_format", locales),
"Live Control": {
value: (settings.enable_live_controls !== undefined ? settings.enable_live_controls : true),
format: v => v ? "On" : "Off",
onchange: v => {
settings.enable_live_controls = v;
writeSettings();
}
},
});
})

2
apps/teatimer/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Fix issue setting colors after showMessage

View File

@ -67,9 +67,9 @@ function startTimer() {
- hint for help in state start
*/
function showCounter(withHint) {
//g.clear();
g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier
E.showMessage("", appTitle());
g.setFontAlign(0,0); // center font
g.reset().setFontAlign(0,0); // center font
// draw the current counter value
g.setBgColor(-1).setColor(0,0,1); // blue
g.setFont("Vector",20); // vector font, 20px
@ -123,9 +123,9 @@ function countUp() {
outOfTime();
return;
}
g.clear();
g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier
E.showMessage("", appTitle());
g.setFontAlign(0,0); // center font
g.reset().setFontAlign(0,0); // center font
g.setBgColor(-1).setColor(0,0,1); // blue
g.setFont("Vector",20); // vector font, 20px
g.drawString("Timer: " + timeFormated(counterStart),80,55);

View File

@ -1,7 +1,7 @@
{
"id": "teatimer",
"name": "Tea Timer",
"version": "0.01",
"version": "0.02",
"description": "A simple timer. You can easyly set up the time.",
"icon": "teatimer.png",
"type": "app",

View File

@ -0,0 +1,2 @@
...
0.04: First update with ChangeLog Added

Some files were not shown because too many files have changed in this diff Show More