Merge branch 'espruino:master' into master
|
@ -282,8 +282,11 @@ and which gives information about the app for the Launcher.
|
|||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
"dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require'
|
||||
"dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require' - see provides_modules
|
||||
"dependencies" : { "message":"widget" } // optional, depend on a specific type of widget - see provides_widgets
|
||||
"provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require'
|
||||
"provides_widgets" : ["battery"] // optional, this app provides a type of widget - 'alarm/battery/bluetooth/pedometer/message'
|
||||
"default" : true, // set if an app is the default implementer of something (a widget/module/etc)
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
// that contains more information about this app (usage, etc)
|
||||
// A 'Read more...' link will be added under the app
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Design improvements and fixes.
|
||||
0.03: Indicate battery level through line occurrence.
|
||||
0.04: Use widget_utils module.
|
||||
0.05: Support for clkinfo.
|
|
@ -10,7 +10,9 @@ The original output of stable diffusion is shown here:
|
|||
|
||||
My implementation is shown below. Note that horizontal lines occur randomly, but the
|
||||
probability is correlated with the battery level. So if your screen contains only
|
||||
a few lines its time to charge your bangle again ;)
|
||||
a few lines its time to charge your bangle again ;) Also note that the upper text
|
||||
implementes the clkinfo module and can be configured via touch left/right/up/down.
|
||||
Touch at the center to trigger the selected action.
|
||||
|
||||

|
||||
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
/**
|
||||
/************************************************
|
||||
* AI Clock
|
||||
*/
|
||||
const storage = require('Storage');
|
||||
const clock_info = require("clock_info");
|
||||
|
||||
|
||||
|
||||
/************************************************
|
||||
* Assets
|
||||
*/
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
Graphics.prototype.setFontGochiHand = function(scale) {
|
||||
// Actual height 27 (29 - 3)
|
||||
|
@ -13,7 +21,7 @@ Graphics.prototype.setFontGochiHand = function(scale) {
|
|||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
/************************************************
|
||||
* Set some important constants such as width, height and center
|
||||
*/
|
||||
var W = g.getWidth(),R=W/2;
|
||||
|
@ -21,6 +29,120 @@ var H = g.getHeight();
|
|||
var cx = W/2;
|
||||
var cy = H/2;
|
||||
var drawTimeout;
|
||||
var lock_input = false;
|
||||
|
||||
|
||||
/************************************************
|
||||
* SETTINGS
|
||||
*/
|
||||
const SETTINGS_FILE = "aiclock.setting.json";
|
||||
let settings = {
|
||||
menuPosX: 0,
|
||||
menuPosY: 0,
|
||||
};
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
settings[key] = saved_settings[key]
|
||||
}
|
||||
|
||||
|
||||
/************************************************
|
||||
* Menu
|
||||
*/
|
||||
function getDate(){
|
||||
var date = new Date();
|
||||
return ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2)
|
||||
}
|
||||
|
||||
|
||||
// Custom clockItems menu - therefore, its added here and not in a clkinfo.js file.
|
||||
var clockItems = {
|
||||
name: getDate(),
|
||||
img: null,
|
||||
items: [
|
||||
{ name: "Week",
|
||||
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
||||
show: function() { clockItems.items[0].emit("redraw"); },
|
||||
hide: function () {}
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
function weekOfYear() {
|
||||
var date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
// Thursday in current week decides the year.
|
||||
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
|
||||
// January 4 is always in week 1.
|
||||
var week1 = new Date(date.getFullYear(), 0, 4);
|
||||
// Adjust to Thursday in week 1 and count number of weeks from date to week1.
|
||||
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
|
||||
- 3 + (week1.getDay() + 6) % 7) / 7);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Load menu
|
||||
var menu = clock_info.load();
|
||||
menu = menu.concat(clockItems);
|
||||
|
||||
|
||||
// Ensure that our settings are still in range (e.g. app uninstall). Otherwise reset the position it.
|
||||
if(settings.menuPosX >= menu.length || settings.menuPosY > menu[settings.menuPosX].items.length ){
|
||||
settings.menuPosX = 0;
|
||||
settings.menuPosY = 0;
|
||||
}
|
||||
|
||||
// Set draw functions for each item
|
||||
menu.forEach((menuItm, x) => {
|
||||
menuItm.items.forEach((item, y) => {
|
||||
function drawItem() {
|
||||
// For the clock, we have a special case, as we don't wanna redraw
|
||||
// immediately when something changes. Instead, we update data each minute
|
||||
// to save some battery etc. Therefore, we hide (and disable the listener)
|
||||
// immedeately after redraw...
|
||||
item.hide();
|
||||
|
||||
// After drawing the item, we enable inputs again...
|
||||
lock_input = false;
|
||||
|
||||
var info = item.get();
|
||||
drawMenuItem(info.text, info.img);
|
||||
}
|
||||
|
||||
item.on('redraw', drawItem);
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
function canRunMenuItem(){
|
||||
if(settings.menuPosY == 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
return item.run !== undefined;
|
||||
}
|
||||
|
||||
|
||||
function runMenuItem(){
|
||||
if(settings.menuPosY == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
try{
|
||||
var ret = item.run();
|
||||
if(ret){
|
||||
Bangle.buzz(300, 0.6);
|
||||
}
|
||||
} catch (ex) {
|
||||
// Simply ignore it...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Based on the great multi clock from https://github.com/jeffmer/BangleApps/
|
||||
|
@ -76,7 +198,50 @@ function toAngle(a){
|
|||
return a
|
||||
}
|
||||
|
||||
|
||||
function drawMenuItem(text, image){
|
||||
if(text == null){
|
||||
drawTime();
|
||||
return
|
||||
}
|
||||
// image = atob("GBiBAAD+AAH+AAH+AAH+AAH/AAOHAAYBgAwAwBgwYBgwYBgwIBAwOBAwOBgYIBgMYBgAYAwAwAYBgAOHAAH/AAH+AAH+AAH+AAD+AA==");
|
||||
|
||||
text = String(text);
|
||||
|
||||
g.reset().setBgColor("#fff").setColor("#000");
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont("Vector", 20);
|
||||
|
||||
var imgWidth = image == null ? 0 : 24;
|
||||
var strWidth = g.stringWidth(text);
|
||||
var strHeight = text.split('\n').length > 1 ? 40 : Math.max(24, imgWidth+2);
|
||||
var w = imgWidth + strWidth;
|
||||
|
||||
g.clearRect(cx-w/2-8, 40-strHeight/2-1, cx+w/2+4, 40+strHeight/2)
|
||||
|
||||
// Draw right line as designed by stable diffusion
|
||||
g.drawLine(cx+w/2+5, 40-strHeight/2-1, cx+w/2+5, 40+strHeight/2);
|
||||
g.drawLine(cx+w/2+6, 40-strHeight/2-1, cx+w/2+6, 40+strHeight/2);
|
||||
g.drawLine(cx+w/2+7, 40-strHeight/2-1, cx+w/2+7, 40+strHeight/2);
|
||||
|
||||
// And finally the text
|
||||
g.drawString(text, cx+imgWidth/2, 42);
|
||||
g.drawString(text, cx+1+imgWidth/2, 41);
|
||||
|
||||
if(image != null) {
|
||||
var scale = image.width ? imgWidth / image.width : 1;
|
||||
g.drawImage(image, W/2 + -strWidth/2-4 - parseInt(imgWidth/2), 41-12, {scale: scale});
|
||||
}
|
||||
|
||||
drawTime();
|
||||
}
|
||||
|
||||
|
||||
function drawTime(){
|
||||
// Draw digital time first
|
||||
drawDigits();
|
||||
|
||||
// And now the analog time
|
||||
var drawHourHand = g.drawRotRect.bind(g,8,12,R-38);
|
||||
var drawMinuteHand = g.drawRotRect.bind(g,6,12,R-12 );
|
||||
|
||||
|
@ -90,13 +255,6 @@ function drawTime(){
|
|||
h += date.getMinutes()/60.0;
|
||||
h = parseInt(h*360/12);
|
||||
|
||||
// Draw minute and hour bg
|
||||
g.setColor(g.theme.bg);
|
||||
drawHourHand(toAngle(h-3));
|
||||
drawHourHand(toAngle(h+3));
|
||||
drawMinuteHand(toAngle(m-2));
|
||||
drawMinuteHand(toAngle(m+3));
|
||||
|
||||
// Draw minute and hour fg
|
||||
g.setColor(g.theme.fg);
|
||||
drawHourHand(h);
|
||||
|
@ -104,28 +262,6 @@ function drawTime(){
|
|||
}
|
||||
|
||||
|
||||
|
||||
function drawDate(){
|
||||
var date = new Date();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFontGochiHand();
|
||||
|
||||
var text = ("0"+date.getDate()).substr(-2) + "/" + ("0"+(date.getMonth()+1)).substr(-2);
|
||||
var w = g.stringWidth(text);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(cx-w/2-4, 20, cx+w/2+4, 40+12);
|
||||
|
||||
g.setColor(g.theme.fg);
|
||||
// Draw right line as designed by stable diffusion
|
||||
g.drawLine(cx+w/2+5, 20, cx+w/2+5, 40+12);
|
||||
g.drawLine(cx+w/2+6, 20, cx+w/2+6, 40+12);
|
||||
g.drawLine(cx+w/2+7, 20, cx+w/2+7, 40+12);
|
||||
|
||||
// And finally the text
|
||||
g.drawString(text, cx, 40);
|
||||
}
|
||||
|
||||
|
||||
function drawDigits(){
|
||||
var date = new Date();
|
||||
|
||||
|
@ -156,20 +292,35 @@ function drawDigits(){
|
|||
}
|
||||
|
||||
|
||||
function drawDate(){
|
||||
var menuEntry = menu[settings.menuPosX];
|
||||
|
||||
// The first entry is the overview...
|
||||
if(settings.menuPosY == 0){
|
||||
drawMenuItem(menuEntry.name, menuEntry.img);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw item if needed
|
||||
lock_input = true;
|
||||
var item = menuEntry.items[settings.menuPosY-1];
|
||||
item.show();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function draw(){
|
||||
// Queue draw in one minute
|
||||
queueDraw();
|
||||
|
||||
|
||||
g.reset();
|
||||
g.clearRect(0, 0, g.getWidth(), g.getHeight());
|
||||
|
||||
g.setColor(1,1,1);
|
||||
|
||||
drawBackground();
|
||||
drawDate();
|
||||
drawDigits();
|
||||
drawTime();
|
||||
drawCircle(Bangle.isLocked());
|
||||
}
|
||||
|
||||
|
@ -190,6 +341,68 @@ Bangle.on('lock', function(isLocked) {
|
|||
drawCircle(isLocked);
|
||||
});
|
||||
|
||||
Bangle.on('touch', function(btn, e){
|
||||
var left = parseInt(g.getWidth() * 0.22);
|
||||
var right = g.getWidth() - left;
|
||||
var upper = parseInt(g.getHeight() * 0.22);
|
||||
var lower = g.getHeight() - upper;
|
||||
|
||||
var is_upper = e.y < upper;
|
||||
var is_lower = e.y > lower;
|
||||
var is_left = e.x < left && !is_upper && !is_lower;
|
||||
var is_right = e.x > right && !is_upper && !is_lower;
|
||||
var is_center = !is_upper && !is_lower && !is_left && !is_right;
|
||||
|
||||
if(lock_input){
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_lower){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = (settings.menuPosY+1) % (menu[settings.menuPosX].items.length+1);
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
if(is_upper){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = settings.menuPosY-1;
|
||||
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].items.length : settings.menuPosY;
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
if(is_right){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosX = (settings.menuPosX+1) % menu.length;
|
||||
settings.menuPosY = 0;
|
||||
draw();
|
||||
}
|
||||
|
||||
if(is_left){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = 0;
|
||||
settings.menuPosX = settings.menuPosX-1;
|
||||
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
|
||||
draw();
|
||||
}
|
||||
|
||||
if(is_center){
|
||||
if(canRunMenuItem()){
|
||||
runMenuItem();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
E.on("kill", function(){
|
||||
try{
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
} catch(ex){
|
||||
// If this fails, we still kill the app...
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Some helpers
|
||||
|
@ -203,7 +416,6 @@ function queueDraw() {
|
|||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Lets start widgets, listen for btn etc.
|
||||
*/
|
||||
|
@ -216,6 +428,7 @@ Bangle.loadWidgets();
|
|||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
require('widget_utils').hide();
|
||||
|
||||
// Clear the screen once, at startup and draw clock
|
||||
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
||||
draw();
|
||||
|
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
|
@ -3,7 +3,7 @@
|
|||
"name": "AI Clock",
|
||||
"shortName":"AI Clock",
|
||||
"icon": "aiclock.png",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "A watch face that was designed by an AI (stable diffusion) and implemented by a human.",
|
||||
|
@ -11,7 +11,9 @@
|
|||
"tags": "clock",
|
||||
"screenshots": [
|
||||
{"url":"orig.png"},
|
||||
{"url":"impl.png"}
|
||||
{"url":"impl.png"},
|
||||
{"url":"impl_2.png"},
|
||||
{"url":"impl_3.png"}
|
||||
],
|
||||
"storage": [
|
||||
{"name":"aiclock.app.js","url":"aiclock.app.js"},
|
||||
|
|
|
@ -36,4 +36,4 @@
|
|||
0.33: Allow hiding timers&alarms
|
||||
0.34: Add "Confirm" option to alarm/timer edit menus
|
||||
0.35: Add automatic translation of more strings
|
||||
|
||||
0.36: alarm widget moved out of app
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.35",
|
||||
"version": "0.36",
|
||||
"description": "Set alarms and timers on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm,widget",
|
||||
"tags": "tool,alarm",
|
||||
"supports": [ "BANGLEJS", "BANGLEJS2" ],
|
||||
"readme": "README.md",
|
||||
"dependencies": { "scheduler":"type" },
|
||||
"dependencies": { "scheduler":"type", "alarm":"widget" },
|
||||
"storage": [
|
||||
{ "name": "alarm.app.js", "url": "app.js" },
|
||||
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true },
|
||||
{ "name": "alarm.wid.js", "url": "widget.js" }
|
||||
{ "name": "alarm.img", "url": "app-icon.js", "evaluate": true }
|
||||
],
|
||||
"screenshots": [
|
||||
{ "url": "screenshot-1.png" },
|
||||
|
|
|
@ -17,3 +17,4 @@
|
|||
0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge
|
||||
0.18: Use new message library
|
||||
If connected to Gadgetbridge, allow GPS forwarding from phone (Gadgetbridge code still not merged)
|
||||
0.19: Add automatic translation for a couple of strings.
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
t:event.cmd=="incoming"?"add":"remove",
|
||||
id:"call", src:"Phone",
|
||||
positive:true, negative:true,
|
||||
title:event.name||"Call", body:"Incoming call\n"+event.number});
|
||||
title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"alarm" : function() {
|
||||
|
@ -148,7 +148,7 @@
|
|||
Bangle.http = (url,options)=>{
|
||||
options = options||{};
|
||||
if (!NRF.getSecurityStatus().connected)
|
||||
return Promise.reject("Not connected to Bluetooth");
|
||||
return Promise.reject(/*LANG*/"Not connected to Bluetooth");
|
||||
if (Bangle.httpRequest === undefined)
|
||||
Bangle.httpRequest={};
|
||||
if (options.id === undefined) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.18",
|
||||
"version": "0.19",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Create astrocalc app
|
||||
0.02: Store last GPS lock, can be used instead of waiting for new GPS on start
|
||||
0.03: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
* Calculate the Sun and Moon positions based on watch GPS and display graphically
|
||||
*/
|
||||
|
||||
const SunCalc = require("suncalc.js");
|
||||
const SunCalc = require("suncalc"); // from modules folder
|
||||
const storage = require("Storage");
|
||||
const LAST_GPS_FILE = "astrocalc.gps.json";
|
||||
let lastGPS = (storage.readJSON(LAST_GPS_FILE, 1) || null);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "astrocalc",
|
||||
"name": "Astrocalc",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
|
||||
"icon": "astrocalc.png",
|
||||
"tags": "app,sun,moon,cycles,tool,outdoors",
|
||||
|
@ -9,7 +9,6 @@
|
|||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"astrocalc.app.js","url":"astrocalc-app.js"},
|
||||
{"name":"suncalc.js","url":"suncalc.js"},
|
||||
{"name":"astrocalc.img","url":"astrocalc-icon.js","evaluate":true},
|
||||
{"name":"first-quarter.img","url":"first-quarter-icon.js","evaluate":true},
|
||||
{"name":"last-quarter.img","url":"last-quarter-icon.js","evaluate":true},
|
||||
|
|
|
@ -1,328 +0,0 @@
|
|||
/*
|
||||
(c) 2011-2015, Vladimir Agafonkin
|
||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
||||
https://github.com/mourner/suncalc
|
||||
*/
|
||||
|
||||
(function () { 'use strict';
|
||||
|
||||
// shortcuts for easier to read formulas
|
||||
|
||||
var PI = Math.PI,
|
||||
sin = Math.sin,
|
||||
cos = Math.cos,
|
||||
tan = Math.tan,
|
||||
asin = Math.asin,
|
||||
atan = Math.atan2,
|
||||
acos = Math.acos,
|
||||
rad = PI / 180;
|
||||
|
||||
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
|
||||
|
||||
|
||||
// date/time constants and conversions
|
||||
|
||||
var dayMs = 1000 * 60 * 60 * 24,
|
||||
J1970 = 2440588,
|
||||
J2000 = 2451545;
|
||||
|
||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
||||
function fromJulian(j) { return (j + 0.5 - J1970) * dayMs; }
|
||||
function toDays(date) { return toJulian(date) - J2000; }
|
||||
|
||||
|
||||
// general calculations for position
|
||||
|
||||
var e = rad * 23.4397; // obliquity of the Earth
|
||||
|
||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
||||
|
||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
||||
|
||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
||||
|
||||
function astroRefraction(h) {
|
||||
if (h < 0) // the following formula works for positive altitudes only.
|
||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
||||
|
||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
||||
}
|
||||
|
||||
// general sun calculations
|
||||
|
||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
||||
|
||||
function eclipticLongitude(M) {
|
||||
|
||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
||||
P = rad * 102.9372; // perihelion of the Earth
|
||||
|
||||
return M + C + P + PI;
|
||||
}
|
||||
|
||||
function sunCoords(d) {
|
||||
|
||||
var M = solarMeanAnomaly(d),
|
||||
L = eclipticLongitude(M);
|
||||
|
||||
return {
|
||||
dec: declination(L, 0),
|
||||
ra: rightAscension(L, 0)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var SunCalc = {};
|
||||
|
||||
|
||||
// calculates sun position for a given date and latitude/longitude
|
||||
|
||||
SunCalc.getPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = sunCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra;
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: altitude(H, phi, c.dec)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// sun times configuration (angle, morning name, evening name)
|
||||
|
||||
var times = SunCalc.times = [
|
||||
[-0.833, 'sunrise', 'sunset' ],
|
||||
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
|
||||
[ -6, 'dawn', 'dusk' ],
|
||||
[ -12, 'nauticalDawn', 'nauticalDusk'],
|
||||
[ -18, 'nightEnd', 'night' ],
|
||||
[ 6, 'goldenHourEnd', 'goldenHour' ]
|
||||
];
|
||||
|
||||
// adds a custom time to the times config
|
||||
|
||||
SunCalc.addTime = function (angle, riseName, setName) {
|
||||
times.push([angle, riseName, setName]);
|
||||
};
|
||||
|
||||
|
||||
// calculations for sun times
|
||||
|
||||
var J0 = 0.0009;
|
||||
|
||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
||||
|
||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
||||
|
||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
||||
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
|
||||
|
||||
// returns set time for the given sun altitude
|
||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
||||
|
||||
var w = hourAngle(h, phi, dec),
|
||||
a = approxTransit(w, lw, n);
|
||||
return solarTransitJ(a, M, L);
|
||||
}
|
||||
|
||||
|
||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
||||
// the observer height (in meters) relative to the horizon
|
||||
|
||||
SunCalc.getTimes = function (date, lat, lng, height) {
|
||||
|
||||
height = height || 0;
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
|
||||
dh = observerAngle(height),
|
||||
|
||||
d = toDays(date),
|
||||
n = julianCycle(d, lw),
|
||||
ds = approxTransit(0, lw, n),
|
||||
|
||||
M = solarMeanAnomaly(ds),
|
||||
L = eclipticLongitude(M),
|
||||
dec = declination(L, 0),
|
||||
|
||||
Jnoon = solarTransitJ(ds, M, L),
|
||||
|
||||
i, len, time, h0, Jset, Jrise;
|
||||
|
||||
|
||||
var result = {
|
||||
solarNoon: new Date(fromJulian(Jnoon)),
|
||||
nadir: new Date(fromJulian(Jnoon - 0.5))
|
||||
};
|
||||
|
||||
for (i = 0, len = times.length; i < len; i += 1) {
|
||||
time = times[i];
|
||||
h0 = (time[0] + dh) * rad;
|
||||
|
||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
||||
Jrise = Jnoon - (Jset - Jnoon);
|
||||
|
||||
result[time[1]] = new Date(fromJulian(Jrise) - (dayMs / 2));
|
||||
result[time[2]] = new Date(fromJulian(Jset) + (dayMs / 2));
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
||||
|
||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
||||
|
||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
||||
|
||||
l = L + rad * 6.289 * sin(M), // longitude
|
||||
b = rad * 5.128 * sin(F), // latitude
|
||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
||||
|
||||
return {
|
||||
ra: rightAscension(l, b),
|
||||
dec: declination(l, b),
|
||||
dist: dt
|
||||
};
|
||||
}
|
||||
|
||||
SunCalc.getMoonPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = moonCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra,
|
||||
h = altitude(H, phi, c.dec),
|
||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
||||
|
||||
h = h + astroRefraction(h); // altitude correction for refraction
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: h,
|
||||
distance: c.dist,
|
||||
parallacticAngle: pa
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// calculations for illumination parameters of the moon,
|
||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
|
||||
// Function updated from gist: https://gist.github.com/endel/dfe6bb2fbe679781948c
|
||||
|
||||
SunCalc.getMoonIllumination = function (date) {
|
||||
let month = date.getMonth();
|
||||
let year = date.getFullYear();
|
||||
let day = date.getDate();
|
||||
|
||||
let c = 0;
|
||||
let e = 0;
|
||||
let jd = 0;
|
||||
let b = 0;
|
||||
|
||||
if (month < 3) {
|
||||
year--;
|
||||
month += 12;
|
||||
}
|
||||
|
||||
++month;
|
||||
c = 365.25 * year;
|
||||
e = 30.6 * month;
|
||||
jd = c + e + day - 694039.09; // jd is total days elapsed
|
||||
jd /= 29.5305882; // divide by the moon cycle
|
||||
b = parseInt(jd); // int(jd) -> b, take integer part of jd
|
||||
jd -= b; // subtract integer part to leave fractional part of original jd
|
||||
b = Math.round(jd * 8); // scale fraction from 0-8 and round
|
||||
|
||||
if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0
|
||||
|
||||
return {phase: b};
|
||||
};
|
||||
|
||||
|
||||
function hoursLater(date, h) {
|
||||
return new Date(date.valueOf() + h * dayMs / 24);
|
||||
}
|
||||
|
||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
||||
|
||||
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
|
||||
var t = date;
|
||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
||||
else t.setHours(0, 0, 0, 0);
|
||||
|
||||
var hc = 0.133 * rad,
|
||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
||||
|
||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
||||
for (var i = 1; i <= 24; i += 2) {
|
||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
||||
|
||||
a = (h0 + h2) / 2 - h1;
|
||||
b = (h2 - h0) / 2;
|
||||
xe = -b / (2 * a);
|
||||
ye = (a * xe + b) * xe + h1;
|
||||
d = b * b - 4 * a * h1;
|
||||
roots = 0;
|
||||
|
||||
if (d >= 0) {
|
||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
||||
x1 = xe - dx;
|
||||
x2 = xe + dx;
|
||||
if (Math.abs(x1) <= 1) roots++;
|
||||
if (Math.abs(x2) <= 1) roots++;
|
||||
if (x1 < -1) x1 = x2;
|
||||
}
|
||||
|
||||
if (roots === 1) {
|
||||
if (h0 < 0) rise = i + x1;
|
||||
else set = i + x1;
|
||||
|
||||
} else if (roots === 2) {
|
||||
rise = i + (ye < 0 ? x2 : x1);
|
||||
set = i + (ye < 0 ? x1 : x2);
|
||||
}
|
||||
|
||||
if (rise && set) break;
|
||||
|
||||
h0 = h2;
|
||||
}
|
||||
|
||||
var result = {};
|
||||
|
||||
if (rise) result.rise = hoursLater(t, rise);
|
||||
if (set) result.set = hoursLater(t, set);
|
||||
|
||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// export as Node module / AMD module / browser variable
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
|
||||
else if (typeof define === 'function' && define.amd) define(SunCalc);
|
||||
else global.SunCalc = SunCalc;
|
||||
|
||||
}());
|
|
@ -61,3 +61,6 @@
|
|||
0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass
|
||||
0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock
|
||||
0.54: Fix for invalid version comparison in polyfill
|
||||
0.55: Add toLocalISOString polyfill for pre-2v15 firmwares
|
||||
Only add boot info comments if settings.bootDebug was set
|
||||
If settings.bootDebug is set, output timing for each section of .boot0
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
/* This rewrites boot0.js based on current settings. If settings changed then it
|
||||
recalculates, but this avoids us doing a whole bunch of reconfiguration most
|
||||
of the time. */
|
||||
{ // execute in our own scope so we don't have to free variables...
|
||||
E.showMessage(/*LANG*/"Updating boot0...");
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||
var FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1"));
|
||||
var boot = "", bootPost = "";
|
||||
let s = require('Storage').readJSON('setting.json',1)||{};
|
||||
const BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||
const FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1"));
|
||||
const DEBUG = s.bootDebug; // we can set this to enable debugging output in boot0
|
||||
let boot = "", bootPost = "";
|
||||
if (DEBUG) {
|
||||
boot += "var _tm=Date.now()\n";
|
||||
bootPost += "delete _tm;";
|
||||
}
|
||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
} else {
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
|
||||
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
|
||||
}
|
||||
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
|
||||
|
@ -88,14 +94,25 @@ delete Bangle.showClock;
|
|||
if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`;
|
||||
delete Bangle.load;
|
||||
if (!Bangle.load) boot += `Bangle.load = load;\n`;
|
||||
let date = new Date();
|
||||
delete date.toLocalISOString; // toLocalISOString was only introduced in 2v15
|
||||
if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function() {
|
||||
var o = this.getTimezoneOffset();
|
||||
var d = new Date(this.getTime() - o*60000);
|
||||
var sign = o>0?"-":"+";
|
||||
o = Math.abs(o);
|
||||
return d.toISOString().slice(0,-1)+sign+Math.floor(o/60).toString().padStart(2,0)+(o%60).toString().padStart(2,0);
|
||||
};\n`;
|
||||
|
||||
// show timings
|
||||
if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
|
||||
// ================================================== BOOT.JS
|
||||
// Append *.boot.js files
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||
var getPriority = /.*\.(\d+)\.boot\.js$/;
|
||||
var aPriority = a.match(getPriority);
|
||||
var bPriority = b.match(getPriority);
|
||||
let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
||||
let getPriority = /.*\.(\d+)\.boot\.js$/;
|
||||
let aPriority = a.match(getPriority);
|
||||
let bPriority = b.match(getPriority);
|
||||
if (aPriority && bPriority){
|
||||
return parseInt(aPriority[1]) - parseInt(bPriority[1]);
|
||||
} else if (aPriority && !bPriority){
|
||||
|
@ -106,14 +123,16 @@ var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
|
|||
return a==b ? 0 : (a>b ? 1 : -1);
|
||||
});
|
||||
// precalculate file size
|
||||
var fileSize = boot.length + bootPost.length;
|
||||
let fileSize = boot.length + bootPost.length;
|
||||
bootFiles.forEach(bootFile=>{
|
||||
// match the size of data we're adding below in bootFiles.forEach
|
||||
fileSize += 2+bootFile.length+1+require('Storage').read(bootFile).length+2;
|
||||
if (DEBUG) fileSize += 2+bootFile.length+1; // `//${bootFile}\n` comment
|
||||
fileSize += require('Storage').read(bootFile).length+2; // boot code plus ";\n"
|
||||
if (DEBUG) fileSize += 48+E.toJS(bootFile).length; // `print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
|
||||
});
|
||||
// write file in chunks (so as not to use up all RAM)
|
||||
require('Storage').write('.boot0',boot,0,fileSize);
|
||||
var fileOffset = boot.length;
|
||||
let fileOffset = boot.length;
|
||||
bootFiles.forEach(bootFile=>{
|
||||
// we add a semicolon so if the file is wrapped in (function(){ ... }()
|
||||
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
|
||||
|
@ -122,16 +141,18 @@ bootFiles.forEach(bootFile=>{
|
|||
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
|
||||
// but we need to do this without ever loading everything into RAM as some
|
||||
// boot files seem to be getting pretty big now.
|
||||
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
|
||||
if (DEBUG) {
|
||||
require('Storage').write('.boot0',`//${bootFile}\n`,fileOffset);
|
||||
fileOffset+=2+bootFile.length+1;
|
||||
var bf = require('Storage').read(bootFile);
|
||||
}
|
||||
let bf = require('Storage').read(bootFile);
|
||||
// we can't just write 'bf' in one go because at least in 2v13 and earlier
|
||||
// Espruino wants to read the whole file into RAM first, and on Bangle.js 1
|
||||
// it can be too big (especially BTHRM).
|
||||
var bflen = bf.length;
|
||||
var bfoffset = 0;
|
||||
let bflen = bf.length;
|
||||
let bfoffset = 0;
|
||||
while (bflen) {
|
||||
var bfchunk = Math.min(bflen, 2048);
|
||||
let bfchunk = Math.min(bflen, 2048);
|
||||
require('Storage').write('.boot0',bf.substr(bfoffset, bfchunk),fileOffset);
|
||||
fileOffset+=bfchunk;
|
||||
bfoffset+=bfchunk;
|
||||
|
@ -139,15 +160,14 @@ bootFiles.forEach(bootFile=>{
|
|||
}
|
||||
require('Storage').write('.boot0',";\n",fileOffset);
|
||||
fileOffset+=2;
|
||||
if (DEBUG) {
|
||||
require('Storage').write('.boot0',`print(${E.toJS(bootFile)},0|(Date.now()-_tm),"ms");_tm=Date.now();\n`,fileOffset);
|
||||
fileOffset += 48+E.toJS(bootFile).length
|
||||
}
|
||||
});
|
||||
require('Storage').write('.boot0',bootPost,fileOffset);
|
||||
|
||||
delete boot;
|
||||
delete bootPost;
|
||||
delete bootFiles;
|
||||
delete fileSize;
|
||||
delete fileOffset;
|
||||
E.showMessage(/*LANG*/"Reloading...");
|
||||
eval(require('Storage').read('.boot0'));
|
||||
}
|
||||
// .bootcde should be run automatically after if required, since
|
||||
// we normally get called automatically from '.boot0'
|
||||
eval(require('Storage').read('.boot0'));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.54",
|
||||
"version": "0.55",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -40,3 +40,4 @@
|
|||
0.16: Set powerdownRequested correctly on BTHRM power on
|
||||
Additional logging on errors
|
||||
Add debug option for disabling active scanning
|
||||
0.17: New GUI based on layout library
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
var intervalInt;
|
||||
var intervalBt;
|
||||
const BPM_FONT_SIZE="19%";
|
||||
const VALUE_TIMEOUT=3000;
|
||||
|
||||
var BODY_LOCS = {
|
||||
0: 'Other',
|
||||
|
@ -7,46 +7,119 @@ var BODY_LOCS = {
|
|||
2: 'Wrist',
|
||||
3: 'Finger',
|
||||
4: 'Hand',
|
||||
5: 'Ear Lobe',
|
||||
5: 'Earlobe',
|
||||
6: 'Foot',
|
||||
};
|
||||
|
||||
var Layout = require("Layout");
|
||||
|
||||
function border(l,c) {
|
||||
g.setColor(c).drawLine(l.x+l.w*0.05, l.y-4, l.x+l.w*0.95, l.y-4);
|
||||
}
|
||||
|
||||
function clear(y){
|
||||
g.reset();
|
||||
g.clearRect(0,y,g.getWidth(),y+75);
|
||||
}
|
||||
|
||||
function draw(y, type, event) {
|
||||
clear(y);
|
||||
var px = g.getWidth()/2;
|
||||
var str = event.bpm + "";
|
||||
g.reset();
|
||||
g.setFontAlign(0,0);
|
||||
g.setFontVector(40).drawString(str,px,y+20);
|
||||
str = "Event: " + type;
|
||||
if (type === "HRM") {
|
||||
str += " Confidence: " + event.confidence;
|
||||
g.setFontVector(12).drawString(str,px,y+40);
|
||||
str = " Source: " + (event.src ? event.src : "internal");
|
||||
g.setFontVector(12).drawString(str,px,y+50);
|
||||
function getRow(id, text, additionalInfo){
|
||||
let additional = [];
|
||||
let l = {
|
||||
type:"h", c: [
|
||||
{
|
||||
type:"v",
|
||||
width: g.getWidth()*0.4,
|
||||
c: [
|
||||
{type:"txt", halign:1, font:"8%", label:text, id:id+"text" },
|
||||
{type:"txt", halign:1, font:BPM_FONT_SIZE, label:"--", id:id, bgCol: g.theme.bg }
|
||||
]
|
||||
},{
|
||||
type:undefined, fillx:1
|
||||
},{
|
||||
type:"v",
|
||||
valign: -1,
|
||||
width: g.getWidth()*0.45,
|
||||
c: additional
|
||||
},{
|
||||
type:undefined, width:g.getWidth()*0.05
|
||||
}
|
||||
if (type === "BTHRM"){
|
||||
if (event.battery) str += " Bat: " + (event.battery ? event.battery : "");
|
||||
g.setFontVector(12).drawString(str,px,y+40);
|
||||
str= "";
|
||||
if (event.location) str += "Loc: " + BODY_LOCS[event.location];
|
||||
if (event.rr && event.rr.length > 0) str += " RR: " + event.rr.join(",");
|
||||
g.setFontVector(12).drawString(str,px,y+50);
|
||||
str= "";
|
||||
if (event.contact) str += " Contact: " + event.contact;
|
||||
if (event.energy) str += " kJoule: " + event.energy.toFixed(0);
|
||||
g.setFontVector(12).drawString(str,px,y+60);
|
||||
]
|
||||
};
|
||||
for (let i of additionalInfo){
|
||||
let label = {type:"txt", font:"6x8", label:i + ":" };
|
||||
let value = {type:"txt", font:"6x8", label:"--", id:id + i };
|
||||
additional.push({type:"h", halign:-1, c:[ label, {type:undefined, fillx:1}, value ]});
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
var layout = new Layout( {
|
||||
type:"v", c: [
|
||||
getRow("int", "INT", ["Confidence"]),
|
||||
getRow("agg", "HRM", ["Confidence", "Source"]),
|
||||
getRow("bt", "BT", ["Battery","Location","Contact", "RR", "Energy"]),
|
||||
{ type:undefined, height:8 } //dummy to protect debug output
|
||||
]
|
||||
}, {
|
||||
lazy:true
|
||||
});
|
||||
|
||||
var int,agg,bt;
|
||||
var firstEvent = true;
|
||||
|
||||
function draw(){
|
||||
if (!(int || agg || bt)) return;
|
||||
|
||||
if (firstEvent) {
|
||||
g.clearRect(Bangle.appRect);
|
||||
firstEvent = false;
|
||||
}
|
||||
|
||||
let now = Date.now();
|
||||
|
||||
if (int && int.time > (now - VALUE_TIMEOUT)){
|
||||
layout.int.label = int.bpm;
|
||||
if (!isNaN(int.confidence)) layout.intConfidence.label = int.confidence;
|
||||
} else {
|
||||
layout.int.label = "--";
|
||||
layout.intConfidence.label = "--";
|
||||
}
|
||||
|
||||
if (agg && agg.time > (now - VALUE_TIMEOUT)){
|
||||
layout.agg.label = agg.bpm;
|
||||
if (!isNaN(agg.confidence)) layout.aggConfidence.label = agg.confidence;
|
||||
if (agg.src) layout.aggSource.label = agg.src;
|
||||
} else {
|
||||
layout.agg.label = "--";
|
||||
layout.aggConfidence.label = "--";
|
||||
layout.aggSource.label = "--";
|
||||
}
|
||||
|
||||
if (bt && bt.time > (now - VALUE_TIMEOUT)) {
|
||||
layout.bt.label = bt.bpm;
|
||||
if (!isNaN(bt.battery)) layout.btBattery.label = bt.battery + "%";
|
||||
if (bt.rr) layout.btRR.label = bt.rr.join(",");
|
||||
if (!isNaN(bt.location)) layout.btLocation.label = BODY_LOCS[bt.location];
|
||||
if (bt.contact !== undefined) layout.btContact.label = bt.contact ? "Yes":"No";
|
||||
if (!isNaN(bt.energy)) layout.btEnergy.label = bt.energy.toFixed(0) + "kJ";
|
||||
} else {
|
||||
layout.bt.label = "--";
|
||||
layout.btBattery.label = "--";
|
||||
layout.btRR.label = "--";
|
||||
layout.btLocation.label = "--";
|
||||
layout.btContact.label = "--";
|
||||
layout.btEnergy.label = "--";
|
||||
}
|
||||
|
||||
layout.update();
|
||||
layout.render();
|
||||
let first = true;
|
||||
for (let c of layout.l.c){
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (c.type && c.type == "h")
|
||||
border(c,g.theme.fg);
|
||||
}
|
||||
}
|
||||
|
||||
var firstEventBt = true;
|
||||
var firstEventInt = true;
|
||||
|
||||
|
||||
// This can get called for the boot code to show what's happening
|
||||
function showStatusInfo(txt) {
|
||||
|
@ -57,41 +130,26 @@ function showStatusInfo(txt) {
|
|||
}
|
||||
|
||||
function onBtHrm(e) {
|
||||
if (firstEventBt){
|
||||
clear(24);
|
||||
firstEventBt = false;
|
||||
}
|
||||
draw(100, "BTHRM", e);
|
||||
if (e.bpm === 0){
|
||||
Bangle.buzz(100,0.2);
|
||||
}
|
||||
if (intervalBt){
|
||||
clearInterval(intervalBt);
|
||||
}
|
||||
intervalBt = setInterval(()=>{
|
||||
clear(100);
|
||||
}, 2000);
|
||||
bt = e;
|
||||
bt.time = Date.now();
|
||||
}
|
||||
|
||||
function onHrm(e) {
|
||||
if (firstEventInt){
|
||||
clear(24);
|
||||
firstEventInt = false;
|
||||
}
|
||||
draw(24, "HRM", e);
|
||||
if (intervalInt){
|
||||
clearInterval(intervalInt);
|
||||
}
|
||||
intervalInt = setInterval(()=>{
|
||||
clear(24);
|
||||
}, 2000);
|
||||
function onInt(e) {
|
||||
int = e;
|
||||
int.time = Date.now();
|
||||
}
|
||||
|
||||
function onAgg(e) {
|
||||
agg = e;
|
||||
agg.time = Date.now();
|
||||
}
|
||||
|
||||
var settings = require('Storage').readJSON("bthrm.json", true) || {};
|
||||
|
||||
Bangle.on('BTHRM', onBtHrm);
|
||||
Bangle.on('HRM', onHrm);
|
||||
Bangle.on('HRM_int', onInt);
|
||||
Bangle.on('HRM', onAgg);
|
||||
|
||||
|
||||
Bangle.setHRMPower(1,'bthrm');
|
||||
if (!(settings.startWithHrm)){
|
||||
|
@ -103,10 +161,11 @@ Bangle.loadWidgets();
|
|||
Bangle.drawWidgets();
|
||||
if (Bangle.setBTHRMPower){
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 24);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2);
|
||||
setInterval(draw, 1000);
|
||||
} else {
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2 + 32);
|
||||
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2);
|
||||
}
|
||||
|
||||
E.on('kill', ()=>Bangle.setBTHRMPower(0,'bthrm'));
|
||||
|
|
|
@ -553,14 +553,15 @@ exports.enable = () => {
|
|||
|
||||
if (settings.replace){
|
||||
// register a listener for original HRM events and emit as HRM_int
|
||||
Bangle.on("HRM", (e) => {
|
||||
e.modified = true;
|
||||
Bangle.on("HRM", (o) => {
|
||||
let e = Object.assign({},o);
|
||||
log("Emitting HRM_int", e);
|
||||
Bangle.emit("HRM_int", e);
|
||||
if (fallbackActive){
|
||||
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens
|
||||
log("Emitting HRM_R(int)", e);
|
||||
Bangle.emit("HRM_R", e);
|
||||
o.src = "int";
|
||||
log("Emitting HRM_R(int)", o);
|
||||
Bangle.emit("HRM_R", o);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -576,6 +577,13 @@ exports.enable = () => {
|
|||
if (name == "HRM") o("HRM_R", cb);
|
||||
else o(name, cb);
|
||||
})(Bangle.removeListener);
|
||||
} else {
|
||||
Bangle.on("HRM", (o)=>{
|
||||
o.src = "int";
|
||||
let e = Object.assign({},o);
|
||||
log("Emitting HRM_int", e);
|
||||
Bangle.emit("HRM_int", e);
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.16",
|
||||
"version": "0.17",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screen.png"}],
|
||||
"type": "app",
|
||||
"tags": "health,bluetooth,hrm,bthrm",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
|
|
After Width: | Height: | Size: 3.6 KiB |
|
@ -32,3 +32,9 @@
|
|||
Use widget_utils if available
|
||||
0.17: Load circles from clkinfo
|
||||
0.18: Improved clkinfo handling and using it for the weather circle
|
||||
0.19: Remove old code and fixing clkinfo handling (fix HRM and other items that change)
|
||||
Remove settings for what is displayed and instead allow circles to be changed by swiping
|
||||
0.20: Add much faster circle rendering (250ms -> 40ms)
|
||||
Add fast load capability
|
||||
0.21: Remade all icons without a palette for dark theme
|
||||
Now re-adds widgets if they were hidden when fast-loading
|
||||
|
|
|
@ -5,6 +5,7 @@ A clock with three or four circles for different data at the bottom in a probabl
|
|||
By default the time, date and day of week is shown.
|
||||
|
||||
It can show the following information (this can be configured):
|
||||
|
||||
* Steps
|
||||
* Steps distance
|
||||
* Heart rate (automatically updates when screen is on and unlocked)
|
||||
|
@ -14,15 +15,24 @@ It can show the following information (this can be configured):
|
|||
* Temperature inside circle
|
||||
* Condition as icon below circle
|
||||
* Big weather icon next to clock
|
||||
* Time and progress until next sunrise or sunset (requires [my location app](https://banglejs.com/apps/#mylocation))
|
||||
* Temperature, air pressure or altitude from internal pressure sensor
|
||||
* Altitude from internal pressure sensor
|
||||
* Active alarms (if `Alarm` app installed)
|
||||
* Sunrise or sunset (if `Sunrise Clockinfo` app installed)
|
||||
|
||||
To change what is shown:
|
||||
|
||||
The color of each circle can be configured. The following colors are available:
|
||||
* Unlock the watch
|
||||
* Tap on the circle to change (a border is drawn around it)
|
||||
* Swipe up/down to change the guage within the given group
|
||||
* Swipe left/right to change the group (eg. between standard Bangle.js and Alarms/etc)
|
||||
|
||||
Data is provided by ['Clock Info'](http://www.espruino.com/Bangle.js+Clock+Info)
|
||||
so any apps that implement this feature can add extra information to be displayed.
|
||||
|
||||
The color of each circle can be configured from `Settings -> Apps -> Circles Clock`. The following colors are available:
|
||||
* Basic colors (red, green, blue, yellow, magenta, cyan, black, white)
|
||||
* Color depending on value (green -> red, red -> green)
|
||||
|
||||
|
||||
## Screenshots
|
||||

|
||||

|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
let clock_info = require("clock_info");
|
||||
let locale = require("locale");
|
||||
let storage = require("Storage");
|
||||
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
|
||||
// Actual height 39 (40 - 2)
|
||||
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAAfAAAAAAAPwAAAAAAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAA4AAAAAAB+AAAAAAD/gAAAAAD/4AAAAAH/4AAAAAP/wAAAAAP/gAAAAAf/gAAAAAf/AAAAAA/+AAAAAB/+AAAAAB/8AAAAAD/4AAAAAH/4AAAAAD/wAAAAAA/wAAAAAAPgAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///wAAAB////gAAA////8AAA/////gAAP////8AAH8AAA/gAB8AAAD4AA+AAAAfAAPAAAADwADwAAAA8AA8AAAAPAAPAAAADwADwAAAA8AA8AAAAPAAPgAAAHwAB8AAAD4AAfwAAD+AAD/////AAA/////wAAH////4AAAf///4AAAB///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAPgAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPAAAAAAAH/////wAB/////8AA//////AAP/////wAD/////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAfgAADwAAP4AAB8AAH+AAA/AAD/gAAfwAB/AAAf8AAfAAAP/AAPgAAH7wAD4AAD88AA8AAB+PAAPAAA/DwADwAAfg8AA8AAPwPAAPAAH4DwADwAH8A8AA+AD+APAAPwB/ADwAB/D/gA8AAf//gAPAAD//wADwAAf/wAA8AAD/4AAPAAAHwAADwAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAADgAAAHwAA+AAAD8AAP4AAB/AAD/AAA/wAA/wAAf4AAD+AAHwAAAPgAD4APAB8AA+ADwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA8AH4APAAPgD+AHwAB8B/wD4AAf7/+B+AAD//v//AAA//x//wAAD/4P/4AAAf8B/4AAAAYAH4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAHwAAAAAAH8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/vAAAAAB/jwAAAAA/g8AAAAA/wPAAAAAfwDwAAAAf4A8AAAAf4APAAAAP8ADwAAAP8AA8AAAH8AAPAAAD/////8AA//////AAP/////wAD/////8AA//////AAAAAAPAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAB/APwAAH//wD+AAD//8A/wAA///AH+AAP//wAPgAD/B4AB8AA8A+AAfAAPAPAADwADwDwAA8AA8A8AAPAAPAPAADwADwD4AA8AA8A+AAPAAPAPwAHwADwD8AD4AA8AfwD+AAPAH///AADwA///wAA8AH//4AAPAAf/4AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//AAAAAD//+AAAAD///4AAAD////AAAB////4AAA/78D/AAAfw8AH4AAPweAA+AAD4PgAHwAB8DwAA8AAfA8AAPAAHgPAADwAD4DwAA8AA+A8AAPAAPAPgAHwADwD4AB8AA8AfgA+AAPAH+B/gAAAA///wAAAAH//4AAAAA//8AAAAAH/8AAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAA8AAAAAAAPAAAAAAADwAAAAAAA8AAAABAAPAAAABwADwAAAB8AA8AAAB/AAPAAAB/wADwAAD/8AA8AAD/8AAPAAD/4AADwAD/4AAA8AD/4AAAPAH/wAAADwH/wAAAA8H/wAAAAPH/wAAAAD3/gAAAAA//gAAAAAP/gAAAAAD/gAAAAAA/AAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwA/4AAAH/Af/AAAH/8P/4AAD//n//AAA//7//4AAfx/+A+AAHwD+AHwAD4AfgB8AA8AHwAPAAPAA8ADwADwAPAA8AA8ADwAPAAPAA8ADwADwAfAA8AA+AH4AfAAHwD+AHwAB/D/4D4AAP/+/n+AAD//n//AAAf/w//gAAB/wH/wAAAHwA/4AAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/8AAAAAD//wAAAAB//+AAAAA///wAAAAf4H+APAAH4AfgDwAD8AB8A8AA+AAfAPAAPAADwDwADwAA8B8AA8AAPAfAAPAADwHgADwAA8D4AA+AAeB+AAHwAHg/AAB+ADwfgAAP8D4/4AAD////8AAAf///8AAAB///+AAAAP//+AAAAAP/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAOAAAB8AAHwAAAfgAD8AAAH4AA/AAAB8AAHwAAAOAAA4AAAAAAAAAAAAAAAAAAAAAAAAAA"), 46, atob("DRUcHBwcHBwcHBwcDA=="), 50+(scale<<8)+(1<<16));
|
||||
|
@ -13,12 +10,16 @@ Graphics.prototype.setFontRobotoRegular21 = function(scale) {
|
|||
return this;
|
||||
};
|
||||
|
||||
{
|
||||
let clock_info = require("clock_info");
|
||||
let locale = require("locale");
|
||||
let storage = require("Storage");
|
||||
|
||||
let SETTINGS_FILE = "circlesclock.json";
|
||||
let settings = Object.assign(
|
||||
storage.readJSON("circlesclock.default.json", true) || {},
|
||||
storage.readJSON(SETTINGS_FILE, true) || {}
|
||||
);
|
||||
|
||||
//TODO deprecate this (and perhaps use in the clkinfo module)
|
||||
// Load step goal from health app and pedometer widget as fallback
|
||||
if (settings.stepGoal == undefined) {
|
||||
|
@ -31,20 +32,10 @@ if (settings.stepGoal == undefined) {
|
|||
}
|
||||
}
|
||||
|
||||
let timerHrm; //TODO deprecate this
|
||||
let drawTimeout;
|
||||
|
||||
/*
|
||||
* Read location from myLocation app
|
||||
*/
|
||||
function getLocation() {
|
||||
return storage.readJSON("mylocation.json", 1) || undefined;
|
||||
}
|
||||
let location = getLocation();
|
||||
|
||||
let showWidgets = settings.showWidgets || false;
|
||||
let circleCount = settings.circleCount || 3;
|
||||
let showBigWeather = settings.showBigWeather || false;
|
||||
const showWidgets = settings.showWidgets || false;
|
||||
const circleCount = settings.circleCount || 3;
|
||||
const showBigWeather = settings.showBigWeather || false;
|
||||
|
||||
let hrtValue; //TODO deprecate this
|
||||
let now = Math.round(new Date().getTime() / 1000);
|
||||
|
@ -52,11 +43,6 @@ let now = Math.round(new Date().getTime() / 1000);
|
|||
// layout values:
|
||||
let colorFg = g.theme.dark ? '#fff' : '#000';
|
||||
let colorBg = g.theme.dark ? '#000' : '#fff';
|
||||
let colorGrey = '#808080';
|
||||
let colorRed = '#ff0000';
|
||||
let colorGreen = '#008000';
|
||||
let colorBlue = '#0000ff';
|
||||
let colorYellow = '#ffff00';
|
||||
let widgetOffset = showWidgets ? 24 : 0;
|
||||
let dowOffset = circleCount == 3 ? 20 : 22; // dow offset relative to date
|
||||
let h = g.getHeight() - widgetOffset;
|
||||
|
@ -64,7 +50,7 @@ let w = g.getWidth();
|
|||
let hOffset = (circleCount == 3 ? 34 : 30) - widgetOffset;
|
||||
let h1 = Math.round(1 * h / 5 - hOffset);
|
||||
let h2 = Math.round(3 * h / 5 - hOffset);
|
||||
let h3 = Math.round(8 * h / 8 - hOffset - 3); // circle y position
|
||||
let h3 = Math.round(8 * h / 8 - hOffset - 3); // circle middle y position
|
||||
|
||||
/*
|
||||
* circle x positions
|
||||
|
@ -87,58 +73,17 @@ let circlePosX = [
|
|||
];
|
||||
|
||||
let radiusOuter = circleCount == 3 ? 25 : 20;
|
||||
let radiusBorder = radiusOuter+3; // absolute border of circles
|
||||
let radiusInner = circleCount == 3 ? 20 : 15;
|
||||
let circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10";
|
||||
let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11";
|
||||
let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12";
|
||||
let iconOffset = circleCount == 3 ? 6 : 8;
|
||||
let defaultCircleTypes = ["Bangle/Steps", "Bangle/HRM", "Bangle/Battery", "weather"];
|
||||
|
||||
let circleInfoNum = [
|
||||
0, // circle1
|
||||
0, // circle2
|
||||
0, // circle3
|
||||
0, // circle4
|
||||
];
|
||||
let circleItemNum = [
|
||||
0, // circle1
|
||||
1, // circle2
|
||||
2, // circle3
|
||||
3, // circle4
|
||||
];
|
||||
let weatherCircleNum = 0;
|
||||
let weatherCircleDataNum = 0;
|
||||
let weatherCircleCondNum = 0;
|
||||
let weatherCircleTempNum = 0;
|
||||
|
||||
function hideWidgets() {
|
||||
/*
|
||||
* we are not drawing the widgets as we are taking over the whole screen
|
||||
* so we will blank out the draw() functions of each widget and change the
|
||||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
if (WIDGETS && typeof WIDGETS === "object") {
|
||||
for (let wd of WIDGETS) {
|
||||
wd.draw = () => {};
|
||||
wd.area = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
g.clear(true);
|
||||
let widgetUtils;
|
||||
|
||||
try {
|
||||
widgetUtils = require("widget_utils");
|
||||
} catch (e) {
|
||||
}
|
||||
if (!showWidgets) {
|
||||
if (widgetUtils) widgetUtils.hide(); else hideWidgets();
|
||||
} else {
|
||||
if (widgetUtils) widgetUtils.show();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
let draw = function() {
|
||||
let R = Bangle.appRect;
|
||||
g.reset().clearRect(R.x,R.y, R.x2, h3-(radiusBorder+1));
|
||||
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(0, widgetOffset, w, h2 + 22);
|
||||
|
@ -180,118 +125,16 @@ function draw() {
|
|||
if (icon) g.drawImage(icon, w - 48, h1, {scale:0.75});
|
||||
}
|
||||
|
||||
drawCircle(1);
|
||||
drawCircle(2);
|
||||
drawCircle(3);
|
||||
if (circleCount >= 4) drawCircle(4);
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
function drawCircle(index) {
|
||||
let type = settings['circle' + index];
|
||||
if (!type) type = defaultCircleTypes[index - 1];
|
||||
let w = getCircleXPosition(type);
|
||||
|
||||
switch (type) {
|
||||
case "weather":
|
||||
drawWeather(w);
|
||||
break;
|
||||
case "sunprogress":
|
||||
case "sunProgress":
|
||||
drawSunProgress(w);
|
||||
break;
|
||||
//TODO those are going to be deprecated, keep for backwards compatibility for now
|
||||
//ideally all data should come from some clkinfo
|
||||
case "steps":
|
||||
drawSteps(w);
|
||||
break;
|
||||
case "stepsDist":
|
||||
drawStepsDistance(w);
|
||||
break;
|
||||
case "hr":
|
||||
drawHeartRate(w);
|
||||
break;
|
||||
case "battery":
|
||||
drawBattery(w);
|
||||
break;
|
||||
case "temperature":
|
||||
drawTemperature(w);
|
||||
break;
|
||||
case "pressure":
|
||||
drawPressure(w);
|
||||
break;
|
||||
case "altitude":
|
||||
drawAltitude(w);
|
||||
break;
|
||||
//end deprecated
|
||||
case "empty":
|
||||
// we draw nothing here
|
||||
return;
|
||||
default:
|
||||
drawClkInfo(index, w);
|
||||
}
|
||||
}
|
||||
|
||||
// serves as cache for quicker lookup of circle positions
|
||||
let circlePositionsCache = [];
|
||||
/*
|
||||
* Looks in the following order if a circle with the given type is somewhere visible/configured
|
||||
* 1. circlePositionsCache
|
||||
* 2. settings
|
||||
* 3. defaultCircleTypes
|
||||
*
|
||||
* In case 2 and 3 the circlePositionsCache will be updated
|
||||
*/
|
||||
function getCirclePosition(type) {
|
||||
if (circlePositionsCache[type] >= 0) {
|
||||
return circlePositionsCache[type];
|
||||
}
|
||||
for (let i = 1; i <= circleCount; i++) {
|
||||
let setting = settings['circle' + i];
|
||||
if (setting == type) {
|
||||
circlePositionsCache[type] = i - 1;
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < defaultCircleTypes.length; i++) {
|
||||
if (type == defaultCircleTypes[i] && (!settings || settings['circle' + (i + 1)] == undefined)) {
|
||||
circlePositionsCache[type] = i;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getCircleXPosition(type) {
|
||||
let circlePos = getCirclePosition(type);
|
||||
if (circlePos != undefined) {
|
||||
return circlePosX[circlePos];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isCircleEnabled(type) {
|
||||
return getCirclePosition(type) != undefined;
|
||||
}
|
||||
|
||||
function getCircleColor(type) {
|
||||
let pos = getCirclePosition(type);
|
||||
let color = settings["circle" + (pos + 1) + "color"];
|
||||
let getCircleColor = function(index) {
|
||||
let color = settings["circle" + index + "color"];
|
||||
if (color && color != "") return color;
|
||||
return g.theme.fg;
|
||||
}
|
||||
|
||||
function getCircleIconColor(type, color, percent) {
|
||||
let pos = getCirclePosition(type);
|
||||
let colorizeIcon = settings["circle" + (pos + 1) + "colorizeIcon"] == true;
|
||||
if (colorizeIcon) {
|
||||
return getGradientColor(color, percent);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getGradientColor(color, percent) {
|
||||
let getGradientColor = function(color, percent) {
|
||||
if (isNaN(percent)) percent = 0;
|
||||
if (percent > 1) percent = 1;
|
||||
let colorList = [
|
||||
|
@ -311,423 +154,16 @@ function getGradientColor(color, percent) {
|
|||
return color;
|
||||
}
|
||||
|
||||
function getImage(graphic, color) {
|
||||
if (!color || color == "") {
|
||||
return graphic;
|
||||
let getCircleIconColor = function(index, color, percent) {
|
||||
let colorizeIcon = settings["circle" + index + "colorizeIcon"] == true;
|
||||
if (colorizeIcon) {
|
||||
return getGradientColor(color, percent);
|
||||
} else {
|
||||
return {
|
||||
width: 16,
|
||||
height: 16,
|
||||
bpp: 1,
|
||||
transparent: 0,
|
||||
buffer: E.toArrayBuffer(graphic),
|
||||
palette: new Uint16Array([colorBg, g.toColor(color)])
|
||||
};
|
||||
return g.theme.fg;
|
||||
}
|
||||
}
|
||||
|
||||
function drawWeather(w) {
|
||||
if (!w) w = getCircleXPosition("weather");
|
||||
let weatherInfo = menu[weatherCircleNum];
|
||||
let weatherCond = weatherCircleCondNum >= 0? weatherInfo.items[weatherCircleCondNum]: undefined;
|
||||
let weatherData = weatherCircleDataNum >= 0? weatherInfo.items[weatherCircleDataNum]: undefined;
|
||||
let weatherTemp = weatherCircleTempNum >= 0? weatherInfo.items[weatherCircleTempNum]: undefined;
|
||||
let color = getCircleColor("weather");
|
||||
let percent = 0;
|
||||
let data = settings.weatherCircleData;
|
||||
let tempString = "?", icon = undefined;
|
||||
let scale = 16/24; //our icons are 16x16 while clkinfo's are 24x24
|
||||
|
||||
if(weatherCond) {
|
||||
weatherCond.show()
|
||||
weatherCond.hide()
|
||||
let data = weatherCond.get()
|
||||
if(settings.legacyWeatherIcons) { //may disappear in future
|
||||
icon = getWeatherIconByCode(data.v);
|
||||
scale = 1;
|
||||
} else
|
||||
icon = data.img;
|
||||
}
|
||||
if(weatherTemp) {
|
||||
weatherTemp.show()
|
||||
weatherTemp.hide()
|
||||
tempString = weatherTemp.get().text;
|
||||
}
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
if(weatherData) {
|
||||
weatherData.show();
|
||||
weatherData.hide();
|
||||
let data = weatherData.get();
|
||||
if(weatherData.hasRange) percent = (data.v-data.min) / (data.max-data.min);
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
writeCircleText(w, tempString);
|
||||
|
||||
if(icon) {
|
||||
g.setColor(getCircleIconColor("weather", color, percent))
|
||||
.drawImage(icon, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: scale});
|
||||
} else {
|
||||
g.drawString("?", w, h3 + radiusOuter);
|
||||
}
|
||||
}
|
||||
function drawWeatherOld(w) {
|
||||
if (!w) w = getCircleXPosition("weather");
|
||||
let weather = getWeather();
|
||||
let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
||||
let code = weather ? weather.code : -1;
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("weather");
|
||||
let percent;
|
||||
let data = settings.weatherCircleData;
|
||||
switch (data) {
|
||||
case "humidity":
|
||||
let humidity = weather ? weather.hum : undefined;
|
||||
if (humidity >= 0) {
|
||||
percent = humidity / 100;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
break;
|
||||
case "wind":
|
||||
if (weather) {
|
||||
let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
if (wind[1] >= 0) {
|
||||
if (wind[2] == "kmh") {
|
||||
wind[1] = windAsBeaufort(wind[1]);
|
||||
}
|
||||
// wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
||||
percent = wind[1] / 12;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "empty":
|
||||
break;
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
writeCircleText(w, tempString ? tempString : "?");
|
||||
|
||||
if (code > 0) {
|
||||
let icon = getWeatherIconByCode(code);
|
||||
if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
} else {
|
||||
g.drawString("?", w, h3 + radiusOuter);
|
||||
}
|
||||
}
|
||||
|
||||
function drawSunProgress(w) {
|
||||
if (!w) w = getCircleXPosition("sunprogress");
|
||||
let percent = getSunProgress();
|
||||
|
||||
// sunset icons:
|
||||
let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
|
||||
let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("sunprogress");
|
||||
|
||||
drawGauge(w, h3, percent, color);
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
let icon = sunSetDown;
|
||||
let text = "?";
|
||||
let times = getSunData();
|
||||
if (times != undefined) {
|
||||
let sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||
let sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||
if (!isDay()) {
|
||||
// night
|
||||
if (now > sunRise) {
|
||||
// after sunRise
|
||||
let upcomingSunRise = sunRise + 60 * 60 * 24;
|
||||
text = formatSeconds(upcomingSunRise - now);
|
||||
} else {
|
||||
text = formatSeconds(sunRise - now);
|
||||
}
|
||||
icon = sunSetUp;
|
||||
} else {
|
||||
// day, approx sunrise tomorrow:
|
||||
text = formatSeconds(sunSet - now);
|
||||
icon = sunSetDown;
|
||||
}
|
||||
}
|
||||
|
||||
writeCircleText(w, text);
|
||||
|
||||
g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
}
|
||||
|
||||
/*
|
||||
* Deprecated but nice as references for clkinfo
|
||||
*/
|
||||
|
||||
function drawSteps(w) {
|
||||
if (!w) w = getCircleXPosition("steps");
|
||||
let steps = getSteps();
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("steps");
|
||||
|
||||
let percent;
|
||||
let stepGoal = settings.stepGoal;
|
||||
if (stepGoal > 0) {
|
||||
percent = steps / stepGoal;
|
||||
if (stepGoal < steps) percent = 1;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
writeCircleText(w, shortValue(steps));
|
||||
|
||||
g.drawImage(getImage(atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"), getCircleIconColor("steps", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
}
|
||||
|
||||
function drawStepsDistance(w) {
|
||||
if (!w) w = getCircleXPosition("stepsDistance");
|
||||
let steps = getSteps();
|
||||
let stepDistance = settings.stepLength;
|
||||
let stepsDistance = Math.round(steps * stepDistance);
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("stepsDistance");
|
||||
|
||||
let percent;
|
||||
let stepDistanceGoal = settings.stepDistanceGoal;
|
||||
if (stepDistanceGoal > 0) {
|
||||
percent = stepsDistance / stepDistanceGoal;
|
||||
if (stepDistanceGoal < stepsDistance) percent = 1;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
writeCircleText(w, shortValue(stepsDistance));
|
||||
|
||||
g.drawImage(getImage(atob("EBCBAAAACAAcAB4AHgAeABwwADgGeAZ4AHgAMAAAAHAAIAAA"), getCircleIconColor("stepsDistance", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
}
|
||||
|
||||
function drawHeartRate(w) {
|
||||
if (!w) w = getCircleXPosition("hr");
|
||||
|
||||
let heartIcon = atob("EBCBAAAAAAAeeD/8P/x//n/+P/w//B/4D/AH4APAAYAAAAAA");
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("hr");
|
||||
|
||||
let percent;
|
||||
if (hrtValue != undefined) {
|
||||
let minHR = settings.minHR;
|
||||
let maxHR = settings.maxHR;
|
||||
percent = (hrtValue - minHR) / (maxHR - minHR);
|
||||
if (isNaN(percent)) percent = 0;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
writeCircleText(w, hrtValue != undefined ? hrtValue : "-");
|
||||
|
||||
g.drawImage(getImage(heartIcon, getCircleIconColor("hr", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
}
|
||||
|
||||
function drawBattery(w) {
|
||||
if (!w) w = getCircleXPosition("battery");
|
||||
let battery = E.getBattery();
|
||||
|
||||
let powerIcon = atob("EBCBAAAAA8ADwA/wD/AP8A/wD/AP8A/wD/AP8A/wD/AH4AAA");
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("battery");
|
||||
|
||||
let percent;
|
||||
if (battery > 0) {
|
||||
percent = battery / 100;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
if (Bangle.isCharging()) {
|
||||
color = colorGreen;
|
||||
} else {
|
||||
if (settings.batteryWarn != undefined && battery <= settings.batteryWarn) {
|
||||
color = colorRed;
|
||||
}
|
||||
}
|
||||
writeCircleText(w, battery + '%');
|
||||
|
||||
g.drawImage(getImage(powerIcon, getCircleIconColor("battery", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
}
|
||||
function drawTemperature(w) {
|
||||
if (!w) w = getCircleXPosition("temperature");
|
||||
|
||||
getPressureValue("temperature").then((temperature) => {
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("temperature");
|
||||
|
||||
let percent;
|
||||
if (temperature) {
|
||||
let min = -40;
|
||||
let max = 85;
|
||||
percent = (temperature - min) / (max - min);
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
if (temperature)
|
||||
writeCircleText(w, locale.temp(temperature));
|
||||
|
||||
g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("temperature", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function drawPressure(w) {
|
||||
if (!w) w = getCircleXPosition("pressure");
|
||||
|
||||
getPressureValue("pressure").then((pressure) => {
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("pressure");
|
||||
|
||||
let percent;
|
||||
if (pressure && pressure > 0) {
|
||||
let minPressure = 950;
|
||||
let maxPressure = 1050;
|
||||
percent = (pressure - minPressure) / (maxPressure - minPressure);
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
if (pressure)
|
||||
writeCircleText(w, Math.round(pressure));
|
||||
|
||||
g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("pressure", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function drawAltitude(w) {
|
||||
if (!w) w = getCircleXPosition("altitude");
|
||||
|
||||
getPressureValue("altitude").then((altitude) => {
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("altitude");
|
||||
|
||||
let percent;
|
||||
if (altitude) {
|
||||
let min = 0;
|
||||
let max = 10000;
|
||||
percent = (altitude - min) / (max - min);
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
if (altitude)
|
||||
writeCircleText(w, locale.distance(Math.round(altitude)));
|
||||
|
||||
g.drawImage(getImage(atob("EBCBAAAAAYADwAJAAkADwAPAA8ADwAfgB+AH4AfgA8ABgAAA"), getCircleIconColor("altitude", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function shortValue(v) {
|
||||
if (isNaN(v)) return '-';
|
||||
if (v <= 999) return v;
|
||||
if (v >= 1000 && v < 10000) {
|
||||
v = Math.floor(v / 100) * 100;
|
||||
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
||||
}
|
||||
if (v >= 10000) {
|
||||
v = Math.floor(v / 1000) * 1000;
|
||||
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
||||
}
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
if (Bangle.getHealthStatus) {
|
||||
return Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
if (WIDGETS && WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom.getSteps();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getPressureValue(type) {
|
||||
return new Promise((resolve) => {
|
||||
if (Bangle.getPressure) {
|
||||
if (!pressureLocked) {
|
||||
pressureLocked = true;
|
||||
if (pressureCache && pressureCache[type]) {
|
||||
resolve(pressureCache[type]);
|
||||
}
|
||||
Bangle.getPressure().then(function(d) {
|
||||
pressureLocked = false;
|
||||
if (d) {
|
||||
pressureCache = d;
|
||||
if (d[type]) {
|
||||
resolve(d[type]);
|
||||
}
|
||||
}
|
||||
}).catch(() => {});
|
||||
} else {
|
||||
if (pressureCache && pressureCache[type]) {
|
||||
resolve(pressureCache[type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* end deprecated
|
||||
*/
|
||||
|
||||
var menu = null;
|
||||
function reloadMenu() {
|
||||
menu = clock_info.load();
|
||||
for(var i=1; i<5; i++)
|
||||
if(settings['circle'+i].includes("/")) {
|
||||
let parts = settings['circle'+i].split("/");
|
||||
let infoName = parts[0], itemName = parts[1];
|
||||
let infoNum = menu.findIndex(e=>e.name==infoName);
|
||||
let itemNum = 0; //get first if dynamic
|
||||
if(!menu[infoNum].dynamic)
|
||||
itemNum = menu[infoNum].items.findIndex(it=>it.name==itemName);
|
||||
circleInfoNum[i-1] = infoNum;
|
||||
circleItemNum[i-1] = itemNum;
|
||||
} else if(settings['circle'+i] == "weather") {
|
||||
weatherCircleNum = menu.findIndex(e=>e.name.toLowerCase() == "weather");
|
||||
weatherCircleDataNum = menu[weatherCircleNum].items.findIndex(it=>it.name==settings.weatherCircleData);
|
||||
weatherCircleCondNum = menu[weatherCircleNum].items.findIndex(it=>it.name=="condition");
|
||||
weatherCircleTempNum = menu[weatherCircleNum].items.findIndex(it=>it.name=="temperature");
|
||||
}
|
||||
}
|
||||
//reload periodically for changes?
|
||||
reloadMenu();
|
||||
|
||||
function drawEmpty(img, w, color) {
|
||||
let drawEmpty = function(img, w, color) {
|
||||
drawGauge(w, h3, 0, color);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
writeCircleText(w, "?");
|
||||
|
@ -736,65 +172,44 @@ function drawEmpty(img, w, color) {
|
|||
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
|
||||
}
|
||||
|
||||
function drawClkInfo(index, w) {
|
||||
var info = menu[circleInfoNum[index-1]];
|
||||
var type = settings['circle'+index];
|
||||
if (!w) w = getCircleXPosition(type);
|
||||
let drawCircle = function(index, item, data) {
|
||||
var w = circlePosX[index-1];
|
||||
drawCircleBackground(w);
|
||||
const color = getCircleColor(type);
|
||||
var item = info.items[circleItemNum[index-1]];
|
||||
if(!info || !item) {
|
||||
drawEmpty(info? info.img : null, w, color);
|
||||
return;
|
||||
}
|
||||
item.show();
|
||||
item.hide();
|
||||
var data=item.get();
|
||||
const color = getCircleColor(index);
|
||||
//drawEmpty(info? info.img : null, w, color);
|
||||
var img = data.img;
|
||||
var percent = 1; //fill up if no range
|
||||
var txt = data.text;
|
||||
if(!img) img = info.img;
|
||||
var txt = ""+data.text;
|
||||
if (txt.endsWith(" bpm")) txt=txt.slice(0,-4); // hack for heart rate - remove the 'bpm' text
|
||||
if(item.hasRange) percent = (data.v-data.min) / (data.max-data.min);
|
||||
if(data.short) txt = data.short;
|
||||
drawGauge(w, h3, percent, color);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
writeCircleText(w, txt);
|
||||
g.setColor(getCircleIconColor(type, color, percent))
|
||||
g.setColor(getCircleIconColor(index, color, percent))
|
||||
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
|
||||
}
|
||||
|
||||
/*
|
||||
* wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
||||
*/
|
||||
function windAsBeaufort(windInKmh) {
|
||||
let beaufort = [2, 6, 12, 20, 29, 39, 50, 62, 75, 89, 103, 118];
|
||||
let l = 0;
|
||||
while (l < beaufort.length && beaufort[l] < windInKmh) {
|
||||
l++;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Choose weather icon to display based on weather conditition code
|
||||
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
||||
*/
|
||||
function getWeatherIconByCode(code, big) {
|
||||
let getWeatherIconByCode = function(code, big) {
|
||||
let codeGroup = Math.round(code / 100);
|
||||
if (big == undefined) big = false;
|
||||
|
||||
// weather icons:
|
||||
let weatherCloudy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
|
||||
let weatherSunny = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAA//8AAA//8AD//wAAD//wAP//AAAP//AA//8AAA//8AAADwAADwAAAAAHgAAeAAAAAAeAAB4AAAAAB8AAPgAAAAADwAA8AAAAAAPgAHwAAAAAAfgB+AAAAAAD/gf8AAAAAAf///4AAAAAD7//3wAAAAAfD/8PgAAAAD4B+AfAAAAAfADwA+AAAAD4APAB8AAAAfAA8AD4AAAB4ADwAHgAAADAAPAAMAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
|
||||
let weatherMoon = big ? atob("QEDBAP//wxgAAAYAAAAPAAAAD4AAAA8AAAAPwAAADwAAAA/gAAAPAAAAB/APAP/wAAAH+A8A//AAAAf4DwD/8AAAB/wPAP/wAAAH/gAADwAAAAe+AAAPAAAAB54AAA8AAAAHngAADwAAAAePAAAAAAAAD48OAAAAAAAPDw+AAAAAAB8PD8AAAAAAHg8P4AAAAAA+DwPwAAAAAHwfAfgAAAAB+D4A/AAA8AfwfgB/8AD//+D+AD/8AP//wfgAH/4Af/8B8AAf/wB//APgAAgfgD+AA8AAAAfAH8AHwAAAA+AP8B+AAAAB4Af//4AAAAHgA///gAAAAPAA//8AAAAA8AAf/wAAAADwAAAAAAAAAPAAAAAAAAAA8AcAAAAAAADwD+AAAAAAAfAfgAAAAAAB+D4AAAAAAAB8fAAAAAAAAD54AAAAAAAAHngAAAAAAAAe8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAPeAAAAAAAAB54AAAAAAAAHnwAAAAAAAA+PgAAAAAAAHwfgAAAAAAB+A/////////wB////////+AD////////wAB///////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
|
||||
let weatherPartlyCloudy = big ? atob("QEDBAP//wxgAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAABwAPAA4AAAAHgA8AHgAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+BAAAAAP+B/wOAAAAAfgB+B8AAAAD4AD8H4AAAAPAA/wPwAAAB8AH+Af/AAAHgA/AA//AAAeAH4AB/+AADwAfAAH/8A//AD4AAIH4D/8AfAAAAHwP/wB4AAAAPg//AHgAAAAeAA8B+AAAAB4AB4fwAAAADwAHn/AAAAAPAAff8AAAAA8AA/8AAAAADwAD/AAAAAAPAEH4AAAAAA8A4PgAAAAAHwHgcAAAAAAfg+AwAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
|
||||
let weatherRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4APAA8AAfg+AA8ADwAAfHwADwAPAAA+eAAPAA8AAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AADw8PDwAP8AAPDw8PAA/wAA8PDw8AD3gADw8PDwAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP/w8PDw8P8Af/Dw8PDw/gA/8PDw8PD8AAfw8PDw8OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAPAAAAAAAPAA8AAAAAAA8ADwAAAAAADwAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
|
||||
let weatherPartlyRainy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAA8AAfg+AAAADwAAfHwAAAAPAAA+eAAAAA8AAB54AAAADwAAHvAAAAAPAAAP8AAAAA8AAA/wAAAADwAAD/AAAA8PAAAP8AAADw8AAA/wAAAPDwAAD3gAAA8PAAAeeAAADw8AAB58AAAPDwAAPj4AAA8PAAB8H4AADw8AAfgP//8PDw//8Af//w8PD//gA///Dw8P/8AAf/8PDw/+AAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
|
||||
let weatherSnowy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAADwAfg+AAAAAPAAfHwAAAAA8AA+eAAAAADwAB54AA8AD/8AHvAADwAP/wAP8AAPAA//AA/wAA8AD/8AD/AA//AA8AAP8AD/8ADwAA/wAP/wAPAAD3gA//AA8AAeeAAPAAAAAB58AA8AAAAAPj4ADwAAAAB8H4APAAAAAfgP/wAA8A//8Af/AADwD//gA/8AAPAP/8AAfwAA8A/+AAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
|
||||
let weatherFoggy = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAwADwADAAAAHgAPAAeAAAAfAA8AD4AAAA+ADwAfAAAAB8APAD4AAAAD4B+AfAAAAAHw//D4AAAAAPv//fAAAAAAf///4AAAAAA/4H/AAAAAAB+AH4AAAAAAPgAHwAAAAAA8AAPAAAAAAHwAA+AAAAAAeAAB4AAAAAB4AAHgAAAAAPAAAPAAAAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AD///AADwAAAP//8AAeAAAA///wAB4AAAD///AAPgAAAAAAAAA8AAAAAAAAAHwAAAAAAAAB+AAAAAAAAAf8AAAAD///D/4AAAAP//8P3wAAAA///w8PgAAAD///CAfAAAAAAAAAA+AAAAAAAAAB8AAAAAAAAAD4AAAAAAAAAHgAAP//8PAAMAAA///w8AAAAAD///DwAAAAAP//8PAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
|
||||
let weatherStormy = big ? atob("QEDBAP//wxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAB//gAAAAAAAP//gAAAAAAD///AAAAAAAf4H+AAAAAAD8AD8AAAAAAfgAH4AAAAAB8AAPwAAAAAPgAAf/AAAAB8AAA//AAAAHgAAB/+AAAAeAAAH/8AAAH4AAAIH4AAB/AAAAAHwAAf8AAAAAPgAD/wAAAAAeAAPwAAAAAB4AB8AAAAAADwAHgAAAAAAPAA+AAAAAAA8ADwAAAAAADwA/AAAAAAAPAH8AAAAAAA8A/wAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAD/AAHvAAAAAf4AAP8AAAAB/gAA/wAAAAP8AAD/AAAAA/gAAP8AAAAH+AAA/wAAAAfwAAD3gAAAD/AAAeeAAAAP4AAB58AAAB/AAAPj4AAAH8AAB8H4AAA/gAAfgP//+D//D/8Af//4f/4P/gA///B//B/8AAf/8P/8P+AAAAAAAPgAAAAAAAAB8AAAAAAAAAHwAAAAAAAAA+AAAAAAAAADwAAAAAAAAAfAAAAAAAAAB4AAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
|
||||
let unknown = big ? atob("QEDBAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAH//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAA/AAD4AAAAAD4H4HwAAAAAfB/4PgAAAAB8P/weAAAAAHg//h4AAAAA+Hw+HwAAAAD4eB8PAAAAAP/wDw8AAAAA//APDwAAAAD/8A8PAAAAAH/gDw8AAAAAAAAfDwAAAAAAAH4fAAAAAAAB/B4AAAAAAAf4HgAAAAAAD/A+AAAAAAAfwHwAAAAAAD8A+AAAAAAAPgH4AAAAAAB8B/AAAAAAAHgf4AAAAAAA+H+AAAAAAADwfwAAAAAAAPD8AAAAAAAA8PAAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAADw8AAAAAAAAPDwAAAAAAAA8PAAAAAAAADw8AAAAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAH/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==") : undefined;
|
||||
let weatherCloudy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAAAAH4PgAAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA94AAAAAAAAHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/////////AH////////4AP////////AAH///////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAAAAAfgD/Af8H/4//7///////9//z/+AAAAAAAA");
|
||||
let weatherSunny = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAMAA8AAwAAAB4ADwAHgAAAHwAPAA+AAAAPgA8AHwAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+AAAAAAP+B/wAAAAAAfgB+AAAAAAD4AB8AAAAAAPAADwAAAAAB8AAPgAAAAAHgAAeAAAAAAeAAB4AAAAADwAADwAAAP//AAAP//AA//8AAA//8AD//wAAD//wAP//AAAP//AAAA8AAA8AAAAAB4AAHgAAAAAHgAAeAAAAAAfAAD4AAAAAA8AAPAAAAAAD4AB8AAAAAAH4AfgAAAAAA/4H/AAAAAAH///+AAAAAA+//98AAAAAHw//D4AAAAA+AfgHwAAAAHwA8APgAAAA+ADwAfAAAAHwAPAA+AAAAeAA8AB4AAAAwADwADAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAQCBAIA8AH4A/wb/YP8A/gB+ARiBAIAYABgAAA");
|
||||
let weatherMoon = big ? atob("QECBAAAGAAAADwAAAA+AAAAPAAAAD8AAAA8AAAAP4AAADwAAAAfwDwD/8AAAB/gPAP/wAAAH+A8A//AAAAf8DwD/8AAAB/4AAA8AAAAHvgAADwAAAAeeAAAPAAAAB54AAA8AAAAHjwAAAAAAAA+PDgAAAAAADw8PgAAAAAAfDw/AAAAAAB4PD+AAAAAAPg8D8AAAAAB8HwH4AAAAAfg+APwAAPAH8H4Af/AA///g/gA//AD//8H4AB/+AH//AfAAH/8Af/wD4AAIH4A/gAPAAAAHwB/AB8AAAAPgD/AfgAAAAeAH//+AAAAB4AP//4AAAADwAP//AAAAAPAAH/8AAAAA8AAAAAAAAADwAAAAAAAAAPAHAAAAAAAA8A/gAAAAAAHwH4AAAAAAAfg+AAAAAAAAfHwAAAAAAAA+eAAAAAAAAB54AAAAAAAAHvAAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD3gAAAAAAAAeeAAAAAAAAB58AAAAAAAAPj4AAAAAAAB8H4AAAAAAAfgP////////8Af////////gA////////8AAf//////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAP8B/4P/w//D/8f/5//j/8P/w//B/4D/ABgAAA");
|
||||
let weatherPartlyCloudy = big ? atob("QECBAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAcADwAOAAAAB4APAB4AAAAHwA8APgAAAAPgH4B8AAAAAfD/8PgAAAAA+//98AAAAAB////gQAAAAD/gf8DgAAAAH4AfgfAAAAA+AA/B+AAAADwAP8D8AAAAfAB/gH/wAAB4APwAP/wAAHgB+AAf/gAA8AHwAB//AP/wA+AACB+A//AHwAAAB8D/8AeAAAAD4P/wB4AAAAHgAPAfgAAAAeAAeH8AAAAA8AB5/wAAAADwAH3/AAAAAPAAP/AAAAAA8AA/wAAAAADwBB+AAAAAAPAOD4AAAAAB8B4HAAAAAAH4PgMAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA94AAAAAAAAHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/////////AH////////4AP////////AAH///////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAAYQAMAD8AIQBhoW+AOYBwwOBBgHGAGP/wf+AAA");
|
||||
let weatherRainy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+ADwAPAAH4PgAPAA8AAHx8AA8ADwAAPngADwAPAAAeeAAAAAAAAB7wAAAAAAAAD/AAAAAAAAAP8AAAAAAAAA/wAA8PDw8AD/AADw8PDwAP8AAPDw8PAA94AA8PDw8AHngAAAAAAAAefAAAAAAAAD4+AAAAAAAAfB+AAAAAAAH4D/8PDw8PD/AH/w8PDw8P4AP/Dw8PDw/AAH8PDw8PDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8ADwAAAAAADwAPAAAAAAAPAA8AAAAAAA8ADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQAJBgjPOEkgGYAZgA8ABgAAA");
|
||||
let weatherPartlyRainy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAPAAH4PgAAAA8AAHx8AAAADwAAPngAAAAPAAAeeAAAAA8AAB7wAAAADwAAD/AAAAAPAAAP8AAAAA8AAA/wAAAPDwAAD/AAAA8PAAAP8AAADw8AAA94AAAPDwAAHngAAA8PAAAefAAADw8AAD4+AAAPDwAAfB+AAA8PAAH4D///Dw8P//AH//8PDw//4AP//w8PD//AAH//Dw8P/gAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAEEAQAAeADMAYaFvoTmAMMDgQIBxhhiGGG9wDwAGA");
|
||||
let weatherSnowy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAA8AH4PgAAAADwAHx8AAAAAPAAPngAAAAA8AAeeAAPAA//AB7wAA8AD/8AD/AADwAP/wAP8AAPAA//AA/wAP/wAPAAD/AA//AA8AAP8AD/8ADwAA94AP/wAPAAHngADwAAAAAefAAPAAAAAD4+AA8AAAAAfB+ADwAAAAH4D/8AAPAP//AH/wAA8A//4AP/AADwD//AAH8AAPAP/gAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAADwAGAEYg73C50BCAEIC50O9wRiAGAA8AAAAAA");
|
||||
let weatherFoggy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAMAA8AAwAAAB4ADwAHgAAAHwAPAA+AAAAPgA8AHwAAAAfADwA+AAAAA+AfgHwAAAAB8P/w+AAAAAD7//3wAAAAAH///+AAAAAAP+B/wAAAAAAfgB+AAAAAAD4AB8AAAAAAPAADwAAAAAB8AAPgAAAAAHgAAeAAAAAAeAAB4AAAAADwAADwAAAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AA///wAA8AAAD///AAHgAAAP//8AAeAAAA///wAD4AAAAAAAAAPAAAAAAAAAB8AAAAAAAAAfgAAAAAAAAH/AAAAA///w/+AAAAD///D98AAAAP//8PD4AAAA///wgHwAAAAAAAAAPgAAAAAAAAAfAAAAAAAAAA+AAAAAAAAAB4AAD///DwADAAAP//8PAAAAAA///w8AAAAAD///DwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAAADwAZgDDA4EGAcQAZAAgAAf74AAAAAd/4AAAAA");
|
||||
let weatherStormy = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAAAAAAf/4AAAAAAAD//4AAAAAAA///wAAAAAAH+B/gAAAAAA/AA/AAAAAAH4AB+AAAAAAfAAD8AAAAAD4AAH/wAAAAfAAAP/wAAAB4AAAf/gAAAHgAAB//AAAB+AAACB+AAAfwAAAAB8AAH/AAAAAD4AA/8AAAAAHgAD8AAAAAAeAAfAAAAAAA8AB4AAAAAADwAPgAAAAAAPAA8AAAAAAA8APwAAAAAADwB/AAAAAAAPAP8AAAAAAB8B+AAAAAAAH4PgAAAAAAAHx8AAAAAAAAPngAAAAAAAAeeAAAAA/wAB7wAAAAH+AAD/AAAAAf4AAP8AAAAD/AAA/wAAAAP4AAD/AAAAB/gAAP8AAAAH8AAA94AAAA/wAAHngAAAD+AAAefAAAAfwAAD4+AAAB/AAAfB+AAAP4AAH4D///g//w//AH//+H/+D/4AP//wf/wf/AAH//D//D/gAAAAAAD4AAAAAAAAAfAAAAAAAAAB8AAAAAAAAAPgAAAAAAAAA8AAAAAAAAAHwAAAAAAAAAeAAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : atob("EBCBAAAAAYAH4AwwOBBgGEAOQMJAgjmOGcgAgACAAAAAAAAA");
|
||||
let unknown = big ? atob("QECBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAH/+AAAAAAAB//+AAAAAAAP//8AAAAAAB/gf4AAAAAAPwAPwAAAAAB+AAfgAAAAAPwAA+AAAAAA+B+B8AAAAAHwf+D4AAAAAfD/8HgAAAAB4P/4eAAAAAPh8Ph8AAAAA+HgfDwAAAAD/8A8PAAAAAP/wDw8AAAAA//APDwAAAAB/4A8PAAAAAAAAHw8AAAAAAAB+HwAAAAAAAfweAAAAAAAH+B4AAAAAAA/wPgAAAAAAH8B8AAAAAAA/APgAAAAAAD4B+AAAAAAAfAfwAAAAAAB4H+AAAAAAAPh/gAAAAAAA8H8AAAAAAADw/AAAAAAAAPDwAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/gAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA8PAAAAAAAADw8AAAAAAAAPDwAAAAAAAA8PAAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") : undefined;
|
||||
|
||||
switch (codeGroup) {
|
||||
case 2:
|
||||
|
@ -823,7 +238,9 @@ function getWeatherIconByCode(code, big) {
|
|||
case 8:
|
||||
switch (code) {
|
||||
case 800:
|
||||
return isDay() ? weatherSunny : weatherMoon;
|
||||
var hr = (new Date()).getHours();
|
||||
var isDay = (hr>6) && (hr<=18); // fixme we don't want to include ALL of suncalc just to choose one icon
|
||||
return isDay ? weatherSunny : weatherMoon;
|
||||
case 801:
|
||||
return weatherPartlyCloudy;
|
||||
case 802:
|
||||
|
@ -836,80 +253,19 @@ function getWeatherIconByCode(code, big) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function isDay() {
|
||||
let times = getSunData();
|
||||
if (times == undefined) return true;
|
||||
let sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||
let sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||
|
||||
return (now > sunRise && now < sunSet);
|
||||
}
|
||||
|
||||
function formatSeconds(s) {
|
||||
if (s > 60 * 60) { // hours
|
||||
return Math.round(s / (60 * 60)) + "h";
|
||||
}
|
||||
if (s > 60) { // minutes
|
||||
return Math.round(s / 60) + "m";
|
||||
}
|
||||
return "<1m";
|
||||
}
|
||||
|
||||
function getSunData() {
|
||||
if (location != undefined && location.lat != undefined) {
|
||||
let SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||
// get today's sunlight times for lat/lon
|
||||
return SunCalc ? SunCalc.getTimes(new Date(), location.lat, location.lon) : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculated progress of the sun between sunrise and sunset in percent
|
||||
*
|
||||
* Taken from rebble app and modified
|
||||
*/
|
||||
function getSunProgress() {
|
||||
let times = getSunData();
|
||||
if (times == undefined) return 0;
|
||||
let sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||
let sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||
|
||||
if (isDay()) {
|
||||
// during day
|
||||
let dayLength = sunSet - sunRise;
|
||||
if (now > sunRise) {
|
||||
return (now - sunRise) / dayLength;
|
||||
} else {
|
||||
return (sunRise - now) / dayLength;
|
||||
}
|
||||
} else {
|
||||
// during night
|
||||
if (now < sunRise) {
|
||||
let prevSunSet = sunSet - 60 * 60 * 24;
|
||||
return 1 - (sunRise - now) / (sunRise - prevSunSet);
|
||||
} else {
|
||||
let upcomingSunRise = sunRise + 60 * 60 * 24;
|
||||
return (upcomingSunRise - now) / (upcomingSunRise - sunSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Draws the background and the grey circle
|
||||
*/
|
||||
function drawCircleBackground(w) {
|
||||
g.clearRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
let drawCircleBackground = function(w) {
|
||||
// Draw rectangle background:
|
||||
g.setColor(colorBg);
|
||||
g.fillRect(w - radiusOuter - 3, h3 - radiusOuter - 3, w + radiusOuter + 3, h3 + radiusOuter + 3);
|
||||
g.fillRect(w - radiusBorder, h3 - radiusBorder, w + radiusBorder, g.getHeight()-1);
|
||||
// Draw grey background circle:
|
||||
g.setColor(colorGrey);
|
||||
g.setColor('#808080'); // grey
|
||||
g.fillCircle(w, h3, radiusOuter);
|
||||
}
|
||||
|
||||
function drawInnerCircleAndTriangle(w) {
|
||||
let drawInnerCircleAndTriangle = function(w) {
|
||||
// Draw inner circle
|
||||
g.setColor(colorBg);
|
||||
g.fillCircle(w, h3, radiusInner);
|
||||
|
@ -917,18 +273,13 @@ function drawInnerCircleAndTriangle(w) {
|
|||
g.fillPoly([w, h3, w - 15, h3 + radiusOuter + 5, w + 15, h3 + radiusOuter + 5]);
|
||||
}
|
||||
|
||||
function radians(a) {
|
||||
return a * Math.PI / 180;
|
||||
}
|
||||
|
||||
/*
|
||||
* This draws the actual gauge consisting out of lots of little filled circles
|
||||
*/
|
||||
function drawGauge(cx, cy, percent, color) {
|
||||
let drawGauge = function(cx, cy, percent, color) {
|
||||
let offset = 15;
|
||||
let end = 360 - offset;
|
||||
let radius = radiusInner + (circleCount == 3 ? 3 : 2);
|
||||
let size = radiusOuter - radiusInner - 2;
|
||||
let radius = radiusOuter+1;
|
||||
|
||||
if (percent <= 0) return; // no gauge needed
|
||||
if (percent > 1) percent = 1;
|
||||
|
@ -938,15 +289,21 @@ function drawGauge(cx, cy, percent, color) {
|
|||
|
||||
color = getGradientColor(color, percent);
|
||||
g.setColor(color);
|
||||
|
||||
for (let i = startRotation; i > endRotation - size; i -= size) {
|
||||
x = cx + radius * Math.sin(radians(i));
|
||||
y = cy + radius * Math.cos(radians(i));
|
||||
g.fillCircle(x, y, size);
|
||||
}
|
||||
// convert to radians
|
||||
startRotation *= Math.PI / 180;
|
||||
let amt = Math.PI / 10;
|
||||
endRotation = (endRotation * Math.PI / 180) - amt;
|
||||
// all we need to draw is an arc, because we'll fill the center
|
||||
let poly = [cx,cy];
|
||||
for (let r = startRotation; r > endRotation; r -= amt)
|
||||
poly.push(
|
||||
cx + radius * Math.sin(r),
|
||||
cy + radius * Math.cos(r)
|
||||
);
|
||||
g.fillPoly(poly);
|
||||
}
|
||||
|
||||
function writeCircleText(w, content) {
|
||||
let writeCircleText = function(w, content) {
|
||||
if (content == undefined) return;
|
||||
let font = String(content).length > 4 ? circleFontSmall : String(content).length > 3 ? circleFont : circleFontBig;
|
||||
g.setFont(font);
|
||||
|
@ -956,26 +313,48 @@ function writeCircleText(w, content) {
|
|||
g.drawString(content, w, h3);
|
||||
}
|
||||
|
||||
function getWeather() {
|
||||
let getWeather=function() {
|
||||
let jsonWeather = storage.readJSON('weather.json');
|
||||
return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined;
|
||||
}
|
||||
|
||||
g.clear(1); // clear the whole screen
|
||||
|
||||
Bangle.setUI({
|
||||
mode : "clock",
|
||||
remove : function() {
|
||||
// Called to unload all of the clock app
|
||||
// Called to unload all of the clock app (allowing for 'fast load')
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
||||
clockInfoMenu.forEach(c => c.remove());
|
||||
delete Graphics.prototype.setFontRobotoRegular50NumericOnly;
|
||||
delete Graphics.prototype.setFontRobotoRegular21;
|
||||
}});
|
||||
if (!showWidgets) require("widget_utils").show();
|
||||
}
|
||||
});
|
||||
|
||||
let clockInfoDraw = (itm, info, options) => {
|
||||
//print("Draw",itm.name,options);
|
||||
drawCircle(options.circlePosition, itm, info);
|
||||
if (options.focus) g.reset().drawRect(options.x, options.y, options.x+options.w-2, options.y+options.h-1)
|
||||
};
|
||||
let clockInfoItems = require("clock_info").load();
|
||||
let clockInfoMenu = [];
|
||||
for(var i=0;i<circleCount; i++) {
|
||||
let w = circlePosX[i];
|
||||
let y = h3-radiusBorder;
|
||||
clockInfoMenu[i] = require("clock_info").addInteractive(clockInfoItems, {
|
||||
x:w-radiusBorder, y:y, w:radiusBorder*2, h:g.getHeight()-(y+1),
|
||||
draw : clockInfoDraw, circlePosition : i+1
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
if (!showWidgets) require("widget_utils").hide();
|
||||
else Bangle.drawWidgets();
|
||||
|
||||
// schedule a draw for the next second or minute
|
||||
function queueDraw() {
|
||||
let queueDraw=function() {
|
||||
let queueMillis = settings.updateInterval * 1000;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
|
@ -985,3 +364,4 @@ function queueDraw() {
|
|||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
"showWidgets": false,
|
||||
"weatherCircleData": "humidity",
|
||||
"circleCount": 3,
|
||||
"circle1": "Bangle/HRM",
|
||||
"circle2": "Bangle/Steps",
|
||||
"circle3": "Bangle/Battery",
|
||||
"circle4": "weather",
|
||||
"circle1color": "green-red",
|
||||
"circle2color": "#0000ff",
|
||||
"circle3color": "red-green",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.18",
|
||||
"version":"0.21",
|
||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||
|
|
|
@ -12,23 +12,6 @@
|
|||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
//const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude", "timer"];
|
||||
//const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude", "timer"];
|
||||
var valuesCircleTypes = ["empty","weather", "sunprogress"];
|
||||
var namesCircleTypes = ["empty","weather", "sun"];
|
||||
clock_info.load().forEach(e=>{
|
||||
if(e.dynamic) {
|
||||
valuesCircleTypes = valuesCircleTypes.concat([e.name+"/"]);
|
||||
namesCircleTypes = namesCircleTypes.concat([e.name]);
|
||||
} else {
|
||||
let values = e.items.map(i=>e.name+"/"+i.name);
|
||||
let names =e.name=="Bangle" ? e.items.map(i=>i.name) : values;
|
||||
valuesCircleTypes = valuesCircleTypes.concat(values);
|
||||
namesCircleTypes = namesCircleTypes.concat(names);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
|
||||
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
|
||||
const namesColors = ["default", "red", "green", "blue", "yellow", "magenta",
|
||||
|
@ -105,12 +88,6 @@
|
|||
const menu = {
|
||||
'': { 'title': /*LANG*/'Circle ' + circleId },
|
||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'data': {
|
||||
value: valuesCircleTypes.indexOf(settings[circleName]),
|
||||
min: 0, max: valuesCircleTypes.length - 1,
|
||||
format: v => namesCircleTypes[v],
|
||||
onchange: x => save(circleName, valuesCircleTypes[x]),
|
||||
},
|
||||
/*LANG*/'color': {
|
||||
value: valuesColors.indexOf(settings[colorKey]) || 0,
|
||||
min: 0, max: valuesColors.length - 1,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
After Width: | Height: | Size: 901 B |
|
@ -0,0 +1,32 @@
|
|||
(function() {
|
||||
require("Font4x8Numeric").add(Graphics);
|
||||
return {
|
||||
name: "Bangle",
|
||||
items: [
|
||||
{ name : "Date",
|
||||
get : () => {
|
||||
let d = new Date();
|
||||
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
|
||||
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
|
||||
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
|
||||
return {
|
||||
text : require("locale").dow(d,1).toUpperCase(),
|
||||
img : g.asImage("string")
|
||||
};
|
||||
},
|
||||
show : function() {
|
||||
this.interval = setTimeout(()=>{
|
||||
this.emit("redraw");
|
||||
this.interval = setInterval(()=>{
|
||||
this.emit("redraw");
|
||||
}, 86400000);
|
||||
}, 86400000 - (Date.now() % 86400000));
|
||||
},
|
||||
hide : function() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
|
@ -0,0 +1,12 @@
|
|||
{ "id": "clkinfocal",
|
||||
"name": "Calendar Clockinfo",
|
||||
"version":"0.01",
|
||||
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday",
|
||||
"icon": "app.png",
|
||||
"type": "clkinfo",
|
||||
"tags": "clkinfo,calendar",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"clkinfocal.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
}
|
|
@ -1 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
Add a 'time' clockinfo that also displays a percentage of day left
|
||||
0.03: Change 3rd mode to show the time to next sunrise/sunset time (not actual time)
|
||||
|
|
|
@ -1,32 +1,75 @@
|
|||
(function() {
|
||||
// get today's sunlight times for lat/lon
|
||||
var sunrise, sunset;
|
||||
var sunrise, sunset, date;
|
||||
var SunCalc = require("suncalc"); // from modules folder
|
||||
const locale = require("locale");
|
||||
|
||||
function calculate() {
|
||||
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||
const locale = require("locale");
|
||||
var location = require("Storage").readJSON("mylocation.json",1)||{};
|
||||
location.lat = location.lat||51.5072;
|
||||
location.lon = location.lon||0.1276;
|
||||
location.location = location.location||"London";
|
||||
var times = SunCalc.getTimes(new Date(), location.lat, location.lon);
|
||||
sunrise = locale.time(times.sunrise,1);
|
||||
sunset = locale.time(times.sunset,1);
|
||||
location.lon = location.lon||0.1276; // London
|
||||
date = new Date(Date.now());
|
||||
var times = SunCalc.getTimes(date, location.lat, location.lon);
|
||||
sunrise = times.sunrise;
|
||||
sunset = times.sunset;
|
||||
/* do we want to re-calculate this every day? Or we just assume
|
||||
that 'show' will get called once a day? */
|
||||
}
|
||||
|
||||
function show() {
|
||||
this.interval = setTimeout(()=>{
|
||||
this.emit("redraw");
|
||||
this.interval = setInterval(()=>{
|
||||
this.emit("redraw");
|
||||
}, 60000);
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
function hide() {
|
||||
clearInterval(this.interval);
|
||||
this.interval = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
name: "Bangle",
|
||||
items: [
|
||||
{ name : "Sunrise",
|
||||
get : () => ({ text : sunrise,
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }),
|
||||
show : calculate, hide : () => {}
|
||||
get : () => { calculate();
|
||||
return { text : locale.time(sunrise,1),
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }},
|
||||
show : show, hide : hide
|
||||
}, { name : "Sunset",
|
||||
get : () => ({ text : sunset,
|
||||
img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }),
|
||||
show : calculate, hide : () => {}
|
||||
get : () => { calculate();
|
||||
return { text : locale.time(sunset,1),
|
||||
img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }},
|
||||
show : show, hide : hide
|
||||
}, { name : "Sunrise/set", // Time in day (uses v/min/max to show percentage through day)
|
||||
hasRange : true,
|
||||
get : () => {
|
||||
calculate();
|
||||
let day = true;
|
||||
let d = date.getTime();
|
||||
let dayLength = sunset.getTime()-sunrise.getTime();
|
||||
let timeUntil, timeTotal;
|
||||
if (d < sunrise.getTime()) {
|
||||
day = false; // early morning
|
||||
timePast = sunrise.getTime()-d;
|
||||
timeTotal = 86400000-dayLength;
|
||||
} else if (d > sunset.getTime()) {
|
||||
day = false; // evening
|
||||
timePast = d-sunset.getTime();
|
||||
timeTotal = 86400000-dayLength;
|
||||
} else { // day!
|
||||
timePast = d-sunrise.getTime();
|
||||
timeTotal = dayLength;
|
||||
}
|
||||
let v = Math.round(100 * timePast / timeTotal);
|
||||
let minutesTo = (timeTotal-timePast)/60000;
|
||||
return { text : (minutesTo>90) ? (Math.round(minutesTo/60)+"h") : (Math.round(minutesTo)+"m"),
|
||||
v : v, min : 0, max : 100,
|
||||
img : day ? atob("GBiBAAAYAAAYAAAYAAgAEBwAOAx+MAD/AAH/gAP/wAf/4Af/4Of/5+f/5wf/4Af/4AP/wAH/gAD/AAx+MBwAOAgAEAAYAAAYAAAYAA==") : atob("GBiBAAfwAA/8AAP/AAH/gAD/wAB/wAB/4AA/8AA/8AA/8AAf8AAf8AAf8AAf8AA/8AA/8AA/4AB/4AB/wAD/wAH/gAf/AA/8AAfwAA==")
|
||||
}
|
||||
},
|
||||
show : show, hide : hide
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "clkinfosunrise",
|
||||
"name": "Sunrise Clockinfo",
|
||||
"version":"0.01",
|
||||
"version":"0.03",
|
||||
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays sunrise and sunset based on the location from the 'My Location' app",
|
||||
"icon": "app.png",
|
||||
"type": "clkinfo",
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
0.06: better contrast for light theme, use fg color instead of dithered for ring
|
||||
0.07: Use default Bangle formatter for booleans
|
||||
0.08: fix idle timer always getting set to true
|
||||
0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||
var SunCalc = require("suncalc"); // from modules folder
|
||||
const storage = require('Storage');
|
||||
const locale = require("locale");
|
||||
const SETTINGS_FILE = "daisy.json";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "daisy",
|
||||
"name": "Daisy",
|
||||
"version":"0.08",
|
||||
"version":"0.09",
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"custom": "custom.html",
|
||||
"storage": [
|
||||
{"name":"espruinoprog.app.js","url":"app.js"},
|
||||
{"name":"espruinoprog.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"espruinoprog.img","url":"app-icon.js","evaluate":true}
|
||||
], "data": [
|
||||
{"name":"espruinoprog.json"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
0.05: Tweaks to help with memory usage
|
||||
0.06: Reduce memory usage
|
||||
0.07: Allow negative numbers when manual-sorting
|
||||
0.08: Automatic translation of strings.
|
||||
|
|
|
@ -3,20 +3,20 @@ const store = require('Storage');
|
|||
function showMainMenu() {
|
||||
const mainmenu = {
|
||||
'': {
|
||||
'title': 'App Manager',
|
||||
'title': /*LANG*/'App Manager',
|
||||
},
|
||||
'< Back': ()=> {load();},
|
||||
'Sort Apps': () => showSortAppsMenu(),
|
||||
'Manage Apps': ()=> showApps(),
|
||||
'Compact': () => {
|
||||
E.showMessage('Compacting...');
|
||||
/*LANG*/'Sort Apps': () => showSortAppsMenu(),
|
||||
/*LANG*/'Manage Apps': ()=> showApps(),
|
||||
/*LANG*/'Compact': () => {
|
||||
E.showMessage(/*LANG*/'Compacting...');
|
||||
try {
|
||||
store.compact();
|
||||
} catch (e) {
|
||||
}
|
||||
showMainMenu();
|
||||
},
|
||||
'Free': {
|
||||
/*LANG*/'Free': {
|
||||
value: undefined,
|
||||
format: (v) => {
|
||||
return store.getFree();
|
||||
|
@ -65,13 +65,13 @@ function eraseData(info) {
|
|||
});
|
||||
}
|
||||
function eraseApp(app, files,data) {
|
||||
E.showMessage('Erasing\n' + app.name + '...');
|
||||
E.showMessage(/*LANG*/'Erasing\n' + app.name + '...');
|
||||
var info = store.readJSON(app.id + ".info", 1)||{};
|
||||
if (files) eraseFiles(info);
|
||||
if (data) eraseData(info);
|
||||
}
|
||||
function eraseOne(app, files,data){
|
||||
E.showPrompt('Erase\n'+app.name+'?').then((v) => {
|
||||
E.showPrompt(/*LANG*/'Erase\n'+app.name+'?').then((v) => {
|
||||
if (v) {
|
||||
Bangle.buzz(100, 1);
|
||||
eraseApp(app, files, data);
|
||||
|
@ -82,7 +82,7 @@ function eraseOne(app, files,data){
|
|||
});
|
||||
}
|
||||
function eraseAll(apps, files,data) {
|
||||
E.showPrompt('Erase all?').then((v) => {
|
||||
E.showPrompt(/*LANG*/'Erase all?').then((v) => {
|
||||
if (v) {
|
||||
Bangle.buzz(100, 1);
|
||||
apps.forEach(app => eraseApp(app, files, data));
|
||||
|
@ -99,11 +99,11 @@ function showAppMenu(app) {
|
|||
'< Back': () => showApps(),
|
||||
};
|
||||
if (app.hasData) {
|
||||
appmenu['Erase Completely'] = () => eraseOne(app, true, true);
|
||||
appmenu['Erase App,Keep Data'] = () => eraseOne(app, true, false);
|
||||
appmenu['Only Erase Data'] = () => eraseOne(app, false, true);
|
||||
appmenu[/*LANG*/'Erase Completely'] = () => eraseOne(app, true, true);
|
||||
appmenu[/*LANG*/'Erase App,Keep Data'] = () => eraseOne(app, true, false);
|
||||
appmenu[/*LANG*/'Only Erase Data'] = () => eraseOne(app, false, true);
|
||||
} else {
|
||||
appmenu['Erase'] = () => eraseOne(app, true, false);
|
||||
appmenu[/*LANG*/'Erase'] = () => eraseOne(app, true, false);
|
||||
}
|
||||
E.showMenu(appmenu);
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ function showAppMenu(app) {
|
|||
function showApps() {
|
||||
const appsmenu = {
|
||||
'': {
|
||||
'title': 'Apps',
|
||||
'title': /*LANG*/'Apps',
|
||||
},
|
||||
'< Back': () => showMainMenu(),
|
||||
};
|
||||
|
@ -128,17 +128,17 @@ function showApps() {
|
|||
menu[app.name] = () => showAppMenu(app);
|
||||
return menu;
|
||||
}, appsmenu);
|
||||
appsmenu['Erase All'] = () => {
|
||||
appsmenu[/*LANG*/'Erase All'] = () => {
|
||||
E.showMenu({
|
||||
'': {'title': 'Erase All'},
|
||||
'Erase Everything': () => eraseAll(list, true, true),
|
||||
'Erase Apps,Keep Data': () => eraseAll(list, true, false),
|
||||
'Only Erase Data': () => eraseAll(list, false, true),
|
||||
'': {'title': /*LANG*/'Erase All'},
|
||||
/*LANG*/'Erase Everything': () => eraseAll(list, true, true),
|
||||
/*LANG*/'Erase Apps,Keep Data': () => eraseAll(list, true, false),
|
||||
/*LANG*/'Only Erase Data': () => eraseAll(list, false, true),
|
||||
'< Back': () => showApps(),
|
||||
});
|
||||
};
|
||||
} else {
|
||||
appsmenu['...No Apps...'] = {
|
||||
appsmenu[/*LANG*/'...No Apps...'] = {
|
||||
value: undefined,
|
||||
format: ()=> '',
|
||||
onchange: ()=> {}
|
||||
|
@ -150,16 +150,16 @@ function showApps() {
|
|||
function showSortAppsMenu() {
|
||||
const sorterMenu = {
|
||||
'': {
|
||||
'title': 'App Sorter',
|
||||
'title': /*LANG*/'App Sorter',
|
||||
},
|
||||
'< Back': () => showMainMenu(),
|
||||
'Sort: manually': ()=> showSortAppsManually(),
|
||||
'Sort: alph. ASC': () => {
|
||||
E.showMessage('Sorting:\nAlphabetically\nascending ...');
|
||||
/*LANG*/'Sort: manually': ()=> showSortAppsManually(),
|
||||
/*LANG*/'Sort: alph. ASC': () => {
|
||||
E.showMessage(/*LANG*/'Sorting:\nAlphabetically\nascending ...');
|
||||
sortAlphabet(false);
|
||||
},
|
||||
'Sort: alph. DESC': () => {
|
||||
E.showMessage('Sorting:\nAlphabetically\ndescending ...');
|
||||
E.showMessage(/*LANG*/'Sorting:\nAlphabetically\ndescending ...');
|
||||
sortAlphabet(true);
|
||||
}
|
||||
};
|
||||
|
@ -169,7 +169,7 @@ function showSortAppsMenu() {
|
|||
function showSortAppsManually() {
|
||||
const appsSorterMenu = {
|
||||
'': {
|
||||
'title': 'Sort: manually',
|
||||
'title': /*LANG*/'Sort: manually',
|
||||
},
|
||||
'< Back': () => showSortAppsMenu(),
|
||||
};
|
||||
|
@ -186,7 +186,7 @@ function showSortAppsManually() {
|
|||
return menu;
|
||||
}, appsSorterMenu);
|
||||
} else {
|
||||
appsSorterMenu['...No Apps...'] = {
|
||||
appsSorterMenu[/*LANG*/'...No Apps...'] = {
|
||||
value: undefined,
|
||||
format: ()=> '',
|
||||
onchange: ()=> {}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "files",
|
||||
"name": "App Manager",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Show currently installed apps, free space, and allow their deletion from the watch",
|
||||
"icon": "files.png",
|
||||
"tags": "tool,system,files",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New app!
|
||||
0.02: Submitted to app loader
|
||||
0.03: Do not invert colors
|
||||
|
|
|
@ -35,7 +35,7 @@ function drawImage(fileName) {
|
|||
Bangle.setLCDBrightness(1); // Full brightness
|
||||
|
||||
image = eval(storage.read(fileName)); // Sadly, the only reasonable way to do this
|
||||
g.clear().reset().drawImage(image, 88, 88, { rotate: angle });
|
||||
g.clear().reset().setBgColor(0).setColor("#fff").drawImage(image, 88, 88, { rotate: angle });
|
||||
}
|
||||
|
||||
setWatch(info => {
|
||||
|
@ -44,7 +44,7 @@ setWatch(info => {
|
|||
else angle = 0;
|
||||
Bangle.buzz();
|
||||
|
||||
g.clear().reset().drawImage(image, 88, 88, { rotate: angle })
|
||||
g.clear().reset().setBgColor(0).setColor("#fff").drawImage(image, 88, 88, { rotate: angle })
|
||||
}
|
||||
}, BTN1, { repeat: true });
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "gallery",
|
||||
"name": "Gallery",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "A gallery that lets you view images uploaded with the IDE (see README)",
|
||||
"readme": "README.md",
|
||||
"icon": "icon.png",
|
||||
|
|
|
@ -13,3 +13,4 @@
|
|||
Reconstruct battery voltage by using calibrated batFullVoltage
|
||||
Averaging for smoothing compass headings
|
||||
Save state if route or waypoint has been chosen
|
||||
0.09: Workaround a minifier issue allowing to install gpstrek with minification enabled
|
||||
|
|
|
@ -259,7 +259,8 @@ let getCompassSlice = function(compassDataSource){
|
|||
|
||||
|
||||
if (compassDataSource.getPoints){
|
||||
for (let p of compassDataSource.getPoints()){
|
||||
let points = compassDataSource.getPoints(); //storing this in a variable works around a minifier bug causing a problem in the next line: for(let a of a.getPoints())
|
||||
for (let p of points){
|
||||
g.reset();
|
||||
var bpos = p.bearing - lastDrawnValue;
|
||||
if (bpos>180) bpos -=360;
|
||||
|
@ -285,7 +286,8 @@ let getCompassSlice = function(compassDataSource){
|
|||
}
|
||||
}
|
||||
if (compassDataSource.getMarkers){
|
||||
for (let m of compassDataSource.getMarkers()){
|
||||
let markers = compassDataSource.getMarkers(); //storing this in a variable works around a minifier bug causing a problem in the next line: for(let a of a.getMarkers())
|
||||
for (let m of markers){
|
||||
g.reset();
|
||||
g.setColor(m.fillcolor);
|
||||
let mpos = m.xpos * width;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "gpstrek",
|
||||
"name": "GPS Trekking",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Added clkinfo for clocks.
|
||||
0.04: Feedback if clkinfo run is called.
|
||||
0.05: Clkinfo improvements.
|
||||
0.06: Updated clkinfo icon.
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
var haItems = {
|
||||
name: "Home",
|
||||
img: atob("GBiBAf/////////n///D//+B//8A//48T/wkD/gkD/A8D+AYB8AYA4eZ4QyZMOyZN+fb5+D/B+B+B+A8B+AYB+AYB+AYB+AYB+A8Bw=="),
|
||||
img: atob("GBiBAAAAAAAAAAAAAAAYAAA+AAB+AADD4AHb4APD4Afn8A/n+BxmOD0mnA0ksAwAMA+B8A/D8A/n8A/n8A/n8A/n8AAAAAAAAAAAAA=="),
|
||||
items: []
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ha",
|
||||
"name": "HomeAssistant",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "Integrates your BangleJS into HomeAssistant.",
|
||||
"icon": "ha.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"storage": [
|
||||
{"name":"hourstrike.app.js","url":"app.js"},
|
||||
{"name":"hourstrike.boot.js","url":"boot.js"},
|
||||
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true}
|
||||
], "data" : [
|
||||
{"name":"hourstrike.json","url":"hourstrike.json"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
0.08: Don't force backlight on/watch unlocked on Bangle 2
|
||||
0.09: Grey out BPM until confidence is over 50%
|
||||
0.10: Autoscale raw graph to maximum value seen
|
||||
0.11: Automatic translation of strings.
|
||||
|
|
|
@ -37,7 +37,7 @@ function updateHrm(){
|
|||
var px = g.getWidth()/2;
|
||||
g.setFontAlign(0,-1);
|
||||
g.clearRect(0,24,g.getWidth(),80);
|
||||
g.setFont("6x8").drawString("Confidence "+(hrmInfo.confidence || "--")+"%", px, 70);
|
||||
g.setFont("6x8").drawString(/*LANG*/"Confidence "+(hrmInfo.confidence || "--")+"%", px, 70);
|
||||
|
||||
updateScale();
|
||||
|
||||
|
@ -46,7 +46,7 @@ function updateHrm(){
|
|||
g.setFontVector(40).setColor(hrmInfo.confidence > 50 ? g.theme.fg : "#888").drawString(str,px,45);
|
||||
px += g.stringWidth(str)/2;
|
||||
g.setFont("6x8").setColor(g.theme.fg);
|
||||
g.drawString("BPM",px+15,45);
|
||||
g.drawString(/*LANG*/"BPM",px+15,45);
|
||||
}
|
||||
|
||||
function updateScale(){
|
||||
|
@ -101,7 +101,7 @@ Bangle.loadWidgets();
|
|||
Bangle.drawWidgets();
|
||||
g.setColor(g.theme.fg);
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,-1);
|
||||
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
g.drawString(/*LANG*/"Please wait...",g.getWidth()/2,g.getHeight()/2 - 16);
|
||||
countDown();
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "hrm",
|
||||
"name": "Heart Rate Monitor",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "Measure your heart rate and see live sensor data",
|
||||
"icon": "heartrate.png",
|
||||
"tags": "health",
|
||||
|
|
|
@ -9,3 +9,7 @@
|
|||
0.23: Added note to configure position in "my location" if not done yet. Small fixes.
|
||||
0.24: Added fast load
|
||||
0.25: Minor code optimization
|
||||
0.26: BJS2: Swipe down to rotate 180 degree
|
||||
0.27: BJS2: Changed swipe down to swipe up
|
||||
0.28: Reverted changes to implementation of 0.25
|
||||
0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
|
|
|
@ -14,7 +14,6 @@ Provide names and the UTC offsets for up to three other timezones in the app sto
|
|||
|
||||
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.
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if you have feature requests or notice bugs.
|
||||
|
|
|
@ -46,7 +46,7 @@ var offsets = require("Storage").readJSON("hworldclock.settings.json") || [];
|
|||
//=======Sun
|
||||
setting = require("Storage").readJSON("setting.json",1);
|
||||
E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ
|
||||
SunCalc = require("hsuncalc.js");
|
||||
SunCalc = require("suncalc"); // from modules folder
|
||||
const LOCATION_FILE = "mylocation.json";
|
||||
var rise = "read";
|
||||
var set = "...";
|
||||
|
@ -141,11 +141,9 @@ function getCurrentTimeFromOffset(dt, offset) {
|
|||
function updatePos() {
|
||||
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 = "v" + times.sunset.toString().split(" ")[4].substr(0,5);
|
||||
//noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon);
|
||||
} else {
|
||||
rise = null;
|
||||
set = null;
|
||||
|
|
|
@ -1,298 +0,0 @@
|
|||
/* Module suncalc.js
|
||||
(c) 2011-2015, Vladimir Agafonkin
|
||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
||||
https://github.com/mourner/suncalc
|
||||
|
||||
PB: Usage:
|
||||
E.setTimeZone(2); // 1 = MEZ, 2 = MESZ
|
||||
SunCalc = require("suncalc.js");
|
||||
pos = SunCalc.getPosition(Date.now(), 53.3, 10.1);
|
||||
times = SunCalc.getTimes(Date.now(), 53.3, 10.1);
|
||||
rise = times.sunrise; // Date object
|
||||
rise_str = rise.getHours() + ':' + rise.getMinutes(); //hh:mm
|
||||
*/
|
||||
var exports={};
|
||||
|
||||
// shortcuts for easier to read formulas
|
||||
|
||||
var PI = Math.PI,
|
||||
sin = Math.sin,
|
||||
cos = Math.cos,
|
||||
tan = Math.tan,
|
||||
asin = Math.asin,
|
||||
atan = Math.atan2,
|
||||
acos = Math.acos,
|
||||
rad = PI / 180;
|
||||
|
||||
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
|
||||
|
||||
// date/time constants and conversions
|
||||
|
||||
var dayMs = 1000 * 60 * 60 * 24,
|
||||
J1970 = 2440588,
|
||||
J2000 = 2451545;
|
||||
|
||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
||||
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } // PB: onece removed + 0.5; included it again 4 Jan 2021
|
||||
function toDays(date) { return toJulian(date) - J2000; }
|
||||
|
||||
|
||||
// general calculations for position
|
||||
|
||||
var e = rad * 23.4397; // obliquity of the Earth
|
||||
|
||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
||||
|
||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
||||
|
||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
||||
|
||||
function astroRefraction(h) {
|
||||
if (h < 0) // the following formula works for positive altitudes only.
|
||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
||||
|
||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
||||
}
|
||||
|
||||
// general sun calculations
|
||||
|
||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
||||
|
||||
function eclipticLongitude(M) {
|
||||
|
||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
||||
P = rad * 102.9372; // perihelion of the Earth
|
||||
|
||||
return M + C + P + PI;
|
||||
}
|
||||
|
||||
function sunCoords(d) {
|
||||
|
||||
var M = solarMeanAnomaly(d),
|
||||
L = eclipticLongitude(M);
|
||||
|
||||
return {
|
||||
dec: declination(L, 0),
|
||||
ra: rightAscension(L, 0)
|
||||
};
|
||||
}
|
||||
|
||||
// calculates sun position for a given date and latitude/longitude
|
||||
|
||||
exports.getPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = sunCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra;
|
||||
|
||||
return {
|
||||
azimuth: Math.round((azimuth(H, phi, c.dec) / rad + 180) % 360), // PB: converted to deg
|
||||
altitude: Math.round( altitude(H, phi, c.dec) / rad) // PB: converted to deg
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// sun times configuration (angle, morning name, evening name)
|
||||
|
||||
var times = [
|
||||
[-0.833, 'sunrise', 'sunset' ]
|
||||
];
|
||||
|
||||
// calculations for sun times
|
||||
var J0 = 0.0009;
|
||||
|
||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
||||
|
||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
||||
|
||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
||||
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
|
||||
|
||||
// returns set time for the given sun altitude
|
||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
||||
|
||||
var w = hourAngle(h, phi, dec),
|
||||
a = approxTransit(w, lw, n);
|
||||
return solarTransitJ(a, M, L);
|
||||
}
|
||||
|
||||
|
||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
||||
// the observer height (in meters) relative to the horizon
|
||||
|
||||
exports.getTimes = function (date, lat, lng, height) {
|
||||
|
||||
height = height || 0;
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
|
||||
dh = observerAngle(height),
|
||||
|
||||
d = toDays(date),
|
||||
n = julianCycle(d, lw),
|
||||
ds = approxTransit(0, lw, n),
|
||||
|
||||
M = solarMeanAnomaly(ds),
|
||||
L = eclipticLongitude(M),
|
||||
dec = declination(L, 0),
|
||||
|
||||
Jnoon = solarTransitJ(ds, M, L),
|
||||
|
||||
i, len, time, h0, Jset, Jrise;
|
||||
|
||||
|
||||
var result = {
|
||||
solarNoon: fromJulian(Jnoon),
|
||||
nadir: fromJulian(Jnoon - 0.5)
|
||||
};
|
||||
|
||||
for (i = 0, len = times.length; i < len; i += 1) {
|
||||
time = times[i];
|
||||
h0 = (time[0] + dh) * rad;
|
||||
|
||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
||||
Jrise = Jnoon - (Jset - Jnoon);
|
||||
|
||||
result[time[1]] = fromJulian(Jrise);
|
||||
result[time[2]] = fromJulian(Jset);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
||||
|
||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
||||
|
||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
||||
|
||||
l = L + rad * 6.289 * sin(M), // longitude
|
||||
b = rad * 5.128 * sin(F), // latitude
|
||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
||||
|
||||
return {
|
||||
ra: rightAscension(l, b),
|
||||
dec: declination(l, b),
|
||||
dist: dt
|
||||
};
|
||||
}
|
||||
|
||||
getMoonPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = moonCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra,
|
||||
h = altitude(H, phi, c.dec),
|
||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
||||
|
||||
h = h + astroRefraction(h); // altitude correction for refraction
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: h,
|
||||
distance: c.dist,
|
||||
parallacticAngle: pa
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// calculations for illumination parameters of the moon,
|
||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
|
||||
getMoonIllumination = function (date) {
|
||||
|
||||
var d = toDays(date || new Date()),
|
||||
s = sunCoords(d),
|
||||
m = moonCoords(d),
|
||||
|
||||
sdist = 149598000, // distance from Earth to Sun in km
|
||||
|
||||
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
|
||||
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
|
||||
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
|
||||
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
|
||||
|
||||
return {
|
||||
fraction: (1 + cos(inc)) / 2,
|
||||
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
|
||||
angle: angle
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
function hoursLater(date, h) {
|
||||
return new Date(date.valueOf() + h * dayMs / 24);
|
||||
}
|
||||
|
||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
||||
|
||||
getMoonTimes = function (date, lat, lng, inUTC) {
|
||||
var t = new Date(date);
|
||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
||||
else t.setHours(0, 0, 0, 0);
|
||||
|
||||
var hc = 0.133 * rad,
|
||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
||||
|
||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
||||
for (var i = 1; i <= 24; i += 2) {
|
||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
||||
|
||||
a = (h0 + h2) / 2 - h1;
|
||||
b = (h2 - h0) / 2;
|
||||
xe = -b / (2 * a);
|
||||
ye = (a * xe + b) * xe + h1;
|
||||
d = b * b - 4 * a * h1;
|
||||
roots = 0;
|
||||
|
||||
if (d >= 0) {
|
||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
||||
x1 = xe - dx;
|
||||
x2 = xe + dx;
|
||||
if (Math.abs(x1) <= 1) roots++;
|
||||
if (Math.abs(x2) <= 1) roots++;
|
||||
if (x1 < -1) x1 = x2;
|
||||
}
|
||||
|
||||
if (roots === 1) {
|
||||
if (h0 < 0) rise = i + x1;
|
||||
else set = i + x1;
|
||||
|
||||
} else if (roots === 2) {
|
||||
rise = i + (ye < 0 ? x2 : x1);
|
||||
set = i + (ye < 0 ? x1 : x2);
|
||||
}
|
||||
|
||||
if (rise && set) break;
|
||||
|
||||
h0 = h2;
|
||||
}
|
||||
|
||||
var result = {};
|
||||
|
||||
if (rise) result.rise = hoursLater(t, rise);
|
||||
if (set) result.set = hoursLater(t, set);
|
||||
|
||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
||||
|
||||
return result;
|
||||
};
|
|
@ -2,7 +2,7 @@
|
|||
"id": "hworldclock",
|
||||
"name": "Hanks World Clock",
|
||||
"shortName": "Hanks World Clock",
|
||||
"version": "0.25",
|
||||
"version": "0.29",
|
||||
"description": "Current time zone plus up to three others",
|
||||
"allow_emulator":true,
|
||||
"icon": "app.png",
|
||||
|
@ -15,8 +15,7 @@
|
|||
"storage": [
|
||||
{"name":"hworldclock.app.js","url":"app.js"},
|
||||
{"name":"hworldclock.img","url":"hworldclock-icon.js","evaluate":true},
|
||||
{"name":"hworldclock.settings.js","url":"settings.js"},
|
||||
{"name":"hsuncalc.js","url":"hsuncalc.js"}
|
||||
{"name":"hworldclock.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"hworldclock.settings.json"},
|
||||
|
|
|
@ -622,12 +622,12 @@ s.pl = {};
|
|||
endPerfLog("fullDraw", true);
|
||||
|
||||
if (!Bangle.uiRemove){
|
||||
setUi();
|
||||
setUi(); // Calls Bangle.setUI() (this comment also fixes lint warning)
|
||||
let orig = Bangle.drawWidgets;
|
||||
Bangle.drawWidgets = ()=>{};
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets = orig;
|
||||
require("widget_utils").swipeOn();
|
||||
require("widget_utils").swipeOn(0);
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}).catch((e)=>{
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Positioning of marker now takes the height of the widget field into account.
|
||||
0.04: Fix issue if going back without typing.
|
||||
0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat.
|
||||
0.06: Support input of numbers and uppercase characters.
|
||||
|
|
|
@ -4,6 +4,10 @@ A library that provides the ability to input text by swiping PalmOS Graffiti-sty
|
|||
|
||||
To get a legend of available characters, just tap the screen.
|
||||
|
||||
To switch between the input of alphabetic and numeric characters tap the widget which displays either "123" or "ABC".
|
||||
|
||||
To switch between lowercase and uppercase characters do an up swipe.
|
||||
|
||||

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

|
||||

|
||||

|
||||
|
||||
This clock is a Lato version of Simplest++. Simplest++ provided the
|
||||
smallest example of a clock that supports 'fast load' and 'clock
|
||||
info'. Lato takes this one step further and adds the lovely Lato
|
||||
font. The clock is derived from Simplest++ and inspired by the
|
||||
Pastel Clock.
|
||||
|
||||
## Usage
|
||||
|
||||
* When the screen is unlocked, tap at the bottom of the csreen on the information text.
|
||||
It should change color showing it is selected.
|
||||
|
||||
* Swipe up or down to cycle through the info screens that can be displayed
|
||||
when you have finished tap again towards the centre of the screen to unselect.
|
||||
|
||||
* Swipe left or right to change the type of info screens displayed (by default
|
||||
there is only one type of data so this will have no effect)
|
||||
|
||||
* Settings are saved automatically and reloaded along with the clock.
|
||||
|
||||
## About Clock Info's
|
||||
|
||||
* The clock info modules enable all clocks to add the display of information to the clock face.
|
||||
|
||||
* The default clock_info module provides a display of battery %, Steps, Heart Rate and Altitude.
|
||||
|
||||
* Installing the [Sunrise ClockInfo](https://banglejs.com/apps/?id=clkinfosunrise) adds Sunrise and Sunset times into the list of info's.
|
||||
|
||||
|
||||
## References
|
||||
|
||||
* [What is Fast Load and how does it work](http://www.espruino.com/Bangle.js+Fast+Load)
|
||||
|
||||
* [Clock Info Tutorial](http://www.espruino.com/Bangle.js+Clock+Info)
|
||||
|
||||
* [How to load modules through the IDE](https://github.com/espruino/BangleApps/blob/master/modules/README.md)
|
||||
|
||||
|
||||
## With Thanks
|
||||
|
||||
* Gordon for support
|
||||
* David Peer for his work on BW Clock
|
||||
|
||||
|
||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support
|
||||
and discussion please post in the [Bangle JS
|
||||
Forum](http://forum.espruino.com/microcosms/1424/)
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkA/4A/AH4AUiIAKCxXzC5c/C5PyC5cvC8PxC5cfLxNEABhgI+gXNp4X3//+9wAK96PJC6/zC5bvKC6//C5YWKC4nUoMUpoXS8lDn/zmlOC6NCA4ckC6Hkl4HD+QwCC5c+LoIsCoSKBMIPjC5tD//0olEp//mgXNmMRiYuBC4JjBBAYAK+MRj//CwIABBAgXkI5AXOiRyBC4J8BkIXN+dEoKnFiNEAYIXNa4sUC59EJAIACkIHBC5iMCoMTn/zmIuBSQIXODAMRAAKqDABikCAAqqBC8i8CAArCBC/n0C49PC5oA/AH4AIA=="))
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": "lato",
|
||||
"name": "Lato",
|
||||
"version": "0.01",
|
||||
"description": "A Lato Font clock with fast load and clock_info",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot3.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"lato.app.js","url":"app.js"},
|
||||
{"name":"lato.img","url":"icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.7 KiB |
|
@ -7,6 +7,7 @@
|
|||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"type": "launch",
|
||||
"default": true,
|
||||
"tags": "tool,system,launcher",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
|
|
|
@ -79,3 +79,6 @@
|
|||
Move widget to widmessage
|
||||
0.56: Fix handling of music messages
|
||||
0.57: Fix "unread Timeout" = off (previously defaulted to 60s)
|
||||
0.58: Fast load messages without writing to flash
|
||||
Don't write messages to flash until the app closes
|
||||
0.59: Ensure we do write messages if messages app can't be fast loaded (see #2373)
|
||||
|
|
|
@ -48,6 +48,11 @@ to the clock. */
|
|||
var unreadTimeout;
|
||||
/// List of all our messages
|
||||
var MESSAGES = require("messages").getMessages();
|
||||
if (Bangle.MESSAGES) {
|
||||
// fast loading messages
|
||||
Bangle.MESSAGES.forEach(m => require("messages").apply(m, MESSAGES));
|
||||
delete Bangle.MESSAGES;
|
||||
}
|
||||
|
||||
var onMessagesModified = function(type,msg) {
|
||||
if (msg.handled) return;
|
||||
|
@ -105,7 +110,6 @@ function showMapMessage(msg) {
|
|||
layout.render();
|
||||
function back() { // mark as not new and return to menu
|
||||
msg.new = false;
|
||||
saveMessages();
|
||||
layout = undefined;
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:0});
|
||||
}
|
||||
|
@ -140,7 +144,6 @@ function showMusicMessage(msg) {
|
|||
openMusic = false;
|
||||
var wasNew = msg.new;
|
||||
msg.new = false;
|
||||
saveMessages();
|
||||
layout = undefined;
|
||||
if (wasNew) checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:0,openMusic:0});
|
||||
else checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
|
@ -223,24 +226,20 @@ function showMessageSettings(msg) {
|
|||
},
|
||||
/*LANG*/"Delete" : () => {
|
||||
MESSAGES = MESSAGES.filter(m=>m.id!=msg.id);
|
||||
saveMessages();
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
},
|
||||
/*LANG*/"Mark Unread" : () => {
|
||||
msg.new = true;
|
||||
saveMessages();
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
},
|
||||
/*LANG*/"Mark all read" : () => {
|
||||
MESSAGES.forEach(msg => msg.new = false);
|
||||
saveMessages();
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
},
|
||||
/*LANG*/"Delete all messages" : () => {
|
||||
E.showPrompt(/*LANG*/"Are you sure?", {title:/*LANG*/"Delete All Messages"}).then(isYes => {
|
||||
if (isYes) {
|
||||
MESSAGES = [];
|
||||
saveMessages();
|
||||
}
|
||||
checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0,openMusic:0});
|
||||
});
|
||||
|
@ -295,7 +294,7 @@ function showMessage(msgid) {
|
|||
}
|
||||
function goBack() {
|
||||
layout = undefined;
|
||||
msg.new = false; saveMessages(); // read mail
|
||||
msg.new = false; // read mail
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0,openMusic:openMusic});
|
||||
}
|
||||
|
@ -303,7 +302,7 @@ function showMessage(msgid) {
|
|||
];
|
||||
if (msg.positive) {
|
||||
buttons.push({type:"btn", src:atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="), cb:()=>{
|
||||
msg.new = false; saveMessages();
|
||||
msg.new = false;
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
Bangle.messageResponse(msg,true);
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:openMusic});
|
||||
|
@ -312,7 +311,7 @@ function showMessage(msgid) {
|
|||
if (msg.negative) {
|
||||
if (buttons.length) buttons.push({width:32}); // nasty hack...
|
||||
buttons.push({type:"btn", src:atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="), cb:()=>{
|
||||
msg.new = false; saveMessages();
|
||||
msg.new = false;
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
Bangle.messageResponse(msg,false);
|
||||
checkMessages({clockIfNoMsg:1,clockIfAllRead:1,showMsgIfUnread:1,openMusic:openMusic});
|
||||
|
|
|
@ -18,9 +18,25 @@ exports.listener = function(type, msg) {
|
|||
if (Bangle.CLOCK && msg.state && msg.title && appSettings.openMusic) loadMessages = true;
|
||||
else return;
|
||||
}
|
||||
if (Bangle.load === load || !Bangle.uiRemove) {
|
||||
// no fast loading: store message to flash
|
||||
/* FIXME: Maybe we need a better way of deciding if an app will
|
||||
be fast loaded than just hard-coding a Bangle.uiRemove check.
|
||||
Bangle.load could return a bool (as the load doesn't happen immediately). */
|
||||
require("messages").save(msg);
|
||||
} else {
|
||||
if (!Bangle.MESSAGES) Bangle.MESSAGES = [];
|
||||
Bangle.MESSAGES.push(msg);
|
||||
}
|
||||
const saveToFlash = () => {
|
||||
// save messages from RAM to flash after all, if we decide not to launch app
|
||||
if (!Bangle.MESSAGES) return;
|
||||
Bangle.MESSAGES.forEach(m => require("messages").save(m));
|
||||
delete Bangle.MESSAGES;
|
||||
}
|
||||
msg.handled = true;
|
||||
if ((msg.t!=="add" || !msg.new) && (type!=="music")) { // music always has t:"modify"
|
||||
saveToFlash();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -35,7 +51,11 @@ exports.listener = function(type, msg) {
|
|||
exports.messageTimeout = setTimeout(function() {
|
||||
delete exports.messageTimeout;
|
||||
if (type!=="music") {
|
||||
if (!loadMessages) return require("messages").buzz(msg.src); // no opening the app, just buzz
|
||||
if (!loadMessages) {
|
||||
// not opening the app, just buzz
|
||||
saveToFlash();
|
||||
return require("messages").buzz(msg.src);
|
||||
}
|
||||
if (!quiet && unlockWatch) {
|
||||
Bangle.setLocked(false);
|
||||
Bangle.setLCDPower(1); // turn screen on
|
||||
|
@ -51,10 +71,12 @@ exports.listener = function(type, msg) {
|
|||
*/
|
||||
exports.open = function(msg) {
|
||||
if (msg && msg.id && !msg.show) {
|
||||
// store which message to load
|
||||
msg.show = 1;
|
||||
if (Bangle.load === load) {
|
||||
// no fast loading: store message to load in flash
|
||||
require("messages").save(msg, {force: 1});
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.load((msg && msg.new && msg.id!=="music") ? "messagegui.new.js" : "messagegui.app.js");
|
||||
};
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"id": "messagegui",
|
||||
"name": "Message UI",
|
||||
"version": "0.57",
|
||||
"shortName": "Messages",
|
||||
"version": "0.59",
|
||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
@ -9,6 +10,7 @@
|
|||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"dependencies" : { "messageicons":"module" },
|
||||
"provides_modules": ["messagegui"],
|
||||
"default": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"messagegui","url":"lib.js"},
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: Moved message icons from messages into standalone library
|
||||
0.02: Added several new icons and colors
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"id": "messageicons",
|
||||
"name": "Message Icons",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Library containing a list of icons and colors for apps",
|
||||
"icon": "app.png",
|
||||
"type": "module",
|
||||
"tags": "tool,system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"provides_modules" : ["messageicons"],
|
||||
"default": true,
|
||||
"storage": [
|
||||
{"name":"messageicons","url":"lib.js"}
|
||||
]
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
0.55: Moved messages library into standalone library
|
||||
0.56: Fix handling of music messages
|
||||
0.57: Optimize saving empty message list
|
||||
0.58: show/hide "messages" widget directly, instead of through library stub
|
||||
|
|
|
@ -125,9 +125,10 @@ exports.openGUI = function(msg) {
|
|||
* @param {boolean} show
|
||||
*/
|
||||
exports.toggleWidget = function(show) {
|
||||
if (!require("Storage").read("messagewidget")) return; // "messagewidget" module is missing!
|
||||
if (show) require("messagewidget").show();
|
||||
else require("messagewidget").hide();
|
||||
if (!global.WIDGETS || !WIDGETS["messages"]) return; // widget is missing!
|
||||
const method = WIDGETS["messages"][show ? "show" : "hide"];
|
||||
/* if (typeof(method)!=="function") return; // widget must always have show()+hide(), fail hard rather than hide problems */
|
||||
method.apply(WIDGETS["messages"]);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -135,7 +136,8 @@ exports.toggleWidget = function(show) {
|
|||
* @param {array} messages Messages to save
|
||||
*/
|
||||
exports.write = function(messages) {
|
||||
require("Storage").writeJSON("messages.json", messages.map(m => {
|
||||
if (!messages.length) require("Storage").erase("messages.json");
|
||||
else require("Storage").writeJSON("messages.json", messages.map(m => {
|
||||
// we never want to save saved/handled status to file;
|
||||
delete m.saved;
|
||||
delete m.handled;
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.56",
|
||||
"version": "0.58",
|
||||
"description": "Library to handle, load and store message events received from Android/iOS",
|
||||
"icon": "app.png",
|
||||
"type": "module",
|
||||
"tags": "tool,system",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"provides_modules" : ["messages"],
|
||||
"dependencies" : { "messagegui":"module","messagewidget":"module" },
|
||||
"dependencies" : {
|
||||
"messagegui":"module",
|
||||
"message":"widget"
|
||||
},
|
||||
"default": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"messages","url":"lib.js"},
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Initial version
|
||||
0.02: Use default Bangle formatter for booleans
|
||||
0.03: Drop duplicate alarm widget
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "noteify",
|
||||
"name": "Noteify",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Write notes using an onscreen keyboard and use them as custom messages for alarms or timers.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm",
|
||||
|
@ -9,8 +9,7 @@
|
|||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"noteify.app.js","url":"app.js"},
|
||||
{"name":"noteify.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"noteify.wid.js","url":"widget.js"}
|
||||
{"name":"noteify.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"noteify.json"}],
|
||||
"dependencies": {"scheduler":"type","textinput":"type"},
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
|
||||
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
},reload:function() {
|
||||
// don't include library here as we're trying to use as little RAM as possible
|
||||
WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0;
|
||||
}
|
||||
};
|
||||
WIDGETS["alarm"].reload();
|
|
@ -19,3 +19,4 @@
|
|||
0.16: make check_idle boolean setting work properly with new B2 menu
|
||||
0.17: Use default Bangle formatter for booleans
|
||||
0.18: fix idle option always getting defaulted to true
|
||||
0.19: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "pastel",
|
||||
"name": "Pastel Clock",
|
||||
"shortName": "Pastel",
|
||||
"version": "0.18",
|
||||
"version": "0.19",
|
||||
"description": "A Configurable clock with custom fonts, background and weather display. Has a cyclic information line that includes, day, date, battery, sunrise and sunset times",
|
||||
"icon": "pastel.png",
|
||||
"dependencies": {"mylocation":"app","weather":"app"},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||
var SunCalc = require("suncalc"); // from modules folder
|
||||
require("f_latosmall").add(Graphics);
|
||||
const storage = require('Storage');
|
||||
const locale = require("locale");
|
||||
|
|
|
@ -4,3 +4,6 @@
|
|||
Addict.
|
||||
0.04: New layout.
|
||||
0.05: Add widget field, tweak layout.
|
||||
0.06: Add compatibility with Fastload Utils.
|
||||
0.07: Remove just the specific listeners to not interfere with Quick Launch
|
||||
when fastloading.
|
||||
|
|
|
@ -1,77 +1,20 @@
|
|||
{
|
||||
/*
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}}));
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}}));
|
||||
|
||||
Podcast Addict is developed by Xavier Guillemane and can be downloaded on Google Play Store: https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=en_US&gl=US
|
||||
Podcast Addict is developed by Xavier Guillemane and can be downloaded on Google Play Store: https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict&hl=en_US&gl=US
|
||||
|
||||
Podcast Addict can be controlled through the sending of remote commands called 'Intents'.
|
||||
Some 3rd parties apps specialized in task automation will then allow you to control Podcast Addict. For example, you will be able to wake up to the sound of your playlist or to start automatically playing when some NFC tag has been detected.
|
||||
In Tasker, you just need to copy/paste one of the following intent in the task Action field ("Misc" action type then select "Send Itent") .
|
||||
If you prefer Automate It, you can use the Podcast Addict plugin that will save you some configuration time (https://play.google.com/store/apps/details?id=com.smarterapps.podcastaddictplugin )
|
||||
Before using an intent make sure to set the following:
|
||||
Package: com.bambuna.podcastaddict
|
||||
Class (UPDATE intent only): com.bambuna.podcastaddict.receiver.PodcastAddictBroadcastReceiver
|
||||
Class (every other intent): com.bambuna.podcastaddict.receiver.PodcastAddictPlayerReceiver
|
||||
Here are the supported commands (Intents) :
|
||||
com.bambuna.podcastaddict.service.player.toggle – Toggle the playlist
|
||||
com.bambuna.podcastaddict.service.player.stop – Stop the player and release its resources
|
||||
com.bambuna.podcastaddict.service.player.play – Start playing the playlist
|
||||
com.bambuna.podcastaddict.service.player.pause – Pause the playlist
|
||||
com.bambuna.podcastaddict.service.player.nexttrack – Start playing next track
|
||||
com.bambuna.podcastaddict.service.player.previoustrack – Start playing previous track
|
||||
com.bambuna.podcastaddict.service.player.jumpforward – Jump 30s forward
|
||||
com.bambuna.podcastaddict.service.player.jumpbackward – Jump 15s backward
|
||||
com.bambuna.podcastaddict.service.player.1xspeed - Disable the variable playback speed
|
||||
com.bambuna.podcastaddict.service.player.1.5xspeed – Force the playback speed at 1.5x
|
||||
com.bambuna.podcastaddict.service.player.2xspeed – Force the playback speed at 2.0x
|
||||
com.bambuna.podcastaddict.service.player.stoptimer – Disable the timer
|
||||
com.bambuna.podcastaddict.service.player.15mntimer – Set the timer at 15 minutes
|
||||
com.bambuna.podcastaddict.service.player.30mntimer – Set the timer at 30 minutes
|
||||
com.bambuna.podcastaddict.service.player.60mntimer – Set the timer at 1 hour
|
||||
com.bambuna.podcastaddict.service.update – Trigger podcasts update
|
||||
com.bambuna.podcastaddict.openmainscreen – Open the app on the Main screen
|
||||
com.bambuna.podcastaddict.openplaylist – Open the app on the Playlist screen
|
||||
com.bambuna.podcastaddict.openplayer – Open the app on the Player screen
|
||||
com.bambuna.podcastaddict.opennewepisodes – Open the app on the New episodes screen
|
||||
com.bambuna.podcastaddict.opendownloadedepisodes – Open the app on the Downloaded episodes screen
|
||||
com.bambuna.podcastaddict.service.player.playfirstepisode – Start playing the first episode in the playlist
|
||||
com.bambuna.podcastaddict.service.player.customspeed – Select playback speed
|
||||
In order to use this intent you need to pass a float argument called "arg1". Valid values are within [0.1, 5.0]
|
||||
com.bambuna.podcastaddict.service.player.customtimer – Start a custom timer
|
||||
In order to use this intent you need to pass an int argument called "arg1" containing the number of minutes. Valid values are within [1, 1440]
|
||||
com.bambuna.podcastaddict.service.player.deletecurrentskipnexttrack – Delete the current episode and skip to the next one. It behaves the same way as long pressing on the player >| button, but doesn't display any confirmation popup.
|
||||
com.bambuna.podcastaddict.service.player.deletecurrentskipprevioustrack – Delete the current episode and skip to the previous one. It behaves the same way as long pressing on the player |< button, but doesn't display any confirmation popup.
|
||||
com.bambuna.podcastaddict.service.player.boostVolume – Toggle the Volume Boost audio effect
|
||||
You can pass a, optional boolean argument called "arg1" in order to create a ON or OFF button for the volume boost. Without this parameter the app will just toggle the current value
|
||||
com.bambuna.podcastaddict.service.player.quickBookmark – Creates a bookmark at the current playback position so you can easily retrieve it later.
|
||||
com.bambuna.podcastaddict.service.download.pause – Pause downloads
|
||||
com.bambuna.podcastaddict.service.download.resume – Resume downloads
|
||||
com.bambuna.podcastaddict.service. download.toggle – Toggle downloads
|
||||
com.bambuna.podcastaddict.service.player.favorite – Mark the current episode playing as favorite.
|
||||
com.bambuna.podcastaddict.openplaylist – Open the app on the Playlist screen
|
||||
You can pass an optional string argument called "arg1" in order to select the playlist to open. Without this parameter the app will open the current playlist
|
||||
Here's how it works:
|
||||
##AUDIO## will open the Audio playlist screen
|
||||
##VIDEO## will open the Video playlist screen
|
||||
##RADIO## will open the Radio screen
|
||||
Any other argument will be used as a CATEGORY name. The app will then open this category under the playlist CUSTOM tab
|
||||
You can pass an optional boolean argument called "arg2" in order to select if the app UI should be opened. Without this parameter the playlist will be displayed
|
||||
You can pass an optional boolean argument called "arg3" in order to select if the app should start playing the selected playlist. Without this parameter the playback won't start
|
||||
Since v2020.3
|
||||
com.bambuna.podcastaddict.service.full_backup – Trigger a full backup of the app data (relies on the app automatic backup settings for the folder and the # of backup to keep)
|
||||
This task takes a lot of resources and might take up to a minute to complete, so please avoid using the app at the same time
|
||||
Since v2020.15
|
||||
com.bambuna.podcastaddict.service.player.toggletimer – This will toggle the Sleep Timer using the last duration and parameter used in the app.
|
||||
Since v2020.16
|
||||
com.bambuna.podcastaddict.service.player.togglespeed – This will toggle the Playback speed for the episode currently playing (alternate between selected speed and 1.0x).
|
||||
How to use intents to control Podcast Addict: https://podcastaddict.com/faq/130
|
||||
*/
|
||||
|
||||
var R;
|
||||
var backToMenu = false;
|
||||
var dark = g.theme.dark; // bool
|
||||
let R;
|
||||
let widgetUtils = require("widget_utils");
|
||||
let backToMenu = false;
|
||||
let dark = g.theme.dark; // bool
|
||||
|
||||
// The main layout of the app
|
||||
function gfx() {
|
||||
//Bangle.drawWidgets();
|
||||
let gfx = function() {
|
||||
widgetUtils.hide();
|
||||
R = Bangle.appRect;
|
||||
marigin = 8;
|
||||
// g.drawString(str, x, y, solid)
|
||||
|
@ -106,19 +49,19 @@ function gfx() {
|
|||
|
||||
g.setFontAlign(1, 1, 0);
|
||||
g.drawString("Speed", R.x + R.w - 2*marigin, R.y + R.h - 2*marigin);
|
||||
}
|
||||
};
|
||||
|
||||
// Touch handler for main layout
|
||||
function touchHandler(_, xy) {
|
||||
let touchHandler = function(_, xy) {
|
||||
x = xy.x;
|
||||
y = xy.y;
|
||||
len = (R.w<R.h+1)?(R.w/3):(R.h/3);
|
||||
|
||||
// doing a<b+1 seemed faster than a<=b, also using a>b-1 instead of a>b.
|
||||
// doing a<b+1 seemed faster than a<=b, also using a>b-1 instead of a>=b.
|
||||
if ((R.x-1<x && x<R.x+len) && (R.y-1<y && y<R.y+len)) {
|
||||
//Menu
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
Bangle.removeListener("touch", touchHandler);
|
||||
Bangle.removeListener("swipe", swipeHandler);
|
||||
backToMenu = true;
|
||||
E.showMenu(paMenu);
|
||||
} else if ((R.x-1<x && x<R.x+len) && (R.y2-len<y && y<R.y2+1)) {
|
||||
|
@ -127,13 +70,13 @@ function touchHandler(_, xy) {
|
|||
gadgetbridgeWake();
|
||||
} else if ((R.x2-len<x && x<R.x2+1) && (R.y-1<y && y<R.y+len)) {
|
||||
//Srch
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
Bangle.removeListener("touch", touchHandler);
|
||||
Bangle.removeListener("swipe", swipeHandler);
|
||||
E.showMenu(searchMenu);
|
||||
} else if ((R.x2-len<x && x<R.x2+1) && (R.y2-len<y && y<R.y2+1)) {
|
||||
//Speed
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
Bangle.removeListener("touch", touchHandler);
|
||||
Bangle.removeListener("swipe", swipeHandler);
|
||||
E.showMenu(speedMenu);
|
||||
} else if ((R.x-1<x && x<R.x+len) && (R.y+R.h/2-len/2<y && y<R.y+R.h/2+len/2)) {
|
||||
//Previous
|
||||
|
@ -145,43 +88,51 @@ function touchHandler(_, xy) {
|
|||
//play/pause
|
||||
btMsg("service", standardCls, "player.toggle");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Swipe handler for main layout, used to jump backward and forward within a podcast episode.
|
||||
function swipeHandler(LR, _) {
|
||||
let swipeHandler = function(LR, _) {
|
||||
if (LR==-1) {
|
||||
btMsg("service", standardCls, "player.jumpforward");
|
||||
}
|
||||
if (LR==1) {
|
||||
btMsg("service", standardCls, "player.jumpbackward");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Navigation input on the main layout
|
||||
function setUI() {
|
||||
// Bangle.setUI code from rigrig's smessages app for volume control: https://git.tubul.net/rigrig/BangleApps/src/branch/personal/apps/smessages/app.js
|
||||
let setUI = function() {
|
||||
// Bangle.setUI code from rigrig's smessages app for volume control: https://git.tubul.net/rigrig/BangleApps/src/branch/personal/apps/smessages/app.js
|
||||
Bangle.setUI(
|
||||
{mode : "updown", back : load},
|
||||
{mode : "updown",
|
||||
remove : ()=>{
|
||||
Bangle.removeListener("touch", touchHandler);
|
||||
Bangle.removeListener("swipe", swipeHandler);
|
||||
clearWatch(buttonHandler);
|
||||
widgetUtils.show();
|
||||
}
|
||||
},
|
||||
ud => {
|
||||
if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup");
|
||||
}
|
||||
);
|
||||
Bangle.on("touch", touchHandler);
|
||||
Bangle.on("swipe", swipeHandler);
|
||||
}
|
||||
let buttonHandler = setWatch(()=>{load();}, BTN, {edge:'falling'});
|
||||
};
|
||||
|
||||
/*
|
||||
The functions for interacting with Android and the Podcast Addict app
|
||||
*/
|
||||
|
||||
pkg = "com.bambuna.podcastaddict";
|
||||
standardCls = pkg + ".receiver.PodcastAddictPlayerReceiver";
|
||||
updateCls = pkg + ".receiver.PodcastAddictBroadcastReceiver";
|
||||
speed = 1.0;
|
||||
let pkg = "com.bambuna.podcastaddict";
|
||||
let standardCls = pkg + ".receiver.PodcastAddictPlayerReceiver";
|
||||
let updateCls = pkg + ".receiver.PodcastAddictBroadcastReceiver";
|
||||
let speed = 1.0;
|
||||
|
||||
simpleSearch = "";
|
||||
let simpleSearch = "";
|
||||
|
||||
function simpleSearchTerm() { // input a simple search term without tags, overrides search with tags (artist and track)
|
||||
let simpleSearchTerm = function() { // input a simple search term without tags, overrides search with tags (artist and track)
|
||||
require("textinput").input({
|
||||
text: simpleSearch
|
||||
}).then(result => {
|
||||
|
@ -189,9 +140,9 @@ function simpleSearchTerm() { // input a simple search term without tags, overri
|
|||
}).then(() => {
|
||||
E.showMenu(searchMenu);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function searchPlayWOTags() { //make a search and play using entered terms
|
||||
let searchPlayWOTags = function() { //make a search and play using entered terms
|
||||
searchString = simpleSearch;
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t: "intent",
|
||||
|
@ -203,9 +154,9 @@ function searchPlayWOTags() { //make a search and play using entered terms
|
|||
},
|
||||
flags: ["FLAG_ACTIVITY_NEW_TASK"]
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
function gadgetbridgeWake() {
|
||||
let gadgetbridgeWake = function() {
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t: "intent",
|
||||
target: "activity",
|
||||
|
@ -213,15 +164,15 @@ function gadgetbridgeWake() {
|
|||
package: "gadgetbridge",
|
||||
class: "nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// For stringing together the action for Podcast Addict to perform
|
||||
function actFn(actName, activOrServ) {
|
||||
let actFn = function(actName, activOrServ) {
|
||||
return "com.bambuna.podcastaddict." + (activOrServ == "service" ? "service." : "") + actName;
|
||||
}
|
||||
};
|
||||
|
||||
// Send the intent message to Gadgetbridge
|
||||
function btMsg(activOrServ, cls, actName, xtra) {
|
||||
let btMsg = function(activOrServ, cls, actName, xtra) {
|
||||
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t: "intent",
|
||||
|
@ -231,22 +182,20 @@ function btMsg(activOrServ, cls, actName, xtra) {
|
|||
target: "broadcastreceiver",
|
||||
extra: xtra
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
// Get back to the main layout
|
||||
function backToGfx() {
|
||||
let backToGfx = function() {
|
||||
E.showMenu();
|
||||
g.clear();
|
||||
g.reset();
|
||||
Bangle.removeAllListeners("touch");
|
||||
Bangle.removeAllListeners("swipe");
|
||||
setUI();
|
||||
gfx();
|
||||
backToMenu = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Podcast Addict Menu
|
||||
var paMenu = {
|
||||
let paMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
back: backToGfx
|
||||
|
@ -271,7 +220,7 @@ var paMenu = {
|
|||
};
|
||||
|
||||
|
||||
var controlMenu = {
|
||||
let controlMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
back: () => {if (backToMenu) E.showMenu(paMenu);
|
||||
|
@ -310,7 +259,7 @@ var controlMenu = {
|
|||
},
|
||||
};
|
||||
|
||||
var speedMenu = {
|
||||
let speedMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
back: () => {if (backToMenu) E.showMenu(paMenu);
|
||||
|
@ -333,7 +282,7 @@ var speedMenu = {
|
|||
//"Slower" : ()=>{speed-=0.1; speed=((speed<0.1)?0.1:speed); btMsg("service",standardCls,"player.customspeed",{arg1:speed});},
|
||||
};
|
||||
|
||||
var searchMenu = {
|
||||
let searchMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
|
||||
|
@ -356,7 +305,7 @@ var searchMenu = {
|
|||
"Simpler search and play" : searchPlayWOTags,
|
||||
};
|
||||
|
||||
var navigationMenu = {
|
||||
let navigationMenu = {
|
||||
"": {
|
||||
title: " ",
|
||||
back: () => {if (backToMenu) E.showMenu(paMenu);
|
||||
|
@ -372,4 +321,6 @@ var navigationMenu = {
|
|||
|
||||
Bangle.loadWidgets();
|
||||
setUI();
|
||||
widgetUtils.hide();
|
||||
gfx();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "podadrem",
|
||||
"name": "Podcast Addict Remote",
|
||||
"shortName": "PA Remote",
|
||||
"version": "0.05",
|
||||
"version": "0.07",
|
||||
"description": "Control Podcast Addict on your android device.",
|
||||
"readme": "README.md",
|
||||
"type": "app",
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"presentor.app.js","url":"app.js"},
|
||||
{"name":"presentor.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"presentor.img","url":"app-icon.js","evaluate":true}
|
||||
], "data": [
|
||||
{"name":"presentor.json","url":"settings.json"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
0.01: first release
|
||||
0.02: added option to buzz on prime, with settings
|
||||
0.03: added option to debug settings and test fw 2.15.93 load speed ups
|
||||
0.04: changed icon
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Prime Time Lato (clock)
|
||||
# Prime Lato (clock)
|
||||
|
||||
A watchface that displays time and its prime factors in the Lato font.
|
||||
For example when the time is 21:05, the prime factors are 5,421.
|
||||
|
|
|
@ -2,6 +2,7 @@ const h = g.getHeight();
|
|||
const w = g.getWidth();
|
||||
const SETTINGS_FILE = "primetimelato.json";
|
||||
let settings;
|
||||
let setStr = 'U';
|
||||
|
||||
Graphics.prototype.setFontLato = function(scale) {
|
||||
// Actual height 41 (43 - 3)
|
||||
|
@ -28,6 +29,22 @@ Graphics.prototype.setFontLatoSmall = function(scale) {
|
|||
function loadSettings() {
|
||||
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
||||
settings.buzz_on_prime = (settings.buzz_on_prime === undefined ? false : settings.buzz_on_prime);
|
||||
settings.debug = (settings.debug === undefined ? false : settings.debug);
|
||||
|
||||
switch(settings.buzz_on_prime) {
|
||||
case true:
|
||||
setStr = 'T';
|
||||
break;
|
||||
|
||||
case false:
|
||||
setStr = 'F';
|
||||
break;
|
||||
|
||||
case undefined:
|
||||
default:
|
||||
setStr = 'U';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// creates a list of prime factors of n and outputs them as a string, if n is prime outputs "Prime Time!"
|
||||
|
@ -69,9 +86,16 @@ function draw() {
|
|||
g.setColor(0,0,0);
|
||||
g.fillRect(Bangle.appRect);
|
||||
|
||||
g.setColor(100,100,100);
|
||||
|
||||
if (settings.debug) {
|
||||
g.setFontLatoSmall();
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString(setStr, w/2, h/4);
|
||||
}
|
||||
|
||||
g.setFontLato();
|
||||
g.setFontAlign(0, 0);
|
||||
g.setColor(100,100,100);
|
||||
g.drawString(timeStr, w/2, h/2);
|
||||
|
||||
g.setFontLatoSmall();
|
||||
|
|
Before Width: | Height: | Size: 710 B After Width: | Height: | Size: 1.4 KiB |