Merge branch 'espruino:master' into Messages-Light
|
@ -14,3 +14,4 @@ _site
|
|||
.owncloudsync.log
|
||||
Desktop.ini
|
||||
.sync_*.db*
|
||||
*.swp
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -11,3 +11,4 @@
|
|||
0.11: Bangle.js2: New pixels, btn1 to exit
|
||||
0.12: Actual pixels as of 29th Nov 2021
|
||||
0.13: Bangle.js 2: Use setUI to add software back button
|
||||
0.14: Add automatic translation of more strings
|
||||
|
|
|
@ -11,8 +11,8 @@ g.drawString("BANGLEJS.COM",120,y-4);
|
|||
} else {
|
||||
y=-(4+h); // small screen, start right at top
|
||||
}
|
||||
g.drawString("Powered by Espruino",0,y+=4+h);
|
||||
g.drawString("Version "+ENV.VERSION,0,y+=h);
|
||||
g.drawString(/*LANG*/"Powered by Espruino",0,y+=4+h);
|
||||
g.drawString(/*LANG*/"Version "+ENV.VERSION,0,y+=h);
|
||||
g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h);
|
||||
function getVersion(name,file) {
|
||||
var j = s.readJSON(file,1);
|
||||
|
@ -24,9 +24,9 @@ getVersion("Launcher","launch.info");
|
|||
getVersion("Settings","setting.info");
|
||||
|
||||
y+=h;
|
||||
g.drawString(MEM.total+" JS Variables available",0,y+=h);
|
||||
g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h);
|
||||
if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h);
|
||||
g.drawString(MEM.total+/*LANG*/" JS Variables available",0,y+=h);
|
||||
g.drawString("Storage: "+(require("Storage").getFree()>>10)+/*LANG*/"k free",0,y+=h);
|
||||
if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+/*LANG*/"k total",0,y+=h);
|
||||
if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h);
|
||||
g.setFontAlign(0,-1);
|
||||
g.flip();
|
||||
|
|
|
@ -35,17 +35,17 @@ function drawInfo() {
|
|||
g.setFont("4x6").setFontAlign(0,0).drawString("BANGLEJS.COM",W-30,56);
|
||||
var h=8, y = 24-h;
|
||||
g.setFont("6x8").setFontAlign(-1,-1);
|
||||
g.drawString("Powered by Espruino",0,y+=4+h);
|
||||
g.drawString("Version "+ENV.VERSION,0,y+=h);
|
||||
g.drawString(/*LANG*/"Powered by Espruino",0,y+=4+h);
|
||||
g.drawString(/*LANG*/"Version "+ENV.VERSION,0,y+=h);
|
||||
g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h);
|
||||
|
||||
getVersion("Bootloader","boot.info");
|
||||
getVersion("Launcher","launch.info");
|
||||
getVersion("Settings","setting.info");
|
||||
|
||||
g.drawString(MEM.total+" JS Vars",0,y+=h);
|
||||
g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h);
|
||||
if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h);
|
||||
g.drawString(MEM.total+/*LANG*/" JS Vars",0,y+=h);
|
||||
g.drawString("Storage: "+(require("Storage").getFree()>>10)+/*LANG*/"k free",0,y+=h);
|
||||
if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+/*LANG*/"k total",0,y+=h);
|
||||
if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h);
|
||||
imageTop = y+h;
|
||||
imgScroll = imgHeight-imageTop;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "about",
|
||||
"name": "About",
|
||||
"version": "0.13",
|
||||
"version": "0.14",
|
||||
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
# Active Pedometer
|
||||
|
||||
Pedometer that filters out arm movement and displays a step goal progress.
|
||||
|
||||
**Note:** Since creation of this app, Bangle.js's step counting algorithm has
|
||||
improved significantly - and as a result the algorithm in this app (which
|
||||
runs *on top* of Bangle.js's algorithm) may no longer be accurate.
|
||||
|
||||
I changed the step counting algorithm completely.
|
||||
Now every step is counted when in status 'active', if the time difference between two steps is not too short or too long.
|
||||
To get in 'active' mode, you have to reach the step threshold before the active timer runs out.
|
||||
|
@ -9,6 +14,7 @@ When you reach the step threshold, the steps needed to reach the threshold are c
|
|||
Steps are saved to a datafile every 5 minutes. You can watch a graph using the app.
|
||||
|
||||
## Screenshots
|
||||
|
||||
* 600 steps
|
||||

|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Active Pedometer",
|
||||
"shortName": "Active Pedometer",
|
||||
"version": "0.09",
|
||||
"description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.",
|
||||
"description": "(NOT RECOMMENDED) Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph. The `Health` app now provides step logging and graphs.",
|
||||
"icon": "app.png",
|
||||
"tags": "outdoors,widget",
|
||||
"supports": ["BANGLEJS"],
|
||||
|
|
|
@ -5,3 +5,7 @@
|
|||
0.05: Displaying calendar colour and name
|
||||
0.06: Added clkinfo for clocks.
|
||||
0.07: Clkinfo improvements.
|
||||
0.08: Fix error in clkinfo (didn't require Storage & locale)
|
||||
Fix clkinfo icon
|
||||
0.09: Ensure Agenda supplies an image for clkinfo items
|
||||
0.10: Update clock_info to avoid a redraw
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
(function() {
|
||||
var agendaItems = {
|
||||
name: "Agenda",
|
||||
img: atob("GBiBAf////////85z/AAAPAAAPgAAP////AAAPAAAPAAAPAAAOAAAeAAAeAAAcAAA8AAAoAABgAADP//+P//8PAAAPAAAPgAAf///w=="),
|
||||
img: atob("GBiBAAAAAAAAAADGMA///w///wf//wAAAA///w///w///w///x///h///h///j///D///X//+f//8wAABwAADw///w///wf//gAAAA=="),
|
||||
items: []
|
||||
};
|
||||
|
||||
var locale = require("locale");
|
||||
var now = new Date();
|
||||
var agenda = storage.readJSON("android.calendar.json")
|
||||
var agenda = require("Storage").readJSON("android.calendar.json")
|
||||
.filter(ev=>ev.timestamp + ev.durationInSeconds > now/1000)
|
||||
.sort((a,b)=>a.timestamp - b.timestamp);
|
||||
|
||||
agenda.forEach((entry, i) => {
|
||||
|
||||
var title = entry.title.slice(0,18);
|
||||
var title = entry.title.slice(0,12);
|
||||
var date = new Date(entry.timestamp*1000);
|
||||
var dateStr = locale.date(date).replace(/\d\d\d\d/,"");
|
||||
dateStr += entry.durationInSeconds < 86400 ? "/ " + locale.time(date,1) : "";
|
||||
|
||||
agendaItems.items.push({
|
||||
name: null,
|
||||
get: () => ({ text: title + "\n" + dateStr, img: null}),
|
||||
show: function() { agendaItems.items[i].emit("redraw"); },
|
||||
name: "Agenda "+i,
|
||||
get: () => ({ text: title + "\n" + dateStr, img: agendaItems.img }),
|
||||
show: function() {},
|
||||
hide: function () {}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "agenda",
|
||||
"name": "Agenda",
|
||||
"version": "0.07",
|
||||
"version": "0.10",
|
||||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
0.01: New app!
|
||||
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.
|
||||
*/
|
||||
|
@ -215,7 +427,7 @@ Bangle.loadWidgets();
|
|||
* so we will blank out the draw() functions of each widget and change the
|
||||
* area to the top bar doesn't get cleared.
|
||||
*/
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
require('widget_utils').hide();
|
||||
|
||||
// Clear the screen once, at startup and draw clock
|
||||
g.setTheme({bg:"#fff",fg:"#000",dark:false}).clear();
|
||||
|
|
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.03",
|
||||
"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"},
|
||||
|
|
|
@ -34,4 +34,6 @@
|
|||
0.32: Fix wrong hidden filter
|
||||
Add option for auto-delete a timer after it expires
|
||||
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
|
||||
|
|
|
@ -128,7 +128,12 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex) {
|
|||
value: alarm.hidden || false,
|
||||
onchange: v => alarm.hidden = v
|
||||
},
|
||||
/*LANG*/"Cancel": () => showMainMenu()
|
||||
/*LANG*/"Cancel": () => showMainMenu(),
|
||||
/*LANG*/"Confirm": () => {
|
||||
prepareAlarmForSave(alarm, alarmIndex, time);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
}
|
||||
};
|
||||
|
||||
if (!isNew) {
|
||||
|
@ -178,7 +183,7 @@ function decodeDOW(alarm) {
|
|||
.map((day, index) => alarm.dow & (1 << (index + firstDayOfWeek)) ? day : "_")
|
||||
.join("")
|
||||
.toLowerCase()
|
||||
: "Once"
|
||||
: /*LANG*/"Once"
|
||||
}
|
||||
|
||||
function showEditRepeatMenu(repeat, dow, dowChangeCallback) {
|
||||
|
@ -293,7 +298,12 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
|
|||
onchange: v => timer.hidden = v
|
||||
},
|
||||
/*LANG*/"Vibrate": require("buzz_menu").pattern(timer.vibrate, v => timer.vibrate = v),
|
||||
/*LANG*/"Cancel": () => showMainMenu()
|
||||
/*LANG*/"Cancel": () => showMainMenu(),
|
||||
/*LANG*/"Confirm": () => {
|
||||
prepareTimerForSave(timer, timerIndex, time);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
}
|
||||
};
|
||||
|
||||
if (!isNew) {
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.33",
|
||||
"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" },
|
||||
|
|
|
@ -14,3 +14,7 @@
|
|||
0.14: Fix timeout of http function not being cleaned up
|
||||
0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later)
|
||||
0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152)
|
||||
0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge
|
||||
0.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.
|
||||
|
|
|
@ -20,6 +20,8 @@ It contains:
|
|||
of Gadgetbridge - making your phone make noise so you can find it.
|
||||
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
||||
keep any messages it has received, or should it delete them?
|
||||
* `Overwrite GPS` - when GPS is requested by an app, this doesn't use Bangle.js's GPS
|
||||
but instead asks Gadgetbridge on the phone to use the phone's GPS
|
||||
* `Messages` - launches the messages app, showing a list of messages
|
||||
|
||||
## How it works
|
||||
|
|
|
@ -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() {
|
||||
|
@ -105,7 +105,7 @@
|
|||
"calendar-" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
//if any of those happen we are out of sync!
|
||||
if (!cal || !Array.isArray(cal)) return;
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
cal = cal.filter(e=>e.id!=event.id);
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
|
@ -126,6 +126,18 @@
|
|||
request.j(event.err); //r = reJect function
|
||||
else
|
||||
request.r(event); //r = resolve function
|
||||
},
|
||||
"gps": function() {
|
||||
const settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
if (!settings.overwriteGps) return;
|
||||
delete event.t;
|
||||
event.satellites = NaN;
|
||||
event.course = NaN;
|
||||
event.fix = 1;
|
||||
Bangle.emit('gps', event);
|
||||
},
|
||||
"is_gps_active": function() {
|
||||
gbSend({ t: "gps_power", status: Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0 });
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
|
@ -136,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) {
|
||||
|
@ -166,7 +178,10 @@
|
|||
|
||||
// Battery monitor
|
||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
NRF.on("connect", () => setTimeout(function() {
|
||||
sendBattery();
|
||||
GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process
|
||||
}, 2000));
|
||||
Bangle.on("charging", sendBattery);
|
||||
if (!settings.keep)
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
|
@ -186,6 +201,30 @@
|
|||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
// error/warn here?
|
||||
};
|
||||
// GPS overwrite logic
|
||||
if (settings.overwriteGps) { // if the overwrite option is set../
|
||||
// Save current logic
|
||||
const originalSetGpsPower = Bangle.setGPSPower;
|
||||
// Replace set GPS power logic to suppress activation of gps (and instead request it from the phone)
|
||||
Bangle.setGPSPower = (isOn, appID) => {
|
||||
// if not connected, use old logic
|
||||
if (!NRF.getSecurityStatus().connected) return originalSetGpsPower(isOn, appID);
|
||||
// Emulate old GPS power logic
|
||||
if (!Bangle._PWR) Bangle._PWR={};
|
||||
if (!Bangle._PWR.GPS) Bangle._PWR.GPS=[];
|
||||
if (!appID) appID="?";
|
||||
if (isOn && !Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.push(appID);
|
||||
if (!isOn && Bangle._PWR.GPS.includes(appID)) Bangle._PWR.GPS.splice(Bangle._PWR.GPS.indexOf(appID),1);
|
||||
let pwr = Bangle._PWR.GPS.length>0;
|
||||
gbSend({ t: "gps_power", status: pwr });
|
||||
return pwr;
|
||||
}
|
||||
// Replace check if the GPS is on to check the _PWR variable
|
||||
Bangle.isGPSOn = () => {
|
||||
return Bangle._PWR && Bangle._PWR.GPS && Bangle._PWR.GPS.length>0;
|
||||
}
|
||||
}
|
||||
|
||||
// remove settings object so it's not taking up RAM
|
||||
delete settings;
|
||||
})();
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.16",
|
||||
"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",
|
||||
"dependencies": {"messages":"app"},
|
||||
"dependencies": {"messages":"module"},
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
(function(back) {
|
||||
|
||||
|
||||
|
||||
function gb(j) {
|
||||
Bluetooth.println(JSON.stringify(j));
|
||||
}
|
||||
|
@ -23,7 +26,17 @@
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Messages" : ()=>load("messages.app.js"),
|
||||
/*LANG*/"Overwrite GPS" : {
|
||||
value : !!settings.overwriteGps,
|
||||
onchange: newValue => {
|
||||
if (newValue) {
|
||||
Bangle.setGPSPower(false, 'android');
|
||||
}
|
||||
settings.overwriteGps = newValue;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Messages" : ()=>require("message").openGUI(),
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
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
|
||||
0.04: Compatibility with Bangle.js 2, get location from My Location
|
||||
|
|
|
@ -9,10 +9,9 @@
|
|||
* 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);
|
||||
const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
|
||||
|
||||
function drawMoon(phase, x, y) {
|
||||
const moonImgFiles = [
|
||||
|
@ -73,7 +72,7 @@ function drawTitle(key) {
|
|||
*/
|
||||
function drawPoint(angle, radius, color) {
|
||||
const pRad = Math.PI / 180;
|
||||
const faceWidth = 80; // watch face radius
|
||||
const faceWidth = g.getWidth()/3; // watch face radius
|
||||
const centerPx = g.getWidth() / 2;
|
||||
|
||||
const a = angle * pRad;
|
||||
|
@ -141,6 +140,7 @@ function drawData(title, obj, startX, startY) {
|
|||
|
||||
function drawMoonPositionPage(gps, title) {
|
||||
const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon);
|
||||
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
|
||||
|
||||
const pageData = {
|
||||
Azimuth: pos.azimuth.toFixed(2),
|
||||
|
@ -150,59 +150,61 @@ function drawMoonPositionPage(gps, title) {
|
|||
};
|
||||
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
|
||||
|
||||
drawData(title, pageData, null, 80);
|
||||
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20);
|
||||
drawPoints();
|
||||
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
drawPoint(azimuthDegrees, 8, moonColor);
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repeat: false, edge: "falling"});
|
||||
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
|
||||
}
|
||||
|
||||
function drawMoonIlluminationPage(gps, title) {
|
||||
const phaseNames = [
|
||||
"New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
|
||||
"Full Moon", "Waning Gibbous", "Last Quater", "Waning Crescent",
|
||||
/*LANG*/"New Moon", /*LANG*/"Waxing Crescent", /*LANG*/"First Quarter", /*LANG*/"Waxing Gibbous",
|
||||
/*LANG*/"Full Moon", /*LANG*/"Waning Gibbous", /*LANG*/"Last Quater", /*LANG*/"Waning Crescent",
|
||||
];
|
||||
|
||||
const phase = SunCalc.getMoonIllumination(new Date());
|
||||
const phaseIdx = Math.round(phase.phase*8);
|
||||
const pageData = {
|
||||
Phase: phaseNames[phase.phase],
|
||||
Phase: phaseNames[phaseIdx],
|
||||
};
|
||||
|
||||
drawData(title, pageData, null, 35);
|
||||
drawMoon(phase.phase, g.getWidth() / 2, g.getHeight() / 2);
|
||||
drawMoon(phaseIdx, g.getWidth() / 2, g.getHeight() / 2);
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repease: false, edge: "falling"});
|
||||
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
|
||||
}
|
||||
|
||||
|
||||
function drawMoonTimesPage(gps, title) {
|
||||
const times = SunCalc.getMoonTimes(new Date(), gps.lat, gps.lon);
|
||||
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
|
||||
|
||||
const pageData = {
|
||||
Rise: dateToTimeString(times.rise),
|
||||
Set: dateToTimeString(times.set),
|
||||
};
|
||||
|
||||
drawData(title, pageData, null, 105);
|
||||
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
|
||||
drawPoints();
|
||||
|
||||
// Draw the moon rise position
|
||||
const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon);
|
||||
const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI);
|
||||
drawPoint(riseAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
drawPoint(riseAzimuthDegrees, 8, moonColor);
|
||||
|
||||
// Draw the moon set position
|
||||
const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon);
|
||||
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
|
||||
drawPoint(setAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
drawPoint(setAzimuthDegrees, 8, moonColor);
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repease: false, edge: "falling"});
|
||||
}, BANGLEJS2 ? BTN : BTN3, {repease: false, edge: "falling"});
|
||||
}
|
||||
|
||||
function drawSunShowPage(gps, key, date) {
|
||||
|
@ -224,7 +226,7 @@ function drawSunShowPage(gps, key, date) {
|
|||
Degrees: azimuthDegrees
|
||||
};
|
||||
|
||||
drawData(key, pageData, null, 85);
|
||||
drawData(key, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
|
||||
|
||||
drawPoints();
|
||||
|
||||
|
@ -233,7 +235,7 @@ function drawSunShowPage(gps, key, date) {
|
|||
|
||||
m = setWatch(() => {
|
||||
m = sunIndexPageMenu(gps);
|
||||
}, BTN3, {repeat: false, edge: "falling"});
|
||||
}, BANGLEJS2 ? BTN : BTN3, {repeat: false, edge: "falling"});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -273,15 +275,15 @@ function moonIndexPageMenu(gps) {
|
|||
},
|
||||
"Times": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonTimesPage(gps, "Times");
|
||||
drawMoonTimesPage(gps, /*LANG*/"Times");
|
||||
},
|
||||
"Position": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonPositionPage(gps, "Position");
|
||||
drawMoonPositionPage(gps, /*LANG*/"Position");
|
||||
},
|
||||
"Illumination": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonIlluminationPage(gps, "Illumination");
|
||||
drawMoonIlluminationPage(gps, /*LANG*/"Illumination");
|
||||
},
|
||||
"< Back": () => m = indexPageMenu(gps),
|
||||
};
|
||||
|
@ -292,15 +294,15 @@ function moonIndexPageMenu(gps) {
|
|||
function indexPageMenu(gps) {
|
||||
const menu = {
|
||||
"": {
|
||||
"title": "Select",
|
||||
"title": /*LANG*/"Select",
|
||||
},
|
||||
"Sun": () => {
|
||||
/*LANG*/"Sun": () => {
|
||||
m = sunIndexPageMenu(gps);
|
||||
},
|
||||
"Moon": () => {
|
||||
/*LANG*/"Moon": () => {
|
||||
m = moonIndexPageMenu(gps);
|
||||
},
|
||||
"< Exit": () => { load(); }
|
||||
"< Back": () => { load(); }
|
||||
};
|
||||
|
||||
return E.showMenu(menu);
|
||||
|
@ -310,78 +312,9 @@ function getCenterStringX(str) {
|
|||
return (g.getWidth() - g.stringWidth(str)) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* GPS wait page, shows GPS locating animation until it gets a lock, then moves to the Sun page
|
||||
*/
|
||||
function drawGPSWaitPage() {
|
||||
const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="));
|
||||
const str1 = "Astrocalc v0.02";
|
||||
const str2 = "Locating GPS";
|
||||
const str3 = "Please wait...";
|
||||
|
||||
g.clear();
|
||||
g.drawImage(img, 100, 50);
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString(str1, getCenterStringX(str1), 105);
|
||||
g.drawString(str2, getCenterStringX(str2), 140);
|
||||
g.drawString(str3, getCenterStringX(str3), 155);
|
||||
|
||||
if (lastGPS) {
|
||||
lastGPS = JSON.parse(lastGPS);
|
||||
lastGPS.time = new Date();
|
||||
|
||||
const str4 = "Press Button 3 to use last GPS";
|
||||
g.setColor("#d32e29");
|
||||
g.fillRect(0, 190, g.getWidth(), 215);
|
||||
g.setColor("#ffffff");
|
||||
g.drawString(str4, getCenterStringX(str4), 200);
|
||||
|
||||
setWatch(() => {
|
||||
clearWatch();
|
||||
Bangle.setGPSPower(0);
|
||||
m = indexPageMenu(lastGPS);
|
||||
}, BTN3, {repeat: false});
|
||||
}
|
||||
|
||||
g.flip();
|
||||
|
||||
const DEBUG = false;
|
||||
if (DEBUG) {
|
||||
clearWatch();
|
||||
|
||||
const gps = {
|
||||
"lat": 56.45783133333,
|
||||
"lon": -3.02188583333,
|
||||
"alt": 75.3,
|
||||
"speed": 0.070376,
|
||||
"course": NaN,
|
||||
"time":new Date(),
|
||||
"satellites": 4,
|
||||
"fix": 1
|
||||
};
|
||||
|
||||
m = indexPageMenu(gps);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Bangle.on('GPS', (gps) => {
|
||||
if (gps.fix === 0) return;
|
||||
clearWatch();
|
||||
|
||||
if (isNaN(gps.course)) gps.course = 0;
|
||||
require("Storage").writeJSON(LAST_GPS_FILE, JSON.stringify(gps));
|
||||
Bangle.setGPSPower(0);
|
||||
Bangle.buzz();
|
||||
Bangle.setLCDPower(true);
|
||||
|
||||
m = indexPageMenu(gps);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
Bangle.setGPSPower(1);
|
||||
drawGPSWaitPage();
|
||||
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
|
||||
indexPageMenu(location);
|
||||
}
|
||||
|
||||
let m;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"id": "astrocalc",
|
||||
"name": "Astrocalc",
|
||||
"version": "0.02",
|
||||
"description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.",
|
||||
"version": "0.04",
|
||||
"description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app",
|
||||
"icon": "astrocalc.png",
|
||||
"tags": "app,sun,moon,cycles,tool,outdoors",
|
||||
"supports": ["BANGLEJS"],
|
||||
"tags": "app,sun,moon,cycles,tool",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"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;
|
||||
|
||||
}());
|
|
@ -13,3 +13,4 @@
|
|||
0.13: Add font setting
|
||||
0.14: Use ClockFace_menu.addItems
|
||||
0.15: Add Power saving option
|
||||
0.16: Support Fast Loading
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
/* jshint esversion: 6 */
|
||||
/**
|
||||
{
|
||||
/**
|
||||
* A simple digital clock showing seconds as a bar
|
||||
**/
|
||||
// Check settings for what type our clock should be
|
||||
let locale = require("locale");
|
||||
{ // add some more info to locale
|
||||
let locale = require("locale");
|
||||
{ // add some more info to locale
|
||||
let date = new Date();
|
||||
date.setFullYear(1111);
|
||||
date.setMonth(1, 3); // februari: months are zero-indexed
|
||||
const localized = locale.date(date, true);
|
||||
locale.dayFirst = /3.*2/.test(localized);
|
||||
locale.hasMeridian = (locale.meridian(date)!=="");
|
||||
}
|
||||
}
|
||||
|
||||
let barW = 0, prevX = 0;
|
||||
function renderBar(l) {
|
||||
let barW = 0, prevX = 0;
|
||||
const renderBar = function (l) {
|
||||
"ram";
|
||||
if (l) prevX = 0; // called from Layout: drawing area was cleared
|
||||
else l = clock.layout.bar;
|
||||
|
@ -25,9 +26,9 @@ function renderBar(l) {
|
|||
if (x2<Math.max(0, prevX)) g.setBgColor(l.bgCol || g.theme.bg).clearRect(x2+1, l.y, prevX, l.y2);
|
||||
else g.setColor(l.col || g.theme.fg).fillRect(prevX+1, l.y, x2, l.y2);
|
||||
prevX = x2;
|
||||
}
|
||||
}
|
||||
|
||||
function timeText(date) {
|
||||
const timeText = function(date) {
|
||||
if (!clock.is12Hour) {
|
||||
return locale.time(date, true);
|
||||
}
|
||||
|
@ -39,19 +40,17 @@ function timeText(date) {
|
|||
date12.setHours(hours-12);
|
||||
}
|
||||
return locale.time(date12, true);
|
||||
}
|
||||
function ampmText(date) {
|
||||
return (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
|
||||
}
|
||||
function dateText(date) {
|
||||
}
|
||||
const ampmText = date => (clock.is12Hour && locale.hasMeridian) ? locale.meridian(date) : "";
|
||||
const dateText = date => {
|
||||
const dayName = locale.dow(date, true),
|
||||
month = locale.month(date, true),
|
||||
day = date.getDate();
|
||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`;
|
||||
return `${dayName} ${dayMonth}`;
|
||||
}
|
||||
};
|
||||
|
||||
const ClockFace = require("ClockFace"),
|
||||
const ClockFace = require("ClockFace"),
|
||||
clock = new ClockFace({
|
||||
precision: 1,
|
||||
settingsFile: "barclock.settings.json",
|
||||
|
@ -110,15 +109,20 @@ const ClockFace = require("ClockFace"),
|
|||
prevX = 0; // force redraw of bar
|
||||
this.layout.forgetLazyState();
|
||||
},
|
||||
remove: function() {
|
||||
if (this.onLock) Bangle.removeListener("lock", this.onLock);
|
||||
},
|
||||
});
|
||||
|
||||
// power saving: only update once a minute while locked, hide bar
|
||||
if (clock.powerSave) {
|
||||
Bangle.on("lock", lock => {
|
||||
// power saving: only update once a minute while locked, hide bar
|
||||
if (clock.powerSave) {
|
||||
clock.onLock = lock => {
|
||||
clock.precision = lock ? 60 : 1;
|
||||
clock.tick();
|
||||
renderBar(); // hide/redraw bar right away
|
||||
});
|
||||
}
|
||||
}
|
||||
Bangle.on("lock", clock.onLock);
|
||||
}
|
||||
|
||||
clock.start();
|
||||
clock.start();
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "barclock",
|
||||
"name": "Bar Clock",
|
||||
"version": "0.15",
|
||||
"version": "0.16",
|
||||
"description": "A simple digital clock showing seconds as a bar",
|
||||
"icon": "clock-bar.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}],
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,9 @@
|
|||
# BTHome Temperature and Pressure
|
||||
|
||||
This app displays temperature and pressure and advertises them over bluetooth using BTHome.io standard (along with battery level)
|
||||
|
||||
This can be used to integrate with [Home Assistant](https://www.home-assistant.io/), so you can use your Bangle as a wireless temperature/pressure sensor.
|
||||
|
||||
More info on the standard at https://bthome.io
|
||||
|
||||
And the data format used is https://bthome.io/format/
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4kA///1N6BIPf//1gMIwdE8sG2me+9Y/8C/2snXsoUNpdnzdt/xj/AH4AYgMRAAUQCyoYSCQNXs1muoFBFyHm1X//+qtwwPiMX1+YmczxP6uIwNFwN6yeDnGDmc504wNFwOpnGYC4OJweaGBsR9WTmYtBmc4GAOuC5ZGBt4SBAAQEBwf2JBcBiupnIuCmedxGTzVRC5cX1AuDnPZF4OKuIXLi3zIoedMgMzn9hC5uICQON5IDBxAXSznYC6RdDPQYXNO4JcB7pdCO56nBnGZ7p6DU5zXBXgSqDa5sAiPqIgOZd4c510RCxQXBi+pRQIXBxODzVxC5hIBvR1DnE505GMGAevzAvC/QuNGAfm1X//+qtwuOGAURq9ms11AoIWOGAQAEFw1EDBwWFggBCkUgAQMigUAAIIAJoABDCgIXQFwYXBCYYBDHAMCEAIkCFgcEAIIKCCoQFCkAhBAQIlCkAsBOoIXCBoIvEAwQTCAYI2BIwgXIF4YXDQwIVCC4YIBMIwfCAQRfGYBSPNC6TBFACgwBACouWAH4AiA="))
|
|
@ -0,0 +1,58 @@
|
|||
// history of temperature/pressure readings
|
||||
var history = [];
|
||||
|
||||
// When we get temperature...
|
||||
function onTemperature(p) {
|
||||
// Average the last 5 temperature readings
|
||||
while (history.length>4) history.shift();
|
||||
history.push(p);
|
||||
var avrTemp = history.reduce((i,h)=>h.temperature+i,0) / history.length;
|
||||
var avrPressure = history.reduce((i,h)=>h.pressure+i,0) / history.length;
|
||||
var t = require('locale').temp(avrTemp).replace("'","°");
|
||||
// Draw
|
||||
var rect = Bangle.appRect;
|
||||
g.reset(1).clearRect(rect.x, rect.y, rect.x2, rect.y2);
|
||||
var x = (rect.x+rect.x2)/2;
|
||||
var y = (rect.y+rect.y2)/2 + 10;
|
||||
g.setFont("6x15").setFontAlign(0,0).drawString("Temperature:", x, y - 65);
|
||||
g.setFontVector(50).setFontAlign(0,0).drawString(t, x, y-25);
|
||||
g.setFont("6x15").setFontAlign(0,0).drawString("Pressure:", x, y+15 );
|
||||
g.setFont("12x20").setFontAlign(0,0).drawString(Math.round(avrPressure)+" hPa", x, y+40);
|
||||
// Set Bluetooth Advertising
|
||||
// https://bthome.io/format/
|
||||
var temp100 = Math.round(avrTemp*100);
|
||||
var pressure100 = Math.round(avrPressure*100);
|
||||
|
||||
Bangle.bleAdvert[0xFCD2] = [ 0x40, /* BTHome Device Information
|
||||
bit 0: "Encryption flag"
|
||||
bit 1-4: "Reserved for future use"
|
||||
bit 5-7: "BTHome Version" */
|
||||
|
||||
0x01, // Battery, 8 bit
|
||||
E.getBattery(),
|
||||
|
||||
0x02, // Temperature, 16 bit
|
||||
temp100&255,temp100>>8,
|
||||
|
||||
0x04, // Pressure, 16 bit
|
||||
pressure100&255,(pressure100>>8)&255,pressure100>>16
|
||||
];
|
||||
NRF.setAdvertising(Bangle.bleAdvert);
|
||||
}
|
||||
|
||||
// Gets the temperature in the most accurate way with pressure sensor
|
||||
function drawTemperature() {
|
||||
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
|
||||
}
|
||||
|
||||
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
|
||||
setInterval(function() {
|
||||
drawTemperature();
|
||||
}, 10000); // update every 10s
|
||||
Bangle.loadWidgets();
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
back : function() {load();}
|
||||
});
|
||||
E.showMessage("Reading temperature...");
|
||||
drawTemperature();
|
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "bthometemp",
|
||||
"name": "BTHome Temperature and Pressure",
|
||||
"shortName":"BTHome T",
|
||||
"version":"0.01",
|
||||
"description": "Displays temperature and pressure, and advertises them over bluetooth using BTHome.io standard",
|
||||
"icon": "app.png",
|
||||
"tags": "bthome,bluetooth,temperature",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bthometemp.app.js","url":"app.js"},
|
||||
{"name":"bthometemp.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -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 |
|
@ -21,3 +21,4 @@
|
|||
0.21: On the default menu the week of year can be shown.
|
||||
0.22: Use the new clkinfo module for the menu.
|
||||
0.23: Feedback of apps after run is now optional and decided by the corresponding clkinfo.
|
||||
0.24: Update clock_info to avoid a redraw
|
||||
|
|
|
@ -93,7 +93,7 @@ var bwItems = {
|
|||
items: [
|
||||
{ name: "WeekOfYear",
|
||||
get: () => ({ text: "Week " + weekOfYear(), img: null}),
|
||||
show: function() { bwItems.items[0].emit("redraw"); },
|
||||
show: function() {},
|
||||
hide: function () {}
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.23",
|
||||
"version": "0.24",
|
||||
"description": "A very minimalistic clock to mainly show date and time.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -2,3 +2,5 @@
|
|||
0.02: More compact rendering & app icon
|
||||
0.03: Tell clock widgets to hide.
|
||||
0.04: Improve current time readability in light theme.
|
||||
0.05: Show calendar colors & improved all day events.
|
||||
0.06: Improved multi-line locations & titles
|
||||
|
|
|
@ -20,41 +20,57 @@ function zp(str) {
|
|||
}
|
||||
|
||||
function drawEventHeader(event, y) {
|
||||
g.setFont("Vector", 24);
|
||||
|
||||
var x = 0;
|
||||
var time = isActive(event) ? new Date() : new Date(event.timestamp * 1000);
|
||||
|
||||
//Don't need to know what time the event is at if its all day
|
||||
if (isActive(event) || !event.allDay) {
|
||||
g.setFont("Vector", 24);
|
||||
var timeStr = zp(time.getHours()) + ":" + zp(time.getMinutes());
|
||||
g.drawString(timeStr, 5, y);
|
||||
y += 24;
|
||||
g.drawString(timeStr, 0, y);
|
||||
y += 3;
|
||||
x = 13*timeStr.length+5;
|
||||
}
|
||||
|
||||
g.setFont("12x20", 1);
|
||||
|
||||
if (isActive(event)) {
|
||||
g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),15*timeStr.length,y-21);
|
||||
g.drawString(zp(time.getDate())+". " + require("locale").month(time,1),x,y);
|
||||
} else {
|
||||
var offset = 0-time.getTimezoneOffset()/1440;
|
||||
var days = Math.floor((time.getTime()/1000)/86400+offset)-Math.floor(getTime()/86400+offset);
|
||||
if(days > 0) {
|
||||
if(days > 0 || event.allDay) {
|
||||
var daysStr = days===1?/*LANG*/"tomorrow":/*LANG*/"in "+days+/*LANG*/" days";
|
||||
g.drawString(daysStr,15*timeStr.length,y-21);
|
||||
g.drawString(daysStr,x,y);
|
||||
}
|
||||
}
|
||||
y += 21;
|
||||
return y;
|
||||
}
|
||||
|
||||
function drawEventBody(event, y) {
|
||||
g.setFont("12x20", 1);
|
||||
var lines = g.wrapString(event.title, g.getWidth()-10);
|
||||
var lines = g.wrapString(event.title, g.getWidth()-15);
|
||||
var yStart = y;
|
||||
if (lines.length > 2) {
|
||||
lines = lines.slice(0,2);
|
||||
lines[1] = lines[1].slice(0,-3)+"...";
|
||||
lines[1] += "...";
|
||||
}
|
||||
g.drawString(lines.join('\n'), 5, y);
|
||||
g.drawString(lines.join('\n'),10,y);
|
||||
y+=20 * lines.length;
|
||||
if(event.location) {
|
||||
g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),5,y);
|
||||
g.drawString(event.location, 20, y);
|
||||
g.drawImage(atob("DBSBAA8D/H/nDuB+B+B+B3Dn/j/B+A8A8AYAYAYAAAAAAA=="),10,y);
|
||||
var loclines = g.wrapString(event.location, g.getWidth()-30);
|
||||
if(loclines.length>1) loclines[0] += "...";
|
||||
g.drawString(loclines[0],25,y);
|
||||
y+=20;
|
||||
}
|
||||
if (event.color) {
|
||||
var oldColor = g.getColor();
|
||||
g.setColor("#"+(0x1000000+Number(event.color)).toString(16).padStart(6,"0"));
|
||||
g.fillRect(0,yStart,5,y-3);
|
||||
g.setColor(oldColor);
|
||||
}
|
||||
y+=5;
|
||||
return y;
|
||||
}
|
||||
|
@ -68,19 +84,19 @@ function drawEvent(event, y) {
|
|||
var curEventHeight = 0;
|
||||
|
||||
function drawCurrentEvents(y) {
|
||||
g.setColor(g.theme.dark ? "#0ff" : "#0000ff");
|
||||
g.clearRect(5, y, g.getWidth() - 5, y + curEventHeight);
|
||||
g.setColor(g.theme.dark ? "#0ff" : "#00f");
|
||||
g.clearRect(0,y,g.getWidth()-5,y+curEventHeight);
|
||||
curEventHeight = y;
|
||||
|
||||
if(current.length === 0) {
|
||||
y = drawEvent({timestamp: getTime(), durationInSeconds: 100}, y);
|
||||
} else {
|
||||
y = drawEventHeader(current[0], y);
|
||||
y = drawEventHeader(current[0],y);
|
||||
for (var e of current) {
|
||||
y = drawEventBody(e, y);
|
||||
y = drawEventBody(e,y);
|
||||
}
|
||||
}
|
||||
curEventHeight = y - curEventHeight;
|
||||
curEventHeight = y-curEventHeight;
|
||||
return y;
|
||||
}
|
||||
|
||||
|
@ -94,7 +110,7 @@ function drawFutureEvents(y) {
|
|||
}
|
||||
|
||||
function fullRedraw() {
|
||||
g.clearRect(5,24,g.getWidth()-5,g.getHeight());
|
||||
g.clearRect(0,24,g.getWidth()-5,g.getHeight());
|
||||
updateCalendar();
|
||||
var y = 30;
|
||||
y = drawCurrentEvents(y);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "calclock",
|
||||
"name": "Calendar Clock",
|
||||
"shortName": "CalClock",
|
||||
"version": "0.04",
|
||||
"version": "0.06",
|
||||
"description": "Show the current and upcoming events synchronized from Gadgetbridge",
|
||||
"icon": "calclock.png",
|
||||
"type": "clock",
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
diff --git a/apps/calclock/calclock.js b/apps/calclock/calclock.js
|
||||
index cb8c6100e..2092c1a4e 100644
|
||||
--- a/apps/calclock/calclock.js
|
||||
+++ b/apps/calclock/calclock.js
|
||||
@@ -3,9 +3,24 @@ var current = [];
|
||||
var next = [];
|
||||
|
||||
function updateCalendar() {
|
||||
- calendar = require("Storage").readJSON("android.calendar.json",true)||[];
|
||||
- calendar = calendar.filter(e => isActive(e) || getTime() <= e.timestamp);
|
||||
- calendar.sort((a,b) => a.timestamp - b.timestamp);
|
||||
+ calendar = [
|
||||
+ {
|
||||
+ t: "calendar",
|
||||
+ id: 2, type: 0, timestamp: getTime(), durationInSeconds: 200,
|
||||
+ title: "Capture Screenshot",
|
||||
+ description: "Capture Screenshot",
|
||||
+ location: "",
|
||||
+ calName: "",
|
||||
+ color: -7151168, allDay: true },
|
||||
+ {
|
||||
+ t: "calendar",
|
||||
+ id: 7186, type: 0, timestamp: getTime() + 2000, durationInSeconds: 100,
|
||||
+ title: "Upload to BangleApps",
|
||||
+ description: "",
|
||||
+ location: "",
|
||||
+ calName: "",
|
||||
+ color: -509406, allDay: false }
|
||||
+ ];
|
||||
|
||||
current = calendar.filter(isActive);
|
||||
next = calendar.filter(e=>!isActive(e));
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -3,7 +3,7 @@
|
|||
"shortName":"Calibration",
|
||||
"icon": "calibration.png",
|
||||
"version":"0.03",
|
||||
"description": "A simple calibration app for the touchscreen",
|
||||
"description": "(NOT RECOMMENDED) A simple calibration app for the touchscreen. Please use the Touchscreen Calibration in the Settings app instead.",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"tags": "tool",
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
0.01: New App!
|
||||
0.02: Support Bangle.js 2
|
||||
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
|
||||
0.04: Combine code for both apps
|
||||
Better colors for Bangle.js 2
|
||||
Fix selection animation for Bangle.js 2
|
||||
New icon
|
||||
Slightly wider arc segments for better visibility
|
||||
Extract arc drawing code in library
|
||||
|
|
|
@ -11,16 +11,21 @@ the players seated in a circle, set the number of segments equal to the number
|
|||
of players, ensure that each person knows which colour represents them, and then
|
||||
choose a segment. After a short animation, the chosen segment will fill the screen.
|
||||
|
||||
You can use Choozi to randomly select an element from any set with 2 to 13 members,
|
||||
You can use Choozi to randomly select an element from any set with 2 to 15 members,
|
||||
as long as you can define a bijection between members of the set and coloured
|
||||
segments on the Bangle.js display.
|
||||
|
||||
## Controls
|
||||
## Controls Bangle 1
|
||||
|
||||
BTN1: increase the number of segments
|
||||
BTN2: choose a segment at random
|
||||
BTN3: decrease the number of segments
|
||||
|
||||
## Controls Bangle 2
|
||||
|
||||
Swipe up/down: increase/decrease the number of segments
|
||||
BTN1 or tap: choose a segment at random
|
||||
|
||||
## Creator
|
||||
|
||||
James Stanley
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
|
||||
require("heatshrink").decompress(atob("mEwwcH/4AW/u27dt2wQL/YOBCIXbv4QI+AODAQVsh4RHwEbCI0LCI9gCIOANAXbsFbG437tkDPg1btoRFFoILBgmSpMggECHQO/CAf2CIVJkgRBAQIjC24RFsECCItIgIRFMYMAiQRFpMAlqmDVwPYgAOEAQUggu274RD4BWCCIskCIPbCIPt20ABwwCCwARFgIRJyEWCIVt2EJCJi2BCJmSUgIRCwARNt/7CIIOICI1sWAwCFoFbCOtt8EACJsAgARR8hwBCJlJk4RlgARQAgIRKDwMn/gRBdJgRPyARBn4RBpARLiQRB/4RBgIRJwAREpIRLAYP///ypMgCJMACI0ECI4JCp4RB/wZECIsAAYN/CIP/5JPDCIhjDCIraHTIWTCAX//K7DCI+fCIf/EZA1CCAn//ipCLIsBk4RF/5ZHCIIQG//wPo8vCI//6QRFpYQIAAPpCIeXCBQAC/VfBI4="))
|
|
@ -4,15 +4,16 @@
|
|||
*
|
||||
* James Stanley 2021
|
||||
*/
|
||||
|
||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
||||
const GU = require("graphics_utils");
|
||||
var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff'];
|
||||
var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0'];
|
||||
|
||||
var stepAngle = 0.18; // radians - resolution of polygon
|
||||
var gapAngle = 0.035; // radians - gap between segments
|
||||
var perimMin = 110; // px - min. radius of perimeter
|
||||
var perimMax = 120; // px - max. radius of perimeter
|
||||
var perimMin = g.getWidth()*0.40; // px - min. radius of perimeter
|
||||
var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter
|
||||
|
||||
var segmentMax = 106; // px - max radius of filled-in segment
|
||||
var segmentMax = g.getWidth()*0.38; // px - max radius of filled-in segment
|
||||
var segmentStep = 5; // px - step size of segment fill animation
|
||||
var circleStep = 4; // px - step size of circle fill animation
|
||||
|
||||
|
@ -22,10 +23,10 @@ var minSpeed = 0.001; // rad/sec
|
|||
var animStartSteps = 300; // how many steps before it can start slowing?
|
||||
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
||||
var ballSize = 3; // px - ball radius
|
||||
var ballTrack = 100; // px - radius of ball path
|
||||
var ballTrack = perimMin - ballSize*2; // px - radius of ball path
|
||||
|
||||
var centreX = 120; // px - centre of screen
|
||||
var centreY = 120; // px - centre of screen
|
||||
var centreX = g.getWidth()*0.5; // px - centre of screen
|
||||
var centreY = g.getWidth()*0.5; // px - centre of screen
|
||||
|
||||
var fontSize = 50; // px
|
||||
|
||||
|
@ -33,7 +34,6 @@ var radians = 2*Math.PI; // radians per circle
|
|||
|
||||
var defaultN = 3; // default value for N
|
||||
var minN = 2;
|
||||
var maxN = colours.length;
|
||||
var N;
|
||||
var arclen;
|
||||
|
||||
|
@ -51,42 +51,14 @@ function shuffle (array) {
|
|||
}
|
||||
}
|
||||
|
||||
// draw an arc between radii minR and maxR, and between
|
||||
// angles minAngle and maxAngle
|
||||
function arc(minR, maxR, minAngle, maxAngle) {
|
||||
var step = stepAngle;
|
||||
var angle = minAngle;
|
||||
var inside = [];
|
||||
var outside = [];
|
||||
var c, s;
|
||||
while (angle < maxAngle) {
|
||||
c = Math.cos(angle);
|
||||
s = Math.sin(angle);
|
||||
inside.push(centreX+c*minR); // x
|
||||
inside.push(centreY+s*minR); // y
|
||||
// outside coordinates are built up in reverse order
|
||||
outside.unshift(centreY+s*maxR); // y
|
||||
outside.unshift(centreX+c*maxR); // x
|
||||
angle += step;
|
||||
}
|
||||
c = Math.cos(maxAngle);
|
||||
s = Math.sin(maxAngle);
|
||||
inside.push(centreX+c*minR);
|
||||
inside.push(centreY+s*minR);
|
||||
outside.unshift(centreY+s*maxR);
|
||||
outside.unshift(centreX+c*maxR);
|
||||
|
||||
var vertices = inside.concat(outside);
|
||||
g.fillPoly(vertices, true);
|
||||
}
|
||||
|
||||
// draw the arc segments around the perimeter
|
||||
function drawPerimeter() {
|
||||
g.setBgColor('#000000');
|
||||
g.clear();
|
||||
for (var i = 0; i < N; i++) {
|
||||
g.setColor(colours[i%colours.length]);
|
||||
var minAngle = (i/N)*radians;
|
||||
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
||||
GU.fillArc(g, centreX, centreY, perimMin,perimMax,minAngle,minAngle+arclen, stepAngle);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,6 +103,7 @@ function animateChoice(target) {
|
|||
g.fillCircle(x, y, ballSize);
|
||||
oldx=x;
|
||||
oldy=y;
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,11 +114,15 @@ function choose() {
|
|||
var maxAngle = minAngle + arclen;
|
||||
animateChoice((minAngle+maxAngle)/2);
|
||||
g.setColor(colours[chosen%colours.length]);
|
||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
||||
arc(i, perimMax, minAngle, maxAngle);
|
||||
arc(0, perimMax, minAngle, maxAngle);
|
||||
for (var r = 1; r < segmentMax; r += circleStep)
|
||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){
|
||||
GU.fillArc(g, centreX, centreY, i, perimMax, minAngle, maxAngle, stepAngle);
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
GU.fillArc(g, centreX, centreY, 0, perimMax, minAngle, maxAngle, stepAngle);
|
||||
for (var r = 1; r < segmentMax; r += circleStep){
|
||||
g.fillCircle(centreX,centreY,r);
|
||||
if (process.env.HWVERSION == 2) g.flip();
|
||||
}
|
||||
g.fillCircle(centreX,centreY,segmentMax);
|
||||
}
|
||||
|
||||
|
@ -171,38 +148,47 @@ function setN(n) {
|
|||
drawPerimeter();
|
||||
}
|
||||
|
||||
// save N to choozi.txt
|
||||
// save N to choozi.save
|
||||
function writeN() {
|
||||
var file = require("Storage").open("choozi.txt","w");
|
||||
file.write(N);
|
||||
var savedN = read();
|
||||
if (savedN != N) require("Storage").write("choozi.save","" + N);
|
||||
}
|
||||
|
||||
// load N from choozi.txt
|
||||
function read(){
|
||||
var n = require("Storage").read("choozi.save");
|
||||
if (n !== undefined) return parseInt(n);
|
||||
return defaultN;
|
||||
}
|
||||
|
||||
// load N from choozi.save
|
||||
function readN() {
|
||||
var file = require("Storage").open("choozi.txt","r");
|
||||
var n = file.readLine();
|
||||
if (n !== undefined) setN(parseInt(n));
|
||||
else setN(defaultN);
|
||||
setN(read());
|
||||
}
|
||||
|
||||
shuffle(colours); // is this really best?
|
||||
Bangle.setLCDMode("direct");
|
||||
Bangle.setLCDTimeout(0); // keep screen on
|
||||
if (process.env.HWVERSION == 1){
|
||||
colours=colours.concat(colours2);
|
||||
shuffle(colours);
|
||||
} else {
|
||||
shuffle(colours);
|
||||
shuffle(colours2);
|
||||
colours=colours.concat(colours2);
|
||||
}
|
||||
|
||||
var maxN = colours.length;
|
||||
if (process.env.HWVERSION == 1){
|
||||
Bangle.setLCDMode("direct");
|
||||
Bangle.setLCDTimeout(0); // keep screen on
|
||||
}
|
||||
readN();
|
||||
drawN();
|
||||
|
||||
setWatch(() => {
|
||||
setN(N+1);
|
||||
drawN();
|
||||
}, BTN1, {repeat:true});
|
||||
|
||||
setWatch(() => {
|
||||
Bangle.setUI("updown", (v)=>{
|
||||
if (!v){
|
||||
writeN();
|
||||
drawPerimeter();
|
||||
choose();
|
||||
}, BTN2, {repeat:true});
|
||||
|
||||
setWatch(() => {
|
||||
setN(N-1);
|
||||
} else {
|
||||
setN(N-v);
|
||||
drawN();
|
||||
}, BTN3, {repeat:true});
|
||||
}
|
||||
});
|
||||
|
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 551 B |
|
@ -1,207 +0,0 @@
|
|||
/* Choozi - Choose people or things at random using Bangle.js.
|
||||
* Inspired by the "Chwazi" Android app
|
||||
*
|
||||
* James Stanley 2021
|
||||
*/
|
||||
|
||||
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
||||
|
||||
var stepAngle = 0.18; // radians - resolution of polygon
|
||||
var gapAngle = 0.035; // radians - gap between segments
|
||||
var perimMin = 80; // px - min. radius of perimeter
|
||||
var perimMax = 87; // px - max. radius of perimeter
|
||||
|
||||
var segmentMax = 70; // px - max radius of filled-in segment
|
||||
var segmentStep = 5; // px - step size of segment fill animation
|
||||
var circleStep = 4; // px - step size of circle fill animation
|
||||
|
||||
// rolling ball animation:
|
||||
var maxSpeed = 0.08; // rad/sec
|
||||
var minSpeed = 0.001; // rad/sec
|
||||
var animStartSteps = 300; // how many steps before it can start slowing?
|
||||
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
||||
var ballSize = 3; // px - ball radius
|
||||
var ballTrack = 75; // px - radius of ball path
|
||||
|
||||
var centreX = 88; // px - centre of screen
|
||||
var centreY = 88; // px - centre of screen
|
||||
|
||||
var fontSize = 50; // px
|
||||
|
||||
var radians = 2*Math.PI; // radians per circle
|
||||
|
||||
var defaultN = 3; // default value for N
|
||||
var minN = 2;
|
||||
var maxN = colours.length;
|
||||
var N;
|
||||
var arclen;
|
||||
|
||||
// https://www.frankmitchell.org/2015/01/fisher-yates/
|
||||
function shuffle (array) {
|
||||
var i = 0
|
||||
, j = 0
|
||||
, temp = null;
|
||||
|
||||
for (i = array.length - 1; i > 0; i -= 1) {
|
||||
j = Math.floor(Math.random() * (i + 1));
|
||||
temp = array[i];
|
||||
array[i] = array[j];
|
||||
array[j] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
// draw an arc between radii minR and maxR, and between
|
||||
// angles minAngle and maxAngle
|
||||
function arc(minR, maxR, minAngle, maxAngle) {
|
||||
var step = stepAngle;
|
||||
var angle = minAngle;
|
||||
var inside = [];
|
||||
var outside = [];
|
||||
var c, s;
|
||||
while (angle < maxAngle) {
|
||||
c = Math.cos(angle);
|
||||
s = Math.sin(angle);
|
||||
inside.push(centreX+c*minR); // x
|
||||
inside.push(centreY+s*minR); // y
|
||||
// outside coordinates are built up in reverse order
|
||||
outside.unshift(centreY+s*maxR); // y
|
||||
outside.unshift(centreX+c*maxR); // x
|
||||
angle += step;
|
||||
}
|
||||
c = Math.cos(maxAngle);
|
||||
s = Math.sin(maxAngle);
|
||||
inside.push(centreX+c*minR);
|
||||
inside.push(centreY+s*minR);
|
||||
outside.unshift(centreY+s*maxR);
|
||||
outside.unshift(centreX+c*maxR);
|
||||
|
||||
var vertices = inside.concat(outside);
|
||||
g.fillPoly(vertices, true);
|
||||
}
|
||||
|
||||
// draw the arc segments around the perimeter
|
||||
function drawPerimeter() {
|
||||
g.clear();
|
||||
for (var i = 0; i < N; i++) {
|
||||
g.setColor(colours[i%colours.length]);
|
||||
var minAngle = (i/N)*radians;
|
||||
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
||||
}
|
||||
}
|
||||
|
||||
// animate a ball rolling around and settling at "target" radians
|
||||
function animateChoice(target) {
|
||||
var angle = 0;
|
||||
var speed = 0;
|
||||
var oldx = -10;
|
||||
var oldy = -10;
|
||||
var decelFromAngle = -1;
|
||||
var allowDecel = false;
|
||||
for (var i = 0; true; i++) {
|
||||
angle = angle + speed;
|
||||
if (angle > radians) angle -= radians;
|
||||
if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) {
|
||||
speed = speed + accel;
|
||||
if (speed > maxSpeed) {
|
||||
speed = maxSpeed;
|
||||
/* when we reach max speed, we know how long it takes
|
||||
* to accelerate, and therefore how long to decelerate, so
|
||||
* we can work out what angle to start decelerating from */
|
||||
if (decelFromAngle < 0) {
|
||||
decelFromAngle = target-angle;
|
||||
while (decelFromAngle < 0) decelFromAngle += radians;
|
||||
while (decelFromAngle > radians) decelFromAngle -= radians;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true;
|
||||
if (allowDecel) speed = speed - accel;
|
||||
if (speed < minSpeed) speed = minSpeed;
|
||||
if (speed == minSpeed && angle < target && angle+speed >= target) return;
|
||||
}
|
||||
|
||||
var r = i/2;
|
||||
if (r > ballTrack) r = ballTrack;
|
||||
var x = centreX+Math.cos(angle)*r;
|
||||
var y = centreY+Math.sin(angle)*r;
|
||||
g.setColor('#000000');
|
||||
g.fillCircle(oldx,oldy,ballSize+1);
|
||||
g.setColor('#ffffff');
|
||||
g.fillCircle(x, y, ballSize);
|
||||
oldx=x;
|
||||
oldy=y;
|
||||
g.flip();
|
||||
}
|
||||
}
|
||||
|
||||
// choose a winning segment and animate its selection
|
||||
function choose() {
|
||||
var chosen = Math.floor(Math.random()*N);
|
||||
var minAngle = (chosen/N)*radians;
|
||||
var maxAngle = minAngle + arclen;
|
||||
animateChoice((minAngle+maxAngle)/2);
|
||||
g.setColor(colours[chosen%colours.length]);
|
||||
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
||||
arc(i, perimMax, minAngle, maxAngle);
|
||||
arc(0, perimMax, minAngle, maxAngle);
|
||||
for (var r = 1; r < segmentMax; r += circleStep)
|
||||
g.fillCircle(centreX,centreY,r);
|
||||
g.fillCircle(centreX,centreY,segmentMax);
|
||||
}
|
||||
|
||||
// draw the current value of N in the middle of the screen, with
|
||||
// up/down arrows
|
||||
function drawN() {
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("Vector",fontSize);
|
||||
g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2);
|
||||
if (N < maxN)
|
||||
g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]);
|
||||
if (N > minN)
|
||||
g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]);
|
||||
}
|
||||
|
||||
// update number of segments, with min/max limit, "arclen" update,
|
||||
// and screen reset
|
||||
function setN(n) {
|
||||
N = n;
|
||||
if (N < minN) N = minN;
|
||||
if (N > maxN) N = maxN;
|
||||
arclen = radians/N - gapAngle;
|
||||
drawPerimeter();
|
||||
}
|
||||
|
||||
// save N to choozi.txt
|
||||
function writeN() {
|
||||
var file = require("Storage").open("choozi.txt","w");
|
||||
file.write(N);
|
||||
}
|
||||
|
||||
// load N from choozi.txt
|
||||
function readN() {
|
||||
var file = require("Storage").open("choozi.txt","r");
|
||||
var n = file.readLine();
|
||||
if (n !== undefined) setN(parseInt(n));
|
||||
else setN(defaultN);
|
||||
}
|
||||
|
||||
shuffle(colours); // is this really best?
|
||||
Bangle.setLCDTimeout(0); // keep screen on
|
||||
readN();
|
||||
drawN();
|
||||
|
||||
setWatch(() => {
|
||||
writeN();
|
||||
drawPerimeter();
|
||||
choose();
|
||||
}, BTN1, {repeat:true});
|
||||
|
||||
Bangle.on('touch', function(zone,e) {
|
||||
if(e.x>+88){
|
||||
setN(N-1);
|
||||
drawN();
|
||||
}else{
|
||||
setN(N+1);
|
||||
drawN();
|
||||
}
|
||||
});
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "choozi",
|
||||
"name": "Choozi",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Choose people or things at random using Bangle.js.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
|
@ -10,8 +10,10 @@
|
|||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
|
||||
"storage": [
|
||||
{"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]},
|
||||
{"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]},
|
||||
{"name":"choozi.app.js","url":"app.js"},
|
||||
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"choozi.save"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -31,3 +31,11 @@
|
|||
0.16: Fix const error
|
||||
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
|
||||
0.22: Fixed crash if item has no image and cutting long overflowing text
|
||||
|
|
|
@ -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,54 +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
|
||||
];
|
||||
|
||||
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);
|
||||
|
@ -176,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 = [
|
||||
|
@ -307,370 +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 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;
|
||||
//suppose unnamed are varying (like timers or events), pick the first
|
||||
if(itemName)
|
||||
itemNum = menu[infoNum].items.findIndex(it=>it.name==itemName);
|
||||
circleInfoNum[i-1] = infoNum;
|
||||
circleItemNum[i-1] = itemNum;
|
||||
}
|
||||
}
|
||||
//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, "?");
|
||||
|
@ -679,64 +172,47 @@ 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);
|
||||
if(!info || !info.items.length) {
|
||||
drawEmpty(info? info.img : null, w, color);
|
||||
return;
|
||||
}
|
||||
var item = info.items[circleItemNum[index-1]];
|
||||
//TODO do hide()+get() here
|
||||
item.show();
|
||||
item.hide();
|
||||
item=item.get();
|
||||
var img = item.img;
|
||||
if(!img) img = info.img;
|
||||
let percent = (item.v-item.min) / item.max;
|
||||
if(isNaN(percent)) percent = 1; //fill it up
|
||||
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 (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;
|
||||
//long text can overflow and we do not draw there anymore..
|
||||
if(txt.length>6) txt = txt.slice(0,5)+"\n"+txt.slice(5,10)
|
||||
drawGauge(w, h3, percent, color);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
writeCircleText(w, item.text);
|
||||
g.setColor(getCircleIconColor(type, color, percent))
|
||||
writeCircleText(w, txt);
|
||||
if(!img) return; //or get it from the clkinfo?
|
||||
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:
|
||||
|
@ -765,7 +241,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:
|
||||
|
@ -778,80 +256,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);
|
||||
|
@ -859,18 +276,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;
|
||||
|
@ -880,15 +292,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);
|
||||
|
@ -898,26 +316,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() {
|
||||
|
@ -927,3 +367,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.17",
|
||||
"version":"0.22",
|
||||
"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,26 +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=>{
|
||||
//TODO filter for hasRange and other
|
||||
if(!e.items.length || !e.items[0].name) {
|
||||
//suppose unnamed are varying (like timers or events), pick the first
|
||||
item = e.items[0];
|
||||
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",
|
||||
|
@ -85,6 +65,12 @@
|
|||
},
|
||||
onchange: x => save('updateInterval', x),
|
||||
},
|
||||
//TODO deprecated local icons, may disappear in future
|
||||
/*LANG*/'legacy weather icons': {
|
||||
value: !!settings.legacyWeatherIcons,
|
||||
format: () => (settings.legacyWeatherIcons ? 'Yes' : 'No'),
|
||||
onchange: x => save('legacyWeatherIcons', x),
|
||||
},
|
||||
/*LANG*/'show big weather': {
|
||||
value: !!settings.showBigWeather,
|
||||
format: () => (settings.showBigWeather ? 'Yes' : 'No'),
|
||||
|
@ -102,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"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
0.01: First release
|
||||
0.02: Update clock_info to avoid a redraw and image allocation
|
After Width: | Height: | Size: 1012 B |
|
@ -0,0 +1,17 @@
|
|||
(function() {
|
||||
return {
|
||||
name: "Bangle",
|
||||
items: [
|
||||
{ name : "FW",
|
||||
get : () => {
|
||||
return {
|
||||
text : process.env.VERSION,
|
||||
img : atob("GBjC////AADve773VWmmmmlVVW22nnlVVbLL445VVwAAAADVWAAAAAAlrAAAAAA6sAAAAAAOWAAAAAAlrAD//wA6sANVVcAOWANVVcAlrANVVcA6rANVVcA6WANVVcAlsANVVcAOrAD//wA6WAAAAAAlsAAAAAAOrAAAAAA6WAAAAAAlVwAAAADVVbLL445VVW22nnlVVWmmmmlV")
|
||||
};
|
||||
},
|
||||
show : function() {},
|
||||
hide : function() {}
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
|
@ -0,0 +1,13 @@
|
|||
{ "id": "clkinfofw",
|
||||
"name": "Firmware Clockinfo",
|
||||
"version":"0.02",
|
||||
"description": "For clocks that display 'clockinfo', this displays the firmware version string",
|
||||
"icon": "app.png",
|
||||
"type": "clkinfo",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"tags": "clkinfo,firmware",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"clkinfofw.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -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",
|
||||
|
|
|
@ -7,3 +7,4 @@
|
|||
0.25: Fixed a bug that would let widgets change the color of the clock.
|
||||
0.26: Time formatted to locale
|
||||
0.27: Fixed the timing code, which sometimes did not update for one minute
|
||||
0.28: More config options for cleaner look, enabled fast loading
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# New Features:
|
||||
- Fast load! (only works if your launcher uses widgets)
|
||||
- widgets, date and weekday are individually configurable
|
||||
- you can hide widgets, date and weekday for a cleaner look when the watch is locked
|
||||
|
||||
Contact me for bug reports or feature requests: ContourClock@gmx.de
|
|
@ -1,35 +1,64 @@
|
|||
var digits = [];
|
||||
var drawTimeout;
|
||||
var fontName="";
|
||||
var settings = require('Storage').readJSON("contourclock.json", true) || {};
|
||||
if (settings.fontIndex==undefined) {
|
||||
{
|
||||
let digits = [];
|
||||
let drawTimeout;
|
||||
let fontName="";
|
||||
let settings = require('Storage').readJSON("contourclock.json", true) || {};
|
||||
if (settings.fontIndex==undefined) {
|
||||
settings.fontIndex=0;
|
||||
require('Storage').writeJSON("myapp.json", settings);
|
||||
}
|
||||
settings.widgets=true;
|
||||
settings.hide=false;
|
||||
settings.weekday=true;
|
||||
settings.hideWhenLocked=false;
|
||||
settings.date=true; require('Storage').writeJSON("myapp.json", settings);
|
||||
}
|
||||
|
||||
function queueDraw() {
|
||||
setTimeout(function() {
|
||||
let queueDraw = function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
queueDraw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
};
|
||||
|
||||
function draw() {
|
||||
let draw = function() {
|
||||
var date = new Date();
|
||||
// Draw day of the week
|
||||
g.reset();
|
||||
if ((!settings.hideWhenLocked) || (!Bangle.isLocked())) {
|
||||
// Draw day of the week
|
||||
g.setFont("Teletext10x18Ascii");
|
||||
g.clearRect(0,138,g.getWidth()-1,176);
|
||||
g.setFontAlign(0,1).drawString(require("locale").dow(date).toUpperCase(),g.getWidth()/2,g.getHeight()-18);
|
||||
if (settings.weekday) g.setFontAlign(0,1).drawString(require("locale").dow(date).toUpperCase(),g.getWidth()/2,g.getHeight()-18);
|
||||
// Draw Date
|
||||
g.setFontAlign(0,1).drawString(require('locale').date(new Date(),1),g.getWidth()/2,g.getHeight());
|
||||
if (settings.date) g.setFontAlign(0,1).drawString(require('locale').date(new Date(),1),g.getWidth()/2,g.getHeight());
|
||||
}
|
||||
require('contourclock').drawClock(settings.fontIndex);
|
||||
}
|
||||
};
|
||||
|
||||
require("FontTeletext10x18Ascii").add(Graphics);
|
||||
Bangle.setUI("clock");
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
queueDraw();
|
||||
draw();
|
||||
require("FontTeletext10x18Ascii").add(Graphics);
|
||||
g.clear();
|
||||
|
||||
draw();
|
||||
if (settings.hideWhenLocked) Bangle.on('lock', function (locked) {
|
||||
if (!locked) require("widget_utils").show();
|
||||
else {
|
||||
g.clear();
|
||||
if (settings.hide) require("widget_utils").swipeOn();
|
||||
else require("widget_utils").hide();
|
||||
}
|
||||
draw();
|
||||
});
|
||||
Bangle.setUI({mode:"clock", remove:function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
if (settings.widgets && settings.hide) require("widget_utils").show();
|
||||
g.reset();
|
||||
g.clear();
|
||||
}});
|
||||
if (settings.widgets) {
|
||||
Bangle.loadWidgets();
|
||||
if (settings.hide) require("widget_utils").swipeOn();
|
||||
else Bangle.drawWidgets();
|
||||
}
|
||||
queueDraw();
|
||||
}
|
||||
|
|
|
@ -1,43 +1,73 @@
|
|||
(function(back) {
|
||||
Bangle.removeAllListeners('drag');
|
||||
Bangle.setUI("");
|
||||
var settings = require('Storage').readJSON('contourclock.json', true) || {};
|
||||
if (settings.fontIndex==undefined) {
|
||||
settings.fontIndex=0;
|
||||
settings.widgets=true;
|
||||
settings.hide=false;
|
||||
settings.weekday=true;
|
||||
settings.date=true;
|
||||
settings.hideWhenLocked=false;
|
||||
require('Storage').writeJSON("myapp.json", settings);
|
||||
}
|
||||
function mainMenu() {
|
||||
E.showMenu({
|
||||
"" : { "title" : "ContourClock" },
|
||||
"< Back" : () => back(),
|
||||
'Widgets': {
|
||||
value: (settings.widgets !== undefined ? settings.widgets : true),
|
||||
onchange : v => {settings.widgets=v; require('Storage').writeJSON('contourclock.json', settings);}
|
||||
},
|
||||
'hide Widgets': {
|
||||
value: (settings.hide !== undefined ? settings.hide : false),
|
||||
onchange : v => {settings.hide=v; require('Storage').writeJSON('contourclock.json', settings);}
|
||||
},
|
||||
'Weekday': {
|
||||
value: (settings.weekday !== undefined ? settings.weekday : true),
|
||||
onchange : v => {settings.weekday=v; require('Storage').writeJSON('contourclock.json', settings);}
|
||||
},
|
||||
'Date': {
|
||||
value: (settings.date !== undefined ? settings.date : true),
|
||||
onchange : v => {settings.date=v; require('Storage').writeJSON('contourclock.json', settings);}
|
||||
},
|
||||
'Hide when locked': {
|
||||
value: (settings.hideWhenLocked !== undefined ? settings.hideWhenLocked : false),
|
||||
onchange : v => {settings.hideWhenLocked=v; require('Storage').writeJSON('contourclock.json', settings);}
|
||||
},
|
||||
'set Font': () => fontMenu()
|
||||
});
|
||||
}
|
||||
function fontMenu() {
|
||||
Bangle.setUI("");
|
||||
savedIndex=settings.fontIndex;
|
||||
saveListener = setWatch(function() { //save changes and return to settings menu
|
||||
require('Storage').writeJSON('contourclock.json', settings);
|
||||
Bangle.removeAllListeners('swipe');
|
||||
Bangle.removeAllListeners('lock');
|
||||
clearWatch(saveListener);
|
||||
g.clear();
|
||||
back();
|
||||
mainMenu();
|
||||
}, BTN, { repeat:false, edge:'falling' });
|
||||
lockListener = Bangle.on('lock', function () { //discard changes and return to clock
|
||||
settings.fontIndex=savedIndex;
|
||||
require('Storage').writeJSON('contourclock.json', settings);
|
||||
Bangle.removeAllListeners('swipe');
|
||||
Bangle.removeAllListeners('lock');
|
||||
clearWatch(saveListener);
|
||||
g.clear();
|
||||
load();
|
||||
mainMenu();
|
||||
});
|
||||
swipeListener = Bangle.on('swipe', function (direction) {
|
||||
var fontName = require('contourclock').drawClock(settings.fontIndex+direction);
|
||||
if (fontName) {
|
||||
settings.fontIndex+=direction;
|
||||
g.clearRect(0,0,g.getWidth()-1,16);
|
||||
g.setFont('6x8:2x2').setFontAlign(0,-1).drawString(fontName,g.getWidth()/2,0);
|
||||
g.clearRect(0,g.getHeight()-36,g.getWidth()-1,g.getHeight()-36+16);
|
||||
g.setFont('6x8:2x2').setFontAlign(0,-1).drawString(fontName,g.getWidth()/2,g.getHeight()-36);
|
||||
} else {
|
||||
require('contourclock').drawClock(settings.fontIndex);
|
||||
}
|
||||
});
|
||||
g.reset();
|
||||
g.clear();
|
||||
g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1);
|
||||
g.setFont('6x8:2x2').setFontAlign(0,-1);
|
||||
g.drawString(require('contourclock').drawClock(settings.fontIndex),g.getWidth()/2,0);
|
||||
g.drawString('Swipe - change',g.getWidth()/2,g.getHeight()-36);
|
||||
g.drawString('BTN - save',g.getWidth()/2,g.getHeight()-18);
|
||||
g.drawString(require('contourclock').drawClock(settings.fontIndex),g.getWidth()/2,g.getHeight()-36);
|
||||
g.drawString('Button to save',g.getWidth()/2,g.getHeight()-18);
|
||||
}
|
||||
mainMenu();
|
||||
})
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
{ "id": "contourclock",
|
||||
"name": "Contour Clock",
|
||||
"shortName" : "Contour Clock",
|
||||
"version":"0.27",
|
||||
"version":"0.28",
|
||||
"icon": "app.png",
|
||||
"description": "A Minimalist clockface with large Digits. Now with more fonts!",
|
||||
"readme": "README.md",
|
||||
"description": "A Minimalist clockface with large Digits.",
|
||||
"screenshots" : [{"url":"cc-screenshot-1.png"},{"url":"cc-screenshot-2.png"}],
|
||||
"tags": "clock",
|
||||
"custom": "custom.html",
|
||||
|
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Add lightning
|
||||
|
|
|
@ -46,6 +46,9 @@ var booster = { x : g.getWidth()/4 + Math.random()*g.getWidth()/2,
|
|||
var exploded = false;
|
||||
var nExplosions = 0;
|
||||
var landed = false;
|
||||
var lightning = 0;
|
||||
|
||||
var settings = require("Storage").readJSON('f9settings.json', 1) || {};
|
||||
|
||||
const gravity = 4;
|
||||
const dt = 0.1;
|
||||
|
@ -61,18 +64,40 @@ function flameImageGen (throttle) {
|
|||
|
||||
function drawFalcon(x, y, throttle, angle) {
|
||||
g.setColor(1, 1, 1).drawImage(falcon9, x, y, {rotate:angle});
|
||||
if (throttle>0) {
|
||||
if (throttle>0 || lightning>0) {
|
||||
var flameImg = flameImageGen(throttle);
|
||||
var r = falcon9.height/2 + flameImg.height/2-1;
|
||||
var xoffs = -Math.sin(angle)*r;
|
||||
var yoffs = Math.cos(angle)*r;
|
||||
if (Math.random()>0.7) g.setColor(1, 0.5, 0);
|
||||
else g.setColor(1, 1, 0);
|
||||
g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle});
|
||||
if (throttle>0) g.drawImage(flameImg, x+xoffs, y+yoffs, {rotate:angle});
|
||||
if (lightning>1 && lightning<30) {
|
||||
for (var i=0; i<6; ++i) {
|
||||
var r = Math.random()*6;
|
||||
var x = Math.random()*5 - xoffs;
|
||||
var y = Math.random()*5 - yoffs;
|
||||
g.setColor(1, Math.random()*0.5+0.5, 0).fillCircle(booster.x+x, booster.y+y, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawLightning() {
|
||||
var c = {x:cloudOffs+50, y:30};
|
||||
var dx = c.x-booster.x;
|
||||
var dy = c.y-booster.y;
|
||||
var m1 = {x:booster.x+0.6*dx+Math.random()*20, y:booster.y+0.6*dy+Math.random()*10};
|
||||
var m2 = {x:booster.x+0.4*dx+Math.random()*20, y:booster.y+0.4*dy+Math.random()*10};
|
||||
g.setColor(1, 1, 1).drawLine(c.x, c.y, m1.x, m1.y).drawLine(m1.x, m1.y, m2.x, m2.y).drawLine(m2.x, m2.y, booster.x, booster.y);
|
||||
}
|
||||
|
||||
function drawBG() {
|
||||
if (lightning==1) {
|
||||
g.setBgColor(1, 1, 1).clear();
|
||||
Bangle.buzz(200);
|
||||
return;
|
||||
}
|
||||
g.setBgColor(0.2, 0.2, 1).clear();
|
||||
g.setColor(0, 0, 1).fillRect(0, g.getHeight()-oceanHeight, g.getWidth()-1, g.getHeight()-1);
|
||||
g.setColor(0.5, 0.5, 1).fillCircle(cloudOffs+34, 30, 15).fillCircle(cloudOffs+60, 35, 20).fillCircle(cloudOffs+75, 20, 10);
|
||||
|
@ -88,6 +113,7 @@ function renderScreen(input) {
|
|||
drawBG();
|
||||
showFuel();
|
||||
drawFalcon(booster.x, booster.y, Math.floor(input.throttle*12), input.angle);
|
||||
if (lightning>1 && lightning<6) drawLightning();
|
||||
}
|
||||
|
||||
function getInputs() {
|
||||
|
@ -97,6 +123,7 @@ function getInputs() {
|
|||
if (t > 1) t = 1;
|
||||
if (t < 0) t = 0;
|
||||
if (booster.fuel<=0) t = 0;
|
||||
if (lightning>0 && lightning<20) t = 0;
|
||||
return {throttle: t, angle: a};
|
||||
}
|
||||
|
||||
|
@ -121,7 +148,6 @@ function gameStep() {
|
|||
else {
|
||||
var input = getInputs();
|
||||
if (booster.y >= targetY) {
|
||||
// console.log(booster.x + " " + booster.y + " " + booster.vy + " " + droneX + " " + input.angle);
|
||||
if (Math.abs(booster.x-droneX-droneShip.width/2)<droneShip.width/2 && Math.abs(input.angle)<Math.PI/8 && booster.vy<maxV) {
|
||||
renderScreen({angle:0, throttle:0});
|
||||
epilogue("You landed!");
|
||||
|
@ -129,6 +155,8 @@ function gameStep() {
|
|||
else exploded = true;
|
||||
}
|
||||
else {
|
||||
if (lightning) ++lightning;
|
||||
if (settings.lightning && (lightning==0||lightning>40) && Math.random()>0.98) lightning = 1;
|
||||
booster.x += booster.vx*dt;
|
||||
booster.y += booster.vy*dt;
|
||||
booster.vy += gravity*dt;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "f9lander",
|
||||
"name": "Falcon9 Lander",
|
||||
"shortName":"F9lander",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Land a rocket booster",
|
||||
"icon": "f9lander.png",
|
||||
"screenshots" : [ { "url":"f9lander_screenshot1.png" }, { "url":"f9lander_screenshot2.png" }, { "url":"f9lander_screenshot3.png" }],
|
||||
|
@ -10,6 +10,7 @@
|
|||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"f9lander.app.js","url":"app.js"},
|
||||
{"name":"f9lander.img","url":"app-icon.js","evaluate":true}
|
||||
{"name":"f9lander.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"f9lander.settings.js", "url":"settings.js"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// This file should contain exactly one function, which shows the app's settings
|
||||
/**
|
||||
* @param {function} back Use back() to return to settings menu
|
||||
*/
|
||||
const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
|
||||
(function(back) {
|
||||
const SETTINGS_FILE = 'f9settings.json'
|
||||
// initialize with default settings...
|
||||
let settings = {
|
||||
'lightning': false,
|
||||
}
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage')
|
||||
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||
for (const key in saved) {
|
||||
settings[key] = saved[key];
|
||||
}
|
||||
// creates a function to safe a specific setting, e.g. save('color')(1)
|
||||
function save(key) {
|
||||
return function (value) {
|
||||
settings[key] = value;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'OpenWind' },
|
||||
'< Back': back,
|
||||
'Lightning': {
|
||||
value: settings.lightning,
|
||||
format: boolFormat,
|
||||
onchange: save('lightning'),
|
||||
}
|
||||
}
|
||||
E.showMenu(menu);
|
||||
})
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Allow redirection of loads to the launcher
|
||||
0.03: Allow hiding the fastloading info screen
|
|
@ -0,0 +1,21 @@
|
|||
# Fastload Utils
|
||||
|
||||
*EXPERIMENTAL* Use this with caution. When you find something misbehaving please check if the problem actually persists when removing this app.
|
||||
|
||||
This allows fast loading of all apps with two conditions:
|
||||
* Loaded app contains `Bangle.loadWidgets`. This is needed to prevent problems with apps not expecting widgets to be already loaded.
|
||||
* Current app can be removed completely from RAM.
|
||||
|
||||
## Settings
|
||||
|
||||
* Allows to redirect all loads usually loading the clock to the launcher instead
|
||||
* The "Fastloading..." screen can be switched off
|
||||
|
||||
## Technical infos
|
||||
|
||||
This is still experimental but it uses the same mechanism as `.bootcde` does.
|
||||
It checks the app to be loaded for widget use and stores the result of that and a hash of the js in a cache.
|
||||
|
||||
# Creator
|
||||
|
||||
[halemmerich](https://github.com/halemmerich)
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
const SETTINGS = require("Storage").readJSON("fastload.json") || {};
|
||||
|
||||
let loadingScreen = function(){
|
||||
g.reset();
|
||||
|
||||
let x = g.getWidth()/2;
|
||||
let y = g.getHeight()/2;
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillRect(x-49, y-19, x+49, y+19);
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawRect(x-50, y-20, x+50, y+20);
|
||||
g.setFont("6x8");
|
||||
g.setFontAlign(0,0);
|
||||
g.drawString("Fastloading...", x, y);
|
||||
g.flip(true);
|
||||
};
|
||||
|
||||
let cache = require("Storage").readJSON("fastload.cache") || {};
|
||||
|
||||
let checkApp = function(n){
|
||||
// no widgets, no problem
|
||||
if (!global.WIDGETS) return true;
|
||||
let app = require("Storage").read(n);
|
||||
if (cache[n] && E.CRC32(app) == cache[n].crc)
|
||||
return cache[n].fast
|
||||
cache[n] = {};
|
||||
cache[n].fast = app.includes("Bangle.loadWidgets");
|
||||
cache[n].crc = E.CRC32(app);
|
||||
require("Storage").writeJSON("fastload.cache", cache);
|
||||
return cache[n].fast;
|
||||
}
|
||||
|
||||
global._load = load;
|
||||
|
||||
let slowload = function(n){
|
||||
global._load(n);
|
||||
}
|
||||
|
||||
let fastload = function(n){
|
||||
if (!n || checkApp(n)){
|
||||
// Bangle.load can call load, to prevent recursion this must be the system load
|
||||
global.load = slowload;
|
||||
Bangle.load(n);
|
||||
// if fastloading worked, we need to set load back to this method
|
||||
global.load = fastload;
|
||||
}
|
||||
else
|
||||
slowload(n);
|
||||
};
|
||||
global.load = fastload;
|
||||
|
||||
Bangle.load = (o => (name) => {
|
||||
if (Bangle.uiRemove && !SETTINGS.hideLoading) loadingScreen();
|
||||
if (SETTINGS.autoloadLauncher && !name){
|
||||
let orig = Bangle.load;
|
||||
Bangle.load = (n)=>{
|
||||
Bangle.load = orig;
|
||||
fastload(n);
|
||||
}
|
||||
Bangle.showLauncher();
|
||||
Bangle.load = orig;
|
||||
} else
|
||||
o(name);
|
||||
})(Bangle.load);
|
||||
}
|
After Width: | Height: | Size: 1.1 KiB |