Merge branch 'master' of github.com:espruino/BangleApps

pull/1547/head
Gordon Williams 2022-03-07 15:55:57 +00:00
commit 970a50b6c4
31 changed files with 891 additions and 168 deletions

1
apps/clockcal/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Initial upload

21
apps/clockcal/README.md Normal file
View File

@ -0,0 +1,21 @@
# Clock & Calendar by Michael
This is my "Hello World". I first made this watchface almost 10 years ago for my original Pebble and Pebble Time and I missed this so much, that I had to write it for the BangleJS2.
I know that it seems redundant because there already **is** a *time&cal*-app, but it didn't fit my style.
- locked screen with only one minimal update/minute
- ![locked screen](https://foostuff.github.io/BangleApps/apps/clockcal/screenshot.png)
- unlocked screen (twist?) with seconds
- ![unlocked screen](https://foostuff.github.io/BangleApps/apps/clockcal/screenshot2.png)
## Configurable Features
- Number of calendar rows (weeks)
- Buzz on connect/disconnect (I know, this should be an extra widget, but for now, it is included)
- Clock Mode (24h/12h). Doesn't have an am/pm indicator. It's only there because it was easy.
- First day of the week
- Red Saturday
- Red Sunday
## Feedback
The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings.
So if something isn't working, please tell me: https://github.com/foostuff/BangleApps/issues

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkECqMCkQACiEDkIXQuUnkUBkESiYXPgN/u8jgEx/8vC6E3k9xiH//8/C6BHCPQMSL6EDO4cgaf4A/ACEC+YFDl4FEAAM/+ISHbIIECh4FB+QWEA4PwCQsfC4gVBkYGDgP/mQ4CCQk/iAXEAQTiCgMiDQQSFiATDBgQXCgILBEQkQBwYrEC4sPLQRpCBwoXECgUCC4oSBAggXHNQRfDV4X/JgQXJBIIXFgYuDC5QKBiE/C4f/bwgXJmanGJgoSDiTQBmQMBE4JYBfwJ5BBYMiYQISEB4IAB+KdCAgfwAwTrCn4SDiczAAMwGwMTmR0CmECBgRSBCQwA/AGsBgEQAgYABAwcHu93s4GBqAXEmLrCiYICmICBj4XEgvABIMMqECiIXCgQXCegLYBC4NwF4VcAQNV4EPkEhF4REBgYXCiQvCu4UCAQMFJYRfKgxGBuxfGLgkjFgMCkMBmEjgEigZaBI4XFMYcRC4kBmRhBkMQgI5DF4MFgAXCLARfCFoIvDkZmBhnF4sA5gvDYghfEHIQJDAAhQBIAPwVQMTgQvCNIMhAwJfBR4MMU4JRB+RJBiUQgUDVwMgYwMBgcwX4amBqBQBiTqBgUQh8RmJhCL4IvC4HMR4ZaEAgIBBL4LBDL5EBmI5BkQvBXwIGBmMPMwMvkEFR4VcR4UgU4MSC4UQmIJBn7dBiQNBqoXBPYNQh8Q+MB+MvgEvG4JyBj8A+RkBhlQd4ZHBiBYCL4bBELxEAA=="))

119
apps/clockcal/app.js Normal file
View File

@ -0,0 +1,119 @@
Bangle.loadWidgets();
var s = Object.assign({
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
MODE24: true, //24h mode vs 12h mode
FIRSTDAYOFFSET: 6, //First day of the week: 0-6: Sun, Sat, Fri, Thu, Wed, Tue, Mon
REDSUN: true, // Use red color for sunday?
REDSAT: true, // Use red color for saturday?
}, require('Storage').readJSON("clockcal.json", true) || {});
const h = g.getHeight();
const w = g.getWidth();
const CELL_W = w / 7;
const CELL_H = 15;
const CAL_Y = h - s.CAL_ROWS * CELL_H;
const DEBUG = false;
function drawMinutes() {
if (DEBUG) console.log("|-->minutes");
var d = new Date();
var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' ');
var minutes = d.getMinutes().toString().padStart(2, '0');
var textColor = NRF.getSecurityStatus().connected ? '#fff' : '#f00';
var size = 50;
var clock_x = (w - 20) / 2;
if (dimSeconds) {
size = 65;
clock_x = 4 + (w / 2);
}
g.setBgColor(0);
g.setColor(textColor);
g.setFont("Vector", size);
g.setFontAlign(0, 1);
g.drawString(hours + ":" + minutes, clock_x, CAL_Y - 10, 1);
var nextminute = (61 - d.getSeconds());
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
minuteInterval = setTimeout(drawMinutes, nextminute * 1000);
}
function drawSeconds() {
if (DEBUG) console.log("|--->seconds");
var d = new Date();
g.setColor();
g.fillRect(w - 31, CAL_Y - 36, w - 3, CAL_Y - 19);
g.setBgColor(0);
g.setColor('#fff');
g.setFont("Vector", 24);
g.setFontAlign(1, 1);
g.drawString(" " + d.getSeconds().toString().padStart(2, '0'), w, CAL_Y - 13);
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
if (!dimSeconds) secondInterval = setTimeout(drawSeconds, 1000);
}
function drawCalendar() {
if (DEBUG) console.log("CALENDAR");
var d = new Date();
g.reset();
g.setBgColor(0);
g.clear();
drawMinutes();
if (!dimSeconds) drawSeconds();
const dow = (s.FIRSTDAYOFFSET + d.getDay()) % 7; //MO=0, SU=6
const today = d.getDate();
var rD = new Date(d.getTime());
rD.setDate(rD.getDate() - dow);
var rDate = rD.getDate();
g.setFontAlign(1, 1);
for (var y = 1; y <= s.CAL_ROWS; y++) {
for (var x = 1; x <= 7; x++) {
bottomrightX = x * CELL_W - 2;
bottomrightY = y * CELL_H + CAL_Y;
g.setFont("Vector", 16);
var fg = ((s.REDSUN && rD.getDay() == 0) || (s.REDSAT && rD.getDay() == 6)) ? '#f00' : '#fff';
if (y == 1 && today == rDate) {
g.setColor('#0f0');
g.fillRect(bottomrightX - CELL_W + 1, bottomrightY - CELL_H - 1, bottomrightX, bottomrightY - 2);
g.setColor('#000');
g.drawString(rDate, bottomrightX, bottomrightY);
}
else {
g.setColor(fg);
g.drawString(rDate, bottomrightX, bottomrightY);
}
rD.setDate(rDate + 1);
rDate = rD.getDate();
}
}
Bangle.drawWidgets();
var nextday = (3600 * 24) - (d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds() + 1);
if (DEBUG) console.log("Next Day:" + (nextday / 3600));
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
dayInterval = setTimeout(drawCalendar, nextday * 1000);
}
function BTevent() {
drawMinutes();
if (s.BUZZ_ON_BT) {
var interval = (NRF.getSecurityStatus().connected) ? 100 : 500;
Bangle.buzz(interval);
setTimeout(function () { Bangle.buzz(interval); }, interval * 3);
}
}
//register events
Bangle.on('lock', locked => {
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
dimSeconds = locked; //dim seconds if lock=on
drawCalendar();
});
NRF.on('connect', BTevent);
NRF.on('disconnect', BTevent);
dimSeconds = Bangle.isLocked();
drawCalendar();
Bangle.setUI("clock");

BIN
apps/clockcal/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,19 @@
{
"id": "clockcal",
"name": "Clock & Calendar",
"version": "0.01",
"description": "Clock with Calendar",
"readme":"README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"clockcal.app.js","url":"app.js"},
{"name":"clockcal.settings.js","url":"settings.js"},
{"name":"clockcal.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"clockcal.json"}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

92
apps/clockcal/settings.js Normal file
View File

@ -0,0 +1,92 @@
(function (back) {
var FILE = "clockcal.json";
settings = Object.assign({
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
MODE24: true, //24h mode vs 12h mode
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
REDSUN: true, // Use red color for sunday?
REDSAT: true, // Use red color for saturday?
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
menu = {
"": { "title": "Clock & Calendar" },
"< Back": () => back(),
'Buzz(dis)conn.?': {
value: settings.BUZZ_ON_BT,
format: v => v ? "On" : "Off",
onchange: v => {
settings.BUZZ_ON_BT = v;
writeSettings();
}
},
'#Calendar Rows': {
value: settings.CAL_ROWS,
min: 0, max: 6,
onchange: v => {
settings.CAL_ROWS = v;
writeSettings();
}
},
'Clock mode': {
value: settings.MODE24,
format: v => v ? "24h" : "12h",
onchange: v => {
settings.MODE24 = v;
writeSettings();
}
},
'First Day': {
value: settings.FIRSTDAY,
min: 0, max: 6,
format: v => ["Sun", "Sat", "Fri", "Thu", "Wed", "Tue", "Mon"][v],
onchange: v => {
settings.FIRSTDAY = v;
writeSettings();
}
},
'Red Saturday?': {
value: settings.REDSAT,
format: v => v ? "On" : "Off",
onchange: v => {
settings.REDSAT = v;
writeSettings();
}
},
'Red Sunday?': {
value: settings.REDSUN,
format: v => v ? "On" : "Off",
onchange: v => {
settings.REDSUN = v;
writeSettings();
}
},
'Load deafauls?': {
value: 0,
min: 0, max: 1,
format: v => ["No", "Yes"][v],
onchange: v => {
if (v == 1) {
settings = {
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect.
MODE24: true, //24h mode vs 12h mode
FIRSTDAY: 6, //First day of the week: mo, tu, we, th, fr, sa, su
REDSUN: true, // Use red color for sunday?
REDSAT: true, // Use red color for saturday?
};
writeSettings();
load()
}
}
},
}
// Show the menu
E.showMenu(menu);
})

View File

@ -1,3 +1,4 @@
0.01: first release
0.02: added settings menu to change color
0.03: fix metadata.json to allow setting as clock
0.04: added heart rate which is switched on when cycled to it through up/down touch on rhs

View File

@ -1,4 +1,4 @@
# Daisy
# Daisy ![](app.png)
*A beautiful digital clock with large ring guage, idle timer and a
cyclic information line that includes, day, date, steps, battery,
@ -8,15 +8,20 @@ Written by: [Hugh Barney](https://github.com/hughbarney) For support
and discussion please post in the [Bangle JS
Forum](http://forum.espruino.com/microcosms/1424/)
* Derived from `The Ring` proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel)
* Derived from [The Ring](https://banglejs.com/apps/?id=thering) proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel)
* Includes the [Lazybones](https://banglejs.com/apps/?q=lazybones) Idle warning timer
* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset)
* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate)
* The heart rate monitor is turned on only when Heart rate is selected and will take a few seconds to settle
* The heart value is displayed in RED if the confidence value is less than 50%
* NOTE: The heart rate monitor of Bangle JS 2 is not very accurate when moving about.
See [#1248](https://github.com/espruino/BangleApps/issues/1248)
* Uses mylocation.json from MyLocation app to calculate sunrise and sunset times for your location
* If your Sunrise, Sunset times look odd make sure you have setup your location using
[MyLocation](https://banglejs.com/apps/?id=mylocation)
* The screen is updated every minute to save battery power
* Uses the [BloggerSansLight](https://www.1001fonts.com/rounded-fonts.html?page=3) font, which if free for commercial use
## Future Development
* Add a heart rate option in the information line that turns on when selected
* Use mini icons in the information line rather that text
* Add weather icons as per Pastel clock
* Add a lock icon to the screen
@ -25,5 +30,3 @@ Forum](http://forum.espruino.com/microcosms/1424/)
![](screenshot_daisy1.png)
It is worth looking at the real thing though as the screenshot does not do it justice.
(Though I need to redo this photo at some point)
![](screenshot_daisy2.jpg)

View File

@ -14,16 +14,19 @@ let warned = 0;
let idle = false;
let IDLE_MINUTES = 26;
var pal1; // palette for 0-40%
var pal2; // palette for 50-100%
const infoWidth = 50;
const infoHeight = 14;
let pal1; // palette for 0-40%
let pal2; // palette for 50-100%
const infoLine = (3*h/4) - 6;
const infoWidth = 56;
const infoHeight = 11;
var drawingSteps = false;
function log_debug(o) {
//print(o);
}
var hrmImg = require("heatshrink").decompress(atob("i0WgIKHgPh8Ef5/g///44CBz///1///5A4PnBQk///wA4PBA4MDA4MH/+Ah/8gEP4EAjw0GA"));
// https://www.1001fonts.com/rounded-fonts.html?page=3
Graphics.prototype.setFontBloggerSansLight46 = function(scale) {
// Actual height 46 (45 - 0)
@ -109,7 +112,8 @@ const infoData = {
ID_SR: { calc: () => 'Sunrise: ' + sunRise },
ID_SS: { calc: () => 'Sunset: ' + sunSet },
ID_STEP: { calc: () => 'Steps: ' + getSteps() },
ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' }
ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' },
ID_HRM: { calc: () => hrmCurrent }
};
const infoList = Object.keys(infoData).sort();
@ -121,6 +125,9 @@ function nextInfo() {
if (idx === infoList.length - 1) infoMode = infoList[0];
else infoMode = infoList[idx + 1];
}
// power HRM on/off accordingly
Bangle.setHRMPower(infoMode == "ID_HRM" ? 1 : 0);
resetHrm();
}
function prevInfo() {
@ -129,8 +136,125 @@ function prevInfo() {
if (idx === 0) infoMode = infoList[infoList.length - 1];
else infoMode = infoList[idx - 1];
}
// power HRM on/off accordingly
Bangle.setHRMPower(infoMode == "ID_HRM" ? 1 : 0);
resetHrm();
}
function clearInfo() {
g.setColor(g.theme.bg);
//g.setColor(g.theme.fg);
g.fillRect((w/2) - infoWidth, infoLine - infoHeight, (w/2) + infoWidth, infoLine + infoHeight);
}
function drawInfo() {
clearInfo();
g.setColor(g.theme.fg);
setSmallFont();
g.setFontAlign(0,0);
if (infoMode == "ID_HRM") {
clearInfo();
g.setColor('#f00'); // red
drawHeartIcon();
} else {
g.drawString((infoData[infoMode].calc()), w/2, infoLine);
}
}
function drawHeartIcon() {
g.drawImage(hrmImg, (w/2) - infoHeight - 20, infoLine - infoHeight);
}
function drawHrm() {
if (idle) return; // dont draw while prompting
var d = new Date();
clearInfo();
g.setColor(d.getSeconds()&1 ? '#f00' : g.theme.bg);
drawHeartIcon();
setSmallFont();
g.setFontAlign(-1,0); // left
g.setColor(hrmConfidence >= 50 ? g.theme.fg : '#f00');
g.drawString(hrmCurrent, (w/2) + 10, infoLine);
}
function draw() {
if (!idle)
drawClock();
else
drawIdle();
queueDraw();
}
function drawClock() {
var date = new Date();
var timeStr = require("locale").time(date,1);
var da = date.toString().split(" ");
var time = da[4].substr(0,5);
var hh = da[4].substr(0,2);
var mm = da[4].substr(3,2);
var steps = getSteps();
var p_steps = Math.round(100*(steps/10000));
g.reset();
g.setColor(g.theme.bg);
g.fillRect(0, 0, w, h);
g.drawImage(getGaugeImage(p_steps), 0, 0);
setLargeFont();
g.setColor(settings.fg);
g.setFontAlign(1,0); // right aligned
g.drawString(hh, (w/2) - 1, h/2);
g.setColor(g.theme.fg);
g.setFontAlign(-1,0); // left aligned
g.drawString(mm, (w/2) + 1, h/2);
drawInfo();
// recalc sunrise / sunset every hour
if (drawCount % 60 == 0)
updateSunRiseSunSet(new Date(), location.lat, location.lon);
drawCount++;
}
function drawSteps() {
if (drawingSteps) return;
drawingSteps = true;
clearInfo();
setSmallFont();
g.setFontAlign(0,0);
g.setColor(g.theme.fg);
g.drawString('Steps ' + getSteps(), w/2, (3*h/4) - 4);
drawingSteps = false;
}
///////////////// GAUGE images /////////////////////////////////////
var hrmCurrent = "--";
var hrmConfidence = 0;
function resetHrm() {
hrmCurrent = "--";
hrmConfidence = 0;
if (infoMode == "ID_HRM") {
clearInfo();
g.setColor('#f00'); // red
drawHeartIcon();
}
}
Bangle.on('HRM', function(hrm) {
hrmCurrent = hrm.bpm;
hrmConfidence = hrm.confidence;
log_debug("HRM=" + hrm.bpm + " (" + hrm.confidence + ")");
if (infoMode == "ID_HRM" ) drawHrm();
});
///////////////// GAUGE images /////////////////////////////////////
// putting into 1 function like this, rather than individual variables
// reduces ram usage from 70%-13%
function getGaugeImage(p) {
@ -247,68 +371,6 @@ function getGaugeImage(p) {
};
}
function draw() {
if (!idle)
drawClock();
else
drawIdle();
queueDraw();
}
function drawClock() {
var date = new Date();
var timeStr = require("locale").time(date,1);
var da = date.toString().split(" ");
var time = da[4].substr(0,5);
var hh = da[4].substr(0,2);
var mm = da[4].substr(3,2);
var steps = getSteps();
var p_steps = Math.round(100*(steps/10000));
g.reset();
g.setColor(g.theme.bg);
g.fillRect(0, 0, w, h);
g.drawImage(getGaugeImage(p_steps), 0, 0);
setLargeFont();
g.setColor(settings.fg);
g.setFontAlign(1,0); // right aligned
g.drawString(hh, (w/2) - 1, h/2);
g.setColor(g.theme.fg);
g.setFontAlign(-1,0); // left aligned
g.drawString(mm, (w/2) + 1, h/2);
setSmallFont();
g.setFontAlign(0,0); // left aligned
g.drawString((infoData[infoMode].calc()), w/2, (3*h/4) - 4);
// recalc sunrise / sunset every hour
if (drawCount % 60 == 0)
updateSunRiseSunSet(new Date(), location.lat, location.lon);
drawCount++;
}
function drawSteps() {
if (drawingSteps) return;
drawingSteps = true;
setSmallFont();
g.setFontAlign(0,0);
var steps = getSteps();
g.setColor(g.theme.bg);
g.fillRect((w/2) - infoWidth, (3*h/4) - infoHeight, (w/2) + infoWidth, (3*h/4) + infoHeight);
g.setColor(g.theme.fg);
g.drawString('Steps ' + steps, w/2, (3*h/4) - 4);
drawingSteps = false;
}
/*
Bangle.on('step', s => {
drawSteps();
});
*/
///////////////// IDLE TIMER /////////////////////////////////////
function drawIdle() {
@ -392,6 +454,8 @@ Bangle.on('step', s => {
}
idle = false;
warned = 0;
if (infoMode == "ID_STEP") drawSteps();
});
function checkIdle() {

View File

@ -1,6 +1,6 @@
{ "id": "daisy",
"name": "Daisy",
"version":"0.03",
"version":"0.04",
"dependencies": {"mylocation":"app"},
"description": "A clock based on the Pastel clock with large ring guage for steps",
"icon": "app.png",

View File

@ -6,3 +6,5 @@
0.06: Adds settings page (hide clocks or launchers)
0.07: Adds setting for directly launching app on touch for Bangle 2
0.08: Optimize line wrapping for Bangle 2
0.09: fix the trasparent widget bar if there are no widgets for Bangle 2
0.10: added "one click exit" setting for Bangle 2

View File

@ -6,8 +6,12 @@ var settings = Object.assign({
showClocks: true,
showLaunchers: true,
direct: false,
oneClickExit:false
}, require('Storage').readJSON("dtlaunch.json", true) || {});
if( settings.oneClickExit)
setWatch(_=> load(), BTN1);
var s = require("Storage");
var apps = s.list(/\.info$/).map(app=>{
var a=s.readJSON(app,1);
@ -125,5 +129,6 @@ Bangle.on("touch",(_,p)=>{
});
Bangle.loadWidgets();
g.clear();
Bangle.drawWidgets();
drawPage(0);

View File

@ -1,7 +1,7 @@
{
"id": "dtlaunch",
"name": "Desktop Launcher",
"version": "0.08",
"version": "0.10",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png",

View File

@ -4,7 +4,8 @@
var settings = Object.assign({
showClocks: true,
showLaunchers: true,
direct: false
direct: false,
oneClickExit:false
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
@ -37,6 +38,14 @@
settings.direct = v;
writeSettings();
}
},
'One click exit': {
value: settings.oneClickExit,
format: v => v?"On":"Off",
onchange: v => {
settings.oneClickExit = v;
writeSettings();
}
}
});
})

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Set Bangle.js 2 compatible

View File

@ -2,12 +2,12 @@
"id": "gpsautotime",
"name": "GPS auto time",
"shortName": "GPS auto time",
"version": "0.01",
"version": "0.02",
"description": "A widget that automatically updates the Bangle.js time to the GPS time whenever there is a valid GPS fix.",
"icon": "widget.png",
"type": "widget",
"tags": "widget,gps",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"gpsautotime.wid.js","url":"widget.js"}
]

View File

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

View File

@ -0,0 +1,15 @@
# Rachet Launcher
Ratchet Launcher is a fork of the default Launcher with modified user interaction. Instead of free scrolling, apps are selected by swiping up and down, but in discrete "ticks", just like in the settings menus.
**WARNING: Untested on Bangle.js v1! Please test and give feedback.**
## Usage
- Choose app: Swipe up/down (top/bottom button on Bangle.js v1)
- Launch app: Tap screen (center button on Bangle.js v1)
- Return to clock: Swipe three ticks beyond first/last app in list
## Installation
1. Install Ratchet Launcher using App Loader
2. Uninstall default Launcher
3. Reload

149
apps/ratchet_launch/app.js Normal file
View File

@ -0,0 +1,149 @@
var Storage = require("Storage");
var Layout = require("Layout");
var font = g.getFonts().includes("6x15") ? "6x15" : "6x8:2";
var largeFont = g.getFonts().includes("12x20") ? "12x20" : "6x8:3";
var currentApp = 0;
var overscroll = 0;
var blankImage = Graphics.createImage(` `);
var rowHeight = g.getHeight()/3;
// Load apps list
var apps = Storage.list(/\.info$/).map(app=>{
var a=Storage.readJSON(app,1);
return a&&{
name:a.name,
type:a.type,
icon:a.icon ? Storage.read(a.icon) : a.icon,
sortorder:a.sortorder,
src:a.src
};
}).filter(app=>app && (
app.type=="app"
// || (app.type=="clock" && settings.showClocks)
|| !app.type
));
apps.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first
if (a.name<b.name) return -1;
if (a.name>b.name) return 1;
return 0;
});
// Uncomment for testing in the emulator without apps:
// apps = [
// {
// name:"Test",
// type:"app",
// icon:blankImage,
// sortorder:undefined,
// src:""
// },
// {
// name:"Test 2",
// type:"app",
// icon:blankImage,
// sortorder:undefined,
// src:""
// },
// ];
// Initialize layout
var layout = new Layout({
type:"v", c:[
// A row for the previous app
{ type:"h", height:rowHeight, c:[
{type: "img", id:"prev_icon", src:blankImage, width:48, height:48, scale:0.8, pad:8},
{type: "txt", id:"prev_name", label:"", font:font, fillx:1, wrap:1},
]},
// A row for the current app
{ type:"h", height:rowHeight, c:[
{type: "img", id:"cur_icon", src:blankImage, width:48, height:48},
{type: "txt", id:"cur_name", label:"", font:largeFont, fillx:1, wrap:1},
]},
// A row for the next app
{ type:"h", height:rowHeight, c:[
{type: "img", id:"next_icon", src:blankImage, width:48, height:48, scale:0.8, pad:8},
{type: "txt", id:"next_name", label:"", font:font, fillx:1, wrap:1},
]},
]
});
// Drawing logic
function render() {
if (!apps.length) {
E.showMessage(/*LANG*/"No apps");
return load();
}
// Previous app
if (currentApp > 0) {
layout.prev_icon.src = apps[currentApp-1].icon;
layout.prev_name.label = apps[currentApp-1].name;
} else {
layout.prev_icon.src = blankImage;
layout.prev_name.label = "";
}
// Current app
layout.cur_icon.src = apps[currentApp].icon;
layout.cur_name.label = apps[currentApp].name;
// Next app
if (currentApp < apps.length-1) {
layout.next_icon.src = apps[currentApp+1].icon;
layout.next_name.label = apps[currentApp+1].name;
} else {
layout.next_icon.src = blankImage;
layout.next_name.label = "";
}
g.clear();
layout.render();
}
// Launch the currently selected app
function launch() {
var app = apps[currentApp];
if (!app) return;
if (!app.src || Storage.read(app.src)===undefined) {
E.showMessage(/*LANG*/"App Source\nNot found");
setTimeout(render, 2000);
} else {
E.showMessage(/*LANG*/"Loading...");
load(app.src);
}
}
// Select previous/next app
function move(step) {
if ((currentApp == 0 && step < 0) || (currentApp >= apps.length-1 && step > 0)) {
// When we hit the end of the list (top or bottom), the step is
// counted towards the overscroll value. When the overscroll
// threshold is exceeded, we return to the clock face.
overscroll += step;
} else {
// This is the default case: the step is countedf towards the currentApp index
currentApp += step;
overscroll = 0;
return render();
}
// Overscroll threshold reached, return to clock
if (Math.abs(overscroll) > 3) {
Bangle.buzz(500, 1);
return load();
}
}
// Wire up user input
Bangle.setUI('updown', dir => {
if (!dir) launch();
else {
if (process.env.HWVERSION==2) dir *= -1; // "natural scrolling" on touch screen
move(dir);
}
});
render();

BIN
apps/ratchet_launch/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -0,0 +1,16 @@
{
"id": "ratchet_launch",
"name": "Ratchet Launcher",
"shortName": "Ratchet",
"version": "0.01",
"description": "Launcher with discrete scrolling for quicker app selection",
"icon": "app.png",
"type": "launch",
"tags": "tool,system,launcher",
"supports": ["BANGLEJS2","BANGLEJS"],
"storage": [
{"name":"ratchet_launch.app.js","url":"app.js"}
],
"sortorder": -10,
"readme":"README.md"
}

View File

@ -1,2 +1,4 @@
0.01: Initial version
0.02: Add battery level
0.03: Fix battery display when full
0.04: Add support for settings

View File

@ -3,3 +3,21 @@
Just a simple watch face for the Banglejs2.
It shows battery level in the upper left corner, date information in the upper right, and time information in the bottom.
![](screenshot.png)
## Settings
**Analog Clock:**
**Human Readable Date:** When the setting is on, the date is shown in a more human-friendly format (e.g. "Oct 2"), otherwise the date is shown in a standard format (e.g. "02/10"). Default is off.
**Show Week Info:** When the setting is on, the weekday and week number are shown in the upper right box. When the setting is off, the full year is shown instead. Default is off.
**Vector Font:** When the setting is on, the app uses Espruino's vector font, otherwise it uses the default font. Default is off.
## Using the app
Monogram Watch Face can be selected as the default clock or it can be run manually from the launcher. Its settings can be accessed and changed via the relevant menu.
Tapping on the "Alerts" area will replace the current time display with the time of the most immediate alert.

View File

@ -1,48 +1,81 @@
const SETTINGSFILE = "smclock.json";
const background = {
width : 176, height : 176, bpp : 3,
transparent : 1,
buffer : require("heatshrink").decompress(atob("/4A/AH4ACUb8H9MkyVJAThB/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INP/AH4A/AAX8Yz4Afn5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INI="))
width: 176,
height: 176,
bpp: 3,
transparent: 1,
buffer: require("heatshrink").decompress(
atob(
"/4A/AH4ACUb8H9MkyVJAThB/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INP/AH4A/AAX8Yz4Afn5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INI="
)
),
};
const monthName = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
const weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const weekday = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];
var level = -1;
// dynamic variables
var batLevel = -1;
var batColor = [0, 0, 0];
function ISO8601_week_no(date) { //copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
var tdt = new Date(date.valueOf());
var dayn = (date.getDay() + 6) % 7;
tdt.setDate(tdt.getDate() - dayn + 3);
var firstThursday = tdt.valueOf();
tdt.setMonth(0, 1);
if (tdt.getDay() !== 4) {
tdt.setMonth(0, 1 + ((4 - tdt.getDay()) + 7) % 7);
}
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
// settings variables
var dateFormat;
var drawInterval;
var pollInterval;
var showAnalogFace;
var showWeekInfo;
var useVectorFont;
// load settings
function loadSettings() {
// Helper function default setting
function def(value, def) {return value !== undefined ? value : def;}
var settings = require("Storage").readJSON(SETTINGSFILE, true) || {};
dateFormat = def(settings.dateFormat, "Short");
drawInterval = def(settings.drawInterval, 10);
pollInterval = def(settings.pollInterval, 60);
showAnalogFace = def(settings.showAnalogFace, false);
showWeekInfo = def(settings.showWeekInfo, false);
useVectorFont = def(settings.useVectorFont, false);
}
// copied from: https://gist.github.com/IamSilviu/5899269#gistcomment-3035480
function ISO8601_week_no(date) {
var tdt = new Date(date.valueOf());
var dayn = (date.getDay() + 6) % 7;
tdt.setDate(tdt.getDate() - dayn + 3);
var firstThursday = tdt.valueOf();
tdt.setMonth(0, 1);
if (tdt.getDay() !== 4) {
tdt.setMonth(0, 1 + ((4 - tdt.getDay() + 7) % 7));
}
return 1 + Math.ceil((firstThursday - tdt) / 604800000);
}
function d02(value) {
return ('0' + value).substr(-2);
return ("0" + value).substr(-2);
}
function pollBattery() {
level = E.getBattery();
return level;
batLevel = E.getBattery();
}
function getBatteryColor(level) {
var color;
if (level < 0) {
level = pollBattery();
pollBattery();
level = batLevel;
}
if(level>80) {
color = [0,0,1];
} else if(level>60) {
color = [0,1,1];
} else if(level>40) {
color = [0,1,0];
} else if(level>20) {
color = [1,1,0];
if (level > 80) {
color = [0, 0, 1];
} else if (level > 60) {
color = [0, 1, 1];
} else if (level > 40) {
color = [0, 1, 0];
} else if (level > 20) {
color = [1, 1, 0];
} else {
color = [1,0,0];
color = [1, 0, 0];
}
return color;
}
@ -50,57 +83,110 @@ function getBatteryColor(level) {
function draw() {
g.drawImage(background);
const color = getBatteryColor();
const bat = d02(E.getBattery()) + "%";
const color = getBatteryColor(batLevel);
var bat = "";
const d = new Date();
const day = d.getDate();
const month = (d.getMonth() + 1);
const month = d.getMonth() + 1;
const week = d02(ISO8601_week_no(d));
const date1 = d02(day) + "/" + d02(month);
const date2 = weekday[d.getDay()] + " " + d02(week);
var date1 = "";
var date2 = "";
const h = d.getHours();
const m = d.getMinutes();
const time = d02(h) + ":" + d02(m);
if (E.getBattery() < 100) {
bat = d02(E.getBattery()) + "%";
} else {
bat = E.getBattery() + "%";
}
g.reset();
g.setColor(0, 0, 0);
g.setFont("Vector", 20);
g.drawString(date1, 105, 20, false);
g.setFont("Vector", 16);
g.drawString(date2, 105, 55, false);
// draw battery info
g.setColor(1, 1, 1);
g.setFont("Vector", 60);
g.drawString(time, 10, 108, false);
g.setColor(1, 1, 1);
g.setFont("Vector", 16);
g.drawString("Bat:", 12, 22, false);
if (useVectorFont == true) {
g.setFont("Vector", 16);
g.drawString("Bat:", 12, 22, false);
} else {
g.setFont("4x6", 2);
g.drawString("Bat:", 10, 22, false);
}
g.setColor(color[0], color[1], color[2]);
g.drawString(bat, 52, 22, false);
if (batLevel < 100) {
g.drawString(bat, 52, 22, false);
} else {
g.drawString(bat, 46, 22, false);
}
// draw date info
g.setColor(0, 0, 0);
if (useVectorFont == true) {
g.setFont("Vector", 20);
} else {
g.setFont("6x8", 2);
}
if (dateFormat == "Short") {
date1 = d02(day) + "/" + d02(month);
g.drawString(date1, 105, 20, false);
} else {
date1 = monthName[month - 1] + d02(day);
g.drawString(date1, 104, 20, false);
}
// draw week info
if (showWeekInfo == true) {
date2 = weekday[d.getDay()] + " " + d02(week)
if (useVectorFont == true) {
g.setFont("Vector", 18);
} else {
g.setFont("6x8", 2);
}
g.drawString(date2, 105, 55, false);
} else {
date2 = d.getFullYear();
if (useVectorFont == true) {
g.setFont("Vector", 22);
g.drawString(date2, 105, 55, false);
} else {
g.setFont("4x6", 3);
g.drawString(date2, 108, 55, false);
}
}
// draw time
g.setColor(1, 1, 1);
if (useVectorFont == true) {
g.setFont("Vector", 60);
g.drawString(time, 10, 108, false);
} else {
g.setFont("6x8", 5);
g.drawString(time, 14, 112, false);
}
}
loadSettings();
g.clear();
pollBattery();
draw();
var batInterval = setInterval(pollBattery, 60000);
var drawInterval = setInterval(draw, 10000);
var batInterval = setInterval(pollBattery, pollInterval * 1000);
var actualDrawInterval = setInterval(draw, drawInterval * 1000);
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
Bangle.on("lcdPower", (on) => {
if (batInterval) clearInterval(batInterval);
batInterval = undefined;
if (drawInterval) clearInterval(drawInterval);
drawInterval = undefined;
if (actualDrawInterval) clearInterval(actualDrawInterval);
actualDrawInterval = undefined;
if (on) {
batInterval = setInterval(pollBattery, 60000);
drawInterval = setInterval(draw, 10000);
batInterval = setInterval(pollBattery, pollInterval * 1000);
actualDrawInterval = setInterval(draw, drawInterval * 1000);
pollBattery();
draw(); // draw immediately
draw();
}
});

View File

@ -1,16 +1,20 @@
{
"id":"smclock",
"name":"Monogram Watch Face",
"shortName":"MonoClock",
"icon":"app.png",
"version":"0.02",
"id": "smclock",
"name": "Monogram Watch Face",
"shortName": "MonoClock",
"icon": "app.png",
"screenshots": [{ "url": "screenshot.png" }],
"version": "0.04",
"description": "A simple watchface based on my stylised monogram.",
"tags":"clock",
"readme":"README.md",
"supports" : ["BANGLEJS2"],
"type": "clock",
"tags": "clock",
"readme": "README.md",
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"smclock.app.js","url":"app.js"},
{"name":"smclock.img","url":"app-icon.js","evaluate":true}
]
{ "name": "smclock.app.js", "url": "app.js" },
{ "name": "smclock.settings.js", "url": "settings.js" },
{ "name": "smclock.img", "url": "app-icon.js", "evaluate": true }
],
"data": [{ "name": "smclock.json" }]
}

BIN
apps/smclock/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

94
apps/smclock/settings.js Normal file
View File

@ -0,0 +1,94 @@
// settings menu for Monogram Watch Face
// Anton Clock settings were used as template
// helper functions taken from Anton Clock
(function (back) {
var FILE = "smclock.json";
// load settings from the file
// assign default values if it doesn't exist
var settings = Object.assign({
dateFormat: "Short",
drawInterval: 10,
pollInterval: 60,
showAnalogFace: false,
showWeekInfo: false,
useVectorFont: false,
}, require("Storage").readJSON(FILE, true) || {});
// write the new settings to the file
function writeSettings() {require("Storage").writeJSON(FILE, settings);}
// helper method which uses int-based menu item for set of string values
function stringItems(startvalue, writer, values) {
return {
value: startvalue === undefined ? 0 : values.indexOf(startvalue),
format: v => values[v],
min: 0,
max: values.length - 1,
wrap: true,
step: 1,
onchange: v => {
writer(values[v]);
writeSettings();
},
};
}
// helper method which breaks string set settings down to local settings object
function stringInSettings(name, values) {
return stringItems(settings[name], (v) => (settings[name] = v), values);
}
// settings menu
var mainmenu = {
"": {title: "Monogram Clock",},
"< Back": () => back(),
"Analog Face": {
value:
settings.showAnalogFace !== undefined ? settings.showAnalogFace : false,
format: v => v ? "On" : "Off",
onchange: v => {
settings.showAnalogFace = v;
writeSettings();
},
},
Date: stringInSettings("dateFormat", ["Long", "Short"]),
"Draw Interval": {
value: settings.drawInterval,
onchange: v => {
settings.drawInterval = v;
writeSettings();
},
},
"Poll Interval": {
value: settings.pollInterval,
onchange: v => {
settings.pollInterval = v;
writeSettings();
},
},
"Week Info": {
value:
settings.showWeekInfo !== undefined ? settings.showWeekInfo : false,
format: v => v ? "On" : "Off",
onchange: v => {
settings.showWeekInfo = v;
writeSettings();
},
},
"Vector Font": {
value:
settings.useVectorFont !== undefined ? settings.useVectorFont : false,
format: v => v ? "On" : "Off",
onchange: v => {
settings.useVectorFont = v;
writeSettings();
},
},
};
// Actually display the menu
E.showMenu(mainmenu);
});
// end of file

View File

@ -11,10 +11,10 @@
"Back": "Terug",
"Repeat": "Herhalen",
"Delete": "Verwijderen",
"ALARM!": "ALARV.",
"Sleep": "Stand-by",
"ALARM!": "ALARM!",
"Sleep": "Standby",
"New Timer": "Nieuwe Timer",
"(repeat)": "(herhaling)",
"(repeat)": "(herhaal)",
"music": "muziek",
"week": "week",
"Auto snooze": "Auto snooze",
@ -31,7 +31,7 @@
"minimum": "minimum",
"valid period": "geldige periode",
"heartrate": "hartslag",
"battery warn": "batterijwaarschuwing",
"battery warn": "batterijwaarsch.",
"data": "gegevens",
"step length": "staplengte",
"min. confidence": "min. vertrouwen",
@ -47,7 +47,7 @@
"Yes\ndefinitely": "Ja\nzeker",
"STEPS": "STAPPEN",
"Show clocks": "Toon klokken",
"Record Run": "Record run",
"Record Run": "Rondje opnemen",
"No Messages": "Geen berichten.",
"View Message": "Bekijk bericht",
"Piezo": "Piëzo",
@ -63,7 +63,7 @@
"Make Connectable": "Maak Verbindbaar",
"Quiet Mode": "Rustige modus",
"BLE": "BLE",
"Dark BW": "Donker BW",
"Dark BW": "Donkere modus",
"Apps": "Apps",
"Programmable": "Programmeerbaar",
"Vibration": "Trilling",
@ -82,32 +82,32 @@
"Remove": "Verwijder",
"Add Device": "Apparaat toevoegen",
"Connect device\nto add to\nwhitelist": "Apparaat aansluiten\ntoe te voegen aan\nwhitelist",
"Wake on Twist": "Wake on Twist",
"Wake on BTN2": "Wake op BTN2",
"Wake on BTN1": "Wake op BTN1",
"Wake on FaceUp": "Wakker worden op FaceUp",
"Wake on Twist": "Aangaan bij draaien",
"Wake on BTN2": "Aangaan bij BTN2",
"Wake on BTN1": "Aangaan bij BTN1",
"Wake on FaceUp": "Aangaan bij FaceUp",
"Log": "Log",
"Debug Info": "Debug info",
"Wake on BTN3": "Wake op BTN3",
"Flatten Battery": "Batterij plat maken",
"Wake on BTN3": "Aangaan bij BTN3",
"Flatten Battery": "Batterij leegmaken",
"Rewrite Settings": "Instellingen herschrijven",
"Compact Storage": "Compacte opslag",
"Utilities": "Nutsbedrijven",
"Compact Storage": "Comprimeer opslag",
"Utilities": "Gereedschap",
"Clock Style": "Klok Stijl",
"Time Zone": "Tijdzone",
"Twist Timeout": "Time-out draaien",
"Twist Max Y": "Twist Max Y",
"Twist Threshold": "Twist Drempel",
"Wake on Touch": "Wakker worden bij aanraking",
"Compacting...\nTakes approx\n1 minute": "Verdichten...\nDuurt ongeveer\n1 minuut",
"Reset to Defaults": "Terugzetten op standaardwaarden",
"Twist Timeout": "Draaien time-out",
"Twist Max Y": "Draaien Max Y",
"Twist Threshold": "Draaien vanaf",
"Wake on Touch": "Aangaan bij aanraking",
"Compacting...\nTakes approx\n1 minute": "Comprimeren...\nDuurt ongeveer\n1 minuut",
"Reset to Defaults": "Terug naar standaardwaarden",
"No Clocks Found": "Geen klokken gevonden",
"Month": "Maand",
"Minute": "Minuutje",
"Minute": "Minuut",
"Flattening battery - this can take hours.\nLong-press button to cancel": "Batterij leegmaken - dit kan uren duren.\nDruk lang op de knop om te annuleren",
"Sleep Phase Alarm": "Slaapfase alarm",
"Second": "Tweede",
"Turn Off": "Zet uit.",
"Turn Off": "Uitzetten",
"Hour": "Uur",
"Storage": "Opslag",
"Date": "Datum",
@ -144,7 +144,7 @@
"Hide": "Verberg",
"Messages": "Berichten",
"Error in settings": "Fout in instellingen",
"BACK": "ACHTER",
"BACK": "TERUG",
"Whitelist": "Whitelist",
"Set Time": "Tijd instellen",
"Disable": "Uitschakelen",
@ -162,7 +162,7 @@
"Loading": "Laden",
"Music": "Muziek",
"color": "kleur",
"off": "van",
"off": "uit",
"Off": "Uit",
"Theme": "Thema"
},