Merge remote-tracking branch 'upstream/master'

master
hughbarney 2021-04-14 22:54:21 +01:00
commit 612717fd54
96 changed files with 3256 additions and 1743 deletions

View File

@ -29,3 +29,4 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
* Added progress bar on Bangle.js for uploads * Added progress bar on Bangle.js for uploads
* Provide a proper error message in case JSON decode fails * Provide a proper error message in case JSON decode fails
* Check you're connecting with a Bangle.js of the correct version * Check you're connecting with a Bangle.js of the correct version
* Allow 'data' style app files to be uploaded with the app (and switch over settings files for various apps)

View File

@ -249,14 +249,20 @@ and which gives information about the app for the Launcher.
{"name":"appid.js", // filename to use in storage. {"name":"appid.js", // filename to use in storage.
// If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file // If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file
"url":"", // URL of file to load (currently relative to apps/) "url":"", // URL of file to load (currently relative to apps/)
"content":"..." // if supplied, this content is loaded directly "content":"...", // if supplied, this content is loaded directly
"evaluate":true // if supplied, data isn't quoted into a String before upload "evaluate":true, // if supplied, data isn't quoted into a String before upload
// (eg it's evaluated as JS) // (eg it's evaluated as JS)
"noOverwrite":true // if supplied, this file will not be overwritten if it
// already exists
}, },
] ]
"data": [ // list of files the app writes to "data": [ // list of files the app writes to
{"name":"appid.data.json", // filename used in storage {"name":"appid.data.json", // filename used in storage
"storageFile":true // if supplied, file is treated as storageFile "storageFile":true // if supplied, file is treated as storageFile
"url":"", // if supplied URL of file to load (currently relative to apps/)
"content":"...", // if supplied, this content is loaded directly
"evaluate":true, // if supplied, data isn't quoted into a String before upload
// (eg it's evaluated as JS)
}, },
{"wildcard":"appid.data.*" // wildcard of filenames used in storage {"wildcard":"appid.data.*" // wildcard of filenames used in storage
}, // this is mutually exclusive with using "name" }, // this is mutually exclusive with using "name"

133
apps.json
View File

@ -80,7 +80,7 @@
"name": "Notifications (default)", "name": "Notifications (default)",
"shortName":"Notifications", "shortName":"Notifications",
"icon": "notify.png", "icon": "notify.png",
"version":"0.07", "version":"0.08",
"description": "A handler for displaying notifications that displays them in a bar at the top of the screen", "description": "A handler for displaying notifications that displays them in a bar at the top of the screen",
"tags": "widget", "tags": "widget",
"type": "notify", "type": "notify",
@ -93,7 +93,7 @@
"name": "Fullscreen Notifications", "name": "Fullscreen Notifications",
"shortName":"Notifications", "shortName":"Notifications",
"icon": "notify.png", "icon": "notify.png",
"version":"0.07", "version":"0.08",
"description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.", "description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.",
"tags": "widget", "tags": "widget",
"type": "notify", "type": "notify",
@ -139,7 +139,7 @@
{ "id": "gbridge", { "id": "gbridge",
"name": "Gadgetbridge", "name": "Gadgetbridge",
"icon": "app.png", "icon": "app.png",
"version":"0.21", "version":"0.22",
"description": "The default notification handler for Gadgetbridge notifications from Android", "description": "The default notification handler for Gadgetbridge notifications from Android",
"tags": "tool,system,android,widget", "tags": "tool,system,android,widget",
"readme": "README.md", "readme": "README.md",
@ -171,7 +171,7 @@
{ "id": "setting", { "id": "setting",
"name": "Settings", "name": "Settings",
"icon": "settings.png", "icon": "settings.png",
"version":"0.23", "version":"0.24",
"description": "A menu for setting up Bangle.js", "description": "A menu for setting up Bangle.js",
"tags": "tool,system", "tags": "tool,system",
"readme": "README.md", "readme": "README.md",
@ -180,13 +180,16 @@
{"name":"setting.boot.js","url":"boot.js"}, {"name":"setting.boot.js","url":"boot.js"},
{"name":"setting.img","url":"settings-icon.js","evaluate":true} {"name":"setting.img","url":"settings-icon.js","evaluate":true}
], ],
"data": [
{"name":"setting.json", "url":"settings.min.json","evaluate":true}
],
"sortorder" : -2 "sortorder" : -2
}, },
{ "id": "alarm", { "id": "alarm",
"name": "Default Alarm", "name": "Default Alarm",
"shortName":"Alarms", "shortName":"Alarms",
"icon": "app.png", "icon": "app.png",
"version":"0.10", "version":"0.11",
"description": "Set and respond to alarms", "description": "Set and respond to alarms",
"tags": "tool,alarm,widget", "tags": "tool,alarm,widget",
"storage": [ "storage": [
@ -216,24 +219,33 @@
{ "id": "slidingtext", { "id": "slidingtext",
"name": "Sliding Clock", "name": "Sliding Clock",
"icon": "slidingtext.png", "icon": "slidingtext.png",
"version":"0.01", "version":"0.02",
"description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently only English, French and Japanese are supported", "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently only English, French and Japanese are supported",
"tags": "clock", "tags": "clock",
"type":"clock", "type":"clock",
"allow_emulator":true, "allow_emulator":true,
"readme": "README.md",
"custom":"custom.html",
"storage": [ "storage": [
{"name":"slidingtext.app.js","url":"slidingtext.js"}, {"name":"slidingtext.app.js","url":"slidingtext.js"},
{"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true} {"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true},
{"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"},
{"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"},
{"name":"slidingtext.utils.en.js","url":"slidingtext.utils.en.js"},
{"name":"slidingtext.locale.fr.js","url":"slidingtext.locale.fr.js"},
{"name":"slidingtext.locale.jp.js","url":"slidingtext.locale.jp.js"},
{"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"}
] ]
}, },
{ "id": "sweepclock", { "id": "sweepclock",
"name": "Sweep Clock", "name": "Sweep Clock",
"icon": "sweepclock.png", "icon": "sweepclock.png",
"version":"0.01", "version":"0.02",
"description": "Smooth sweep secondhand with single hour numeral. Use button1 to toggle the numeral font", "description": "Smooth sweep secondhand with single hour numeral. Use button1 to toggle the numeral font and button3 to change the colour theme",
"tags": "clock", "tags": "clock",
"type":"clock", "type":"clock",
"allow_emulator":true, "allow_emulator":true,
"readme": "README.md",
"storage": [ "storage": [
{"name":"sweepclock.app.js","url":"sweepclock.js"}, {"name":"sweepclock.app.js","url":"sweepclock.js"},
{"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true} {"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true}
@ -413,7 +425,7 @@
{ "id": "gpsrec", { "id": "gpsrec",
"name": "GPS Recorder", "name": "GPS Recorder",
"icon": "app.png", "icon": "app.png",
"version":"0.18", "version":"0.19",
"interface": "interface.html", "interface": "interface.html",
"description": "Application that allows you to record a GPS track. Can run in background", "description": "Application that allows you to record a GPS track. Can run in background",
"tags": "tool,outdoors,gps,widget", "tags": "tool,outdoors,gps,widget",
@ -438,14 +450,16 @@
"interface":"waypoints.html", "interface":"waypoints.html",
"storage": [ "storage": [
{"name":"gpsnav.app.js","url":"app.min.js"}, {"name":"gpsnav.app.js","url":"app.min.js"},
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
{"name":"gpsnav.img","url":"app-icon.js","evaluate":true} {"name":"gpsnav.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"waypoints.json","url":"waypoints.json"}
] ]
}, },
{ "id": "heart", { "id": "heart",
"name": "Heart Rate Recorder", "name": "Heart Rate Recorder",
"icon": "app.png", "icon": "app.png",
"version":"0.02", "version":"0.04",
"interface": "interface.html", "interface": "interface.html",
"description": "Application that allows you to record your heart rate. Can run in background", "description": "Application that allows you to record your heart rate. Can run in background",
"tags": "tool,health,widget", "tags": "tool,health,widget",
@ -558,7 +572,7 @@
"shortName": "Battery Warning", "shortName": "Battery Warning",
"icon": "widget.png", "icon": "widget.png",
"readme": "README.md", "readme": "README.md",
"version":"0.01", "version":"0.02",
"description": "Show a warning when the battery runs low.", "description": "Show a warning when the battery runs low.",
"tags": "tool,battery", "tags": "tool,battery",
"type":"widget", "type":"widget",
@ -738,7 +752,7 @@
{ "id": "route", { "id": "route",
"name": "Route Viewer", "name": "Route Viewer",
"icon": "app.png", "icon": "app.png",
"version":"0.01", "version":"0.02",
"description": "Upload a KML file of a route, and have your watch display a map with how far around it you are", "description": "Upload a KML file of a route, and have your watch display a map with how far around it you are",
"tags": "", "tags": "",
"custom": "custom.html", "custom": "custom.html",
@ -1072,7 +1086,7 @@
{ "id": "widpedom", { "id": "widpedom",
"name": "Pedometer widget", "name": "Pedometer widget",
"icon": "widget.png", "icon": "widget.png",
"version":"0.11", "version":"0.12",
"description": "Daily pedometer widget", "description": "Daily pedometer widget",
"tags": "widget", "tags": "widget",
"type":"widget", "type":"widget",
@ -1203,7 +1217,7 @@
{ "id": "marioclock", { "id": "marioclock",
"name": "Mario Clock", "name": "Mario Clock",
"icon": "marioclock.png", "icon": "marioclock.png",
"version":"0.14", "version":"0.15",
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.", "description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
"tags": "clock,mario,retro", "tags": "clock,mario,retro",
"type": "clock", "type": "clock",
@ -1701,9 +1715,9 @@
{ {
"id": "rclock", "id": "rclock",
"name": "Round clock with seconds, minutes and date", "name": "Round clock with seconds, minutes and date",
"shortName":"Round Clock", "shortName": "Round Clock",
"icon": "app.png", "icon": "app.png",
"version":"0.04", "version": "0.05",
"description": "Designed round clock with ticks for minutes and seconds and heart rate indication", "description": "Designed round clock with ticks for minutes and seconds and heart rate indication",
"tags": "clock", "tags": "clock",
"type": "clock", "type": "clock",
@ -1712,6 +1726,20 @@
{"name":"rclock.img","url":"app-icon.js","evaluate":true} {"name":"rclock.img","url":"app-icon.js","evaluate":true}
] ]
}, },
{
"id": "fclock",
"name": "fclock",
"shortName": "F Clock",
"icon": "app.png",
"version": "0.01",
"description": "Simple design of a digital clock",
"tags": "clock",
"type": "clock",
"storage": [
{"name":"fclock.app.js","url":"fclock.app.js"},
{"name":"fclock.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "hamloc", { "id": "hamloc",
"name": "QTH Locator / Maidenhead Locator System", "name": "QTH Locator / Maidenhead Locator System",
"shortName": "QTH Locator", "shortName": "QTH Locator",
@ -2100,7 +2128,7 @@
"name": "SleepPhaseAlarm", "name": "SleepPhaseAlarm",
"shortName":"SleepPhaseAlarm", "shortName":"SleepPhaseAlarm",
"icon": "app.png", "icon": "app.png",
"version":"0.01", "version":"0.02",
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
"tags": "alarm", "tags": "alarm",
"storage": [ "storage": [
@ -2225,7 +2253,7 @@
"name": "Apple Notification Widget", "name": "Apple Notification Widget",
"shortName":"ANCS Widget", "shortName":"ANCS Widget",
"icon": "widget.png", "icon": "widget.png",
"version":"0.06", "version":"0.07",
"description": "Displays call, message etc notifications from a paired iPhone. Read README before installation as it only works with compatible apps", "description": "Displays call, message etc notifications from a paired iPhone. Read README before installation as it only works with compatible apps",
"readme": "README.md", "readme": "README.md",
"tags": "widget", "tags": "widget",
@ -2399,9 +2427,11 @@
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"worldclock.app.js","url":"app.js"}, {"name":"worldclock.app.js","url":"app.js"},
{"name":"worldclock.settings.json"},
{"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true} {"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true}
] ],
"data": [
{"name":"worldclock.settings.json"}
]
}, },
{ "id": "digiclock", { "id": "digiclock",
"name": "Digital Clock Face", "name": "Digital Clock Face",
@ -2593,7 +2623,7 @@
"name": "Hard Alarm", "name": "Hard Alarm",
"shortName":"HardAlarm", "shortName":"HardAlarm",
"icon": "app.png", "icon": "app.png",
"version":"0.01", "version":"0.02",
"description": "Make sure you wake up! Count to the right number to turn off the alarm", "description": "Make sure you wake up! Count to the right number to turn off the alarm",
"tags": "tool,alarm,widget", "tags": "tool,alarm,widget",
"storage": [ "storage": [
@ -2644,8 +2674,10 @@
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"breath.app.js","url":"app.js"}, {"name":"breath.app.js","url":"app.js"},
{"name":"breath.settings.json","url":"settings.json"},
{"name":"breath.img","url":"app-icon.js","evaluate":true} {"name":"breath.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"breath.settings.json","url":"settings.json"}
] ]
}, },
{ "id": "lazyclock", { "id": "lazyclock",
@ -2742,9 +2774,11 @@
"storage": [ "storage": [
{"name":"gpsservice.app.js","url":"app.js"}, {"name":"gpsservice.app.js","url":"app.js"},
{"name":"gpsservice.settings.js","url":"settings.js"}, {"name":"gpsservice.settings.js","url":"settings.js"},
{"name":"gpsservice.settings.json","url":"settings.json"},
{"name":"gpsservice.wid.js","url":"widget.js"}, {"name":"gpsservice.wid.js","url":"widget.js"},
{"name":"gpsservice.img","url":"gpsservice-icon.js","evaluate":true} {"name":"gpsservice.img","url":"gpsservice-icon.js","evaluate":true}
],
"data": [
{"name":"gpsservice.settings.json","url":"settings.json"}
] ]
}, },
{ "id": "mclockplus", { "id": "mclockplus",
@ -2841,16 +2875,18 @@
"storage": [ "storage": [
{"name":"gpssetup","url":"gpssetup.js"}, {"name":"gpssetup","url":"gpssetup.js"},
{"name":"gpssetup.settings.js","url":"settings.js"}, {"name":"gpssetup.settings.js","url":"settings.js"},
{"name":"gpssetup.settings.json","url":"settings.json"},
{"name":"gpssetup.app.js","url":"app.js"}, {"name":"gpssetup.app.js","url":"app.js"},
{"name":"gpssetup.img","url":"icon.js","evaluate":true} {"name":"gpssetup.img","url":"icon.js","evaluate":true}
],
"data": [
{"name":"gpssetup.settings.json","url":"settings.json"}
] ]
}, },
{ "id": "walkersclock", { "id": "walkersclock",
"name": "Walkers Clock", "name": "Walkers Clock",
"shortName":"Walkers Clock", "shortName":"Walkers Clock",
"icon": "walkersclock48.png", "icon": "walkersclock48.png",
"version":"0.03", "version":"0.04",
"description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference", "description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference",
"type":"clock", "type":"clock",
"tags": "clock, gps, tools, outdoors", "tags": "clock, gps, tools, outdoors",
@ -2944,8 +2980,10 @@
"interface":"waypoints.html", "interface":"waypoints.html",
"storage": [ "storage": [
{"name":"waypointer.app.js","url":"app.js"}, {"name":"waypointer.app.js","url":"app.js"},
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
{"name":"waypointer.img","url":"icon.js","evaluate":true} {"name":"waypointer.img","url":"icon.js","evaluate":true}
],
"data": [
{"name":"waypoints.json","url":"waypoints.json"}
] ]
}, },
{ "id": "color_catalog", { "id": "color_catalog",
@ -3004,7 +3042,7 @@
"name": "Gadgetbridge Music Controls", "name": "Gadgetbridge Music Controls",
"shortName":"Music Controls", "shortName":"Music Controls",
"icon": "icon.png", "icon": "icon.png",
"version":"0.01", "version":"0.02",
"description": "Control the music on your Gadgetbridge-connected phone", "description": "Control the music on your Gadgetbridge-connected phone",
"tags": "tools,bluetooth,gadgetbridge,music", "tags": "tools,bluetooth,gadgetbridge,music",
"type":"app", "type":"app",
@ -3045,7 +3083,7 @@
{ "id": "kitchen", { "id": "kitchen",
"name": "Kitchen Combo", "name": "Kitchen Combo",
"icon": "kitchen.png", "icon": "kitchen.png",
"version":"0.02", "version":"0.03",
"description": "Combination of the stepo, walkersclock, arrow and waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later", "description": "Combination of the stepo, walkersclock, arrow and waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later",
"tags": "tool,outdoors,gps", "tags": "tool,outdoors,gps",
"readme": "README.md", "readme": "README.md",
@ -3059,5 +3097,38 @@
{"name":"waypoints.json","url":"waypoints.json","evaluate":false}, {"name":"waypoints.json","url":"waypoints.json","evaluate":false},
{"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true}
] ]
} },
{ "id": "qmsched",
"name": "Quiet Mode Schedule",
"shortName":"Quiet Mode",
"icon": "app.png",
"version":"0.01",
"description": "Automatically turn Quiet Mode on or off at set times",
"readme": "README.md",
"tags": "tool",
"storage": [
{"name":"qmsched","url":"lib.js"},
{"name":"qmsched.app.js","url":"app.js"},
{"name":"qmsched.boot.js","url":"boot.js"},
{"name":"qmsched.img","url":"icon.js","evaluate":true}
],
"data": [
{"name":"qmsched.json"}
]
},
{
"id": "hourstrike",
"name": "Hour Strike",
"shortName": "Hour Strike",
"icon": "app-icon.png",
"version": "0.07",
"description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
"tags": "tool,alarm",
"readme": "README.md",
"storage": [
{"name":"hourstrike.app.js","url":"app.js"},
{"name":"hourstrike.boot.js","url":"boot.js"},
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true}
]
}
] ]

View File

@ -8,3 +8,4 @@
0.08: Make alarm scheduling more reliable 0.08: Make alarm scheduling more reliable
0.09: Add per alarm auto-snooze option 0.09: Add per alarm auto-snooze option
0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506) 0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506)
0.11: Respect Quiet Mode

View File

@ -38,6 +38,7 @@ function showAlarm(alarm) {
load(); load();
}); });
function buzz() { function buzz() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
Bangle.buzz(100).then(()=>{ Bangle.buzz(100).then(()=>{
setTimeout(()=>{ setTimeout(()=>{
Bangle.buzz(100).then(function() { Bangle.buzz(100).then(function() {

1
apps/fclock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: First published version of app

1
apps/fclock/app-icon.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/fclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

206
apps/fclock/fclock.app.js Normal file
View File

@ -0,0 +1,206 @@
{
var minutes;
var seconds;
var hours;
var date;
var first = true;
var locale = require('locale');
var _12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"] || false;
//HR variables
var id = 0;
var grow = true;
var size=10;
//Screen dimensions
const screen = {
width: g.getWidth(),
height: g.getWidth(),
middle: g.getWidth() / 2,
center: g.getHeight() / 2,
};
// Ssettings
const settings = {
time: {
color: '#dddddd',
font: 'Vector',
size: 100,
middle: screen.middle,
center: screen.center,
},
date: {
color: '#dddddd',
font: 'Vector',
size: 15,
middle: screen.height-17, // at bottom of screen
center: screen.center,
},
circle: {
colormin: '#ffffff',
colorsec: '#ffffff',
width: 10,
middle: screen.middle,
center: screen.center,
height: screen.height
},
hr: {
color: '#333333',
size: 20,
x: screen.center,
y: screen.middle + 65
}
};
const dateStr = function (date) {
return locale.date(new Date(), 1);
};
const getFormated = function(val) {
if (val<10) {
val='0'+val;
}
return val;
};
const drawMin = function (sections, color) {
g.setFontAlign(0, 0, 0);
g.setColor('#000000');
g.setFont(settings.time.font, settings.time.size/2);
g.drawString(getFormated(sections-1), settings.time.center+50, settings.time.middle);
g.setColor(settings.time.color);
g.setFont(settings.time.font, settings.time.size/2);
g.drawString(getFormated(sections), settings.time.center+50, settings.time.middle);
};
const drawSec = function (sections, color) {
g.setFontAlign(0, 0, 0);
g.setColor('#000000');
g.setFont(settings.time.font, settings.time.size/4);
g.drawString(getFormated(sections-1), settings.time.center+100, settings.time.middle);
g.setColor(settings.time.color);
g.setFont(settings.time.font, settings.time.size/4);
g.drawString(getFormated(sections), settings.time.center+100, settings.time.middle);
};
const drawClock = function () {
currentTime = new Date();
//Get date as a string
date = dateStr(currentTime);
if(seconds==59) {
g.clear();
}
// Update minutes when needed
if (minutes != currentTime.getMinutes()) {
minutes = currentTime.getMinutes();
drawMin(minutes, settings.circle.colormin);
}
//Update seconds when needed
if (seconds != currentTime.getSeconds()) {
seconds = currentTime.getSeconds();
drawSec(seconds, settings.circle.colorsec);
}
//Write the time as configured in the settings
hours = currentTime.getHours();
if (_12hour && hours > 13) {
hours = hours - 12;
}
var meridian;
if (typeof locale.meridian === "function") {
meridian = locale.meridian(new Date());
} else {
meridian = "";
}
var timestr;
if (meridian.length > 0 && _12hour) {
timestr = hours + " " + meridian;
} else {
timestr = hours;
}
g.setFontAlign(0, 0, 0);
g.setColor(settings.time.color);
g.setFont(settings.time.font, settings.time.size);
g.drawString(timestr, settings.time.center-40, settings.time.middle);
//Write the date as configured in the settings
g.setColor(settings.date.color);
g.setFont(settings.date.font, settings.date.size);
g.drawString(date, settings.date.center, settings.date.middle);
};
//setInterval for HR visualisation
const newBeats = function (hr) {
if (id != 0) {
changeInterval(id, 6e3 / hr.bpm);
} else {
id = setInterval(drawHR, 6e3 / hr.bpm);
}
};
//visualize HR with circles pulsating
const drawHR = function () {
if (grow && size < settings.hr.size) {
size++;
}
if (!grow && size > 3) {
size--;
}
if (size == settings.hr.size || size == 3) {
grow = !grow;
}
if (grow) {
color = settings.hr.color;
g.setColor(color);
g.fillCircle(settings.hr.x, settings.hr.y, size);
} else {
color = "#000000";
g.setColor(color);
g.drawCircle(settings.hr.x, settings.hr.y, size);
}
};
// clean app screen
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
//manage when things should be enabled and not
Bangle.on('lcdPower', function (on) {
if (on) {
Bangle.setHRMPower(1);
} else {
Bangle.setHRMPower(0);
}
});
// refesh every second
setInterval(drawClock, 1E3);
//start HR monitor and update frequency of update
Bangle.setHRMPower(1);
Bangle.on('HRM', function (d) {
newBeats(d);
});
// draw now
drawClock();
// Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
}

View File

@ -1 +1,2 @@
0.01: Initial version 0.01: Initial version
0.02: Increase text brightness, improve controls, (try to) reduce memory usage

View File

@ -23,9 +23,13 @@ You can change this under `Settings`->`App/Widget Settings`->`Music Controls`.
## Controls ## Controls
### Buttons ### Buttons
* Button 1: Volume up (hold to repeat) * Button 1: Volume up
* Button 2: Toggle play/pause, long-press for menu * Button 2:
* Button 3: Volume down (hold to repeat, but remember that holding for too long resets your watch) - Single press: toggle play/pause
- Double press: next song
- Triple press: previous song
- Long-press: open application launcher
* Button 3: Volume down
### Touch ### Touch
* Left: pause/previous song * Left: pause/previous song

File diff suppressed because it is too large Load Diff

View File

@ -21,3 +21,4 @@
0.19: Support for call incoming/start/end 0.19: Support for call incoming/start/end
0.20: Reduce memory usage 0.20: Reduce memory usage
0.21: Fix HRM setting 0.21: Fix HRM setting
0.22: Respect Quiet Mode

View File

@ -154,7 +154,9 @@
case "notify-": case "notify-":
if (event.t === "notify") { if (event.t === "notify") {
require("notify").show(prettifyNotificationEvent(event)); require("notify").show(prettifyNotificationEvent(event));
Bangle.buzz(); if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.buzz();
}
} else { // notify- } else { // notify-
require("notify").hide(event); require("notify").hide(event);
} }
@ -174,7 +176,9 @@
body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))} body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))}
if (event.cmd === "incoming") { if (event.cmd === "incoming") {
require("notify").show(note); require("notify").show(note);
Bangle.buzz(); if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.buzz();
}
} else if (event.cmd === "start") { } else if (event.cmd === "start") {
require("notify").show(Object.assign(note, { require("notify").show(Object.assign(note, {
bgColor : "#008000", titleBgColor : "#00C000", bgColor : "#008000", titleBgColor : "#00C000",
@ -194,6 +198,7 @@
delete state.find; delete state.find;
} }
if (event.n) if (event.n)
// Ignore quiet mode: we always want to find our watch
state.find = setInterval(_=>{ state.find = setInterval(_=>{
Bangle.buzz(); Bangle.buzz();
setTimeout(_=>Bangle.beep(), 1000); setTimeout(_=>Bangle.beep(), 1000);

View File

@ -20,3 +20,4 @@
0.16: Add gpsrec app to Settings menu 0.16: Add gpsrec app to Settings menu
0.17: Disable recording if storage is full (fix #574) 0.17: Disable recording if storage is full (fix #574)
0.18: Period counter now uses GPS time rather than counting packets (allows use with GPS Setup) 0.18: Period counter now uses GPS time rather than counting packets (allows use with GPS Setup)
0.19: Fix memory usage issues inside track viewer app

View File

@ -51,7 +51,7 @@ function showMainMenu() {
updateSettings(); updateSettings();
} }
}, },
'View Tracks': viewTracks, 'View Tracks': ()=>{viewTracks();},
'< Back': ()=>{load();} '< Back': ()=>{load();}
}; };
return E.showMenu(mainmenu); return E.showMenu(mainmenu);
@ -65,13 +65,13 @@ function viewTracks() {
for (var n=0;n<36;n++) { for (var n=0;n<36;n++) {
var f = require("Storage").open(getFN(n),"r"); var f = require("Storage").open(getFN(n),"r");
if (f.readLine()!==undefined) { if (f.readLine()!==undefined) {
menu["Track "+n] = viewTrack.bind(null,n,false); menu["Track "+n] = (n=>viewTrack(n)).bind(null,n,false);
found = true; found = true;
} }
} }
if (!found) if (!found)
menu["No Tracks found"] = function(){}; menu["No Tracks found"] = function(){};
menu['< Back'] = showMainMenu; menu['< Back'] = () => { showMainMenu(); };
return E.showMenu(menu); return E.showMenu(menu);
} }
@ -161,7 +161,7 @@ function viewTrack(n, info) {
viewTrack(n, info); viewTrack(n, info);
}); });
}; };
menu['< Back'] = viewTracks; menu['< Back'] = () => { viewTracks(); };
return E.showMenu(menu); return E.showMenu(menu);
} }

View File

@ -1 +1,2 @@
0.01: Add a number to match to turn off alarm 0.01: Add a number to match to turn off alarm
0.02: Respect Quiet Mode

View File

@ -62,6 +62,7 @@ function showPrompt(msg, buzzCount, alarm) {
} }
function showAlarm(alarm) { function showAlarm(alarm) {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
var msg = formatTime(alarm.hr); var msg = formatTime(alarm.hr);
var buzzCount = 20; var buzzCount = 20;
if (alarm.msg) if (alarm.msg)

View File

@ -1,3 +1,5 @@
0.01: New App! 0.01: New App!
0.02: Don't overwrite existing settings on app update 0.02: Don't overwrite existing settings on app update
Clean up recordings on app removal Clean up recordings on app removal
0.03: added graphing feature of 164 latest measurements
0.04: Fix memory usage when viewing HRM traces

View File

@ -1,8 +1,25 @@
const GraphXZero = 40;
const GraphYZero = 200;
const GraphY100 = 80;
const GraphMarkerOffset = 5;
const MaxValueCount = 164;
const GraphXMax = GraphXZero + MaxValueCount;
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
var settings = require("Storage").readJSON("heart.json",1)||{}; var settings = require("Storage").readJSON("heart.json",1)||{};
var globalSettings = require('Storage').readJSON('setting.json', true) || {timezone: 0};
require('DateExt').locale({
str: "0D.0M. 0h:0m",
offset: [
globalSettings.timezone * 60,
globalSettings.timezone * 60
]
});
function getFileNbr(n) { function getFileNbr(n) {
return ".heart"+n.toString(36); return ".heart"+n.toString(36);
} }
@ -35,7 +52,8 @@ function showMainMenu() {
updateSettings(); updateSettings();
} }
}, },
'View Records': viewRecords, 'View Records': ()=>{viewRecords()},
'Graph Records': ()=>{graphRecords()},
'< Back': ()=>{load();} '< Back': ()=>{load();}
}; };
return E.showMenu(mainMenu); return E.showMenu(mainMenu);
@ -55,7 +73,7 @@ function viewRecords() {
} }
if (!found) if (!found)
menu["No Records Found"] = function(){}; menu["No Records Found"] = function(){};
menu['< Back'] = showMainMenu; menu['< Back'] = ()=>{showMainMenu()};
return E.showMenu(menu); return E.showMenu(menu);
} }
@ -92,9 +110,188 @@ function viewRecord(n) {
viewRecord(n); viewRecord(n);
}); });
}; };
menu['< Back'] = viewRecords; menu['< Back'] = ()=>{viewRecords()};
print(menu); print(menu);
return E.showMenu(menu); return E.showMenu(menu);
} }
function graphRecords() {
const menu = {
'': { 'title': 'Heart Records' }
};
var found = false;
for (var n=0;n<36;n++) {
var f = require("Storage").open(getFileNbr(n),"r");
var line = f.readLine();
if (line!==undefined) {
menu["#"+n+" "+Date(line.split(",")[0]*1000).as().str] = graphRecord.bind(null,n);
found = true;
}
}
if (!found)
menu["No Records Found"] = function(){};
menu['< Back'] = ()=>{showMainMenu()};
return E.showMenu(menu);
}
// based on batchart
function renderHomeIcon() {
//Home for Btn2
g.setColor(1, 1, 1);
g.drawLine(220, 118, 227, 110);
g.drawLine(227, 110, 234, 118);
g.drawPoly([222,117,222,125,232,125,232,117], false);
g.drawRect(226,120,229,125);
}
function renderChart() {
// Left Y axis (Battery)
g.setColor(1, 1, 0);
g.drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100);
g.setFontAlign(1, -1, 0);
g.drawString("150", 35, GraphY100 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100);
g.drawString("125", 35, GraphYZero - 110 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("100", 35, GraphYZero - 100 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("90", 35, GraphYZero - 90 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("80", 35, GraphYZero - 70 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("70", 35, GraphYZero - 50 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("60", 35, GraphYZero - 30 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("50", 35, GraphYZero - 20 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("40", 35, GraphYZero - 10 - GraphMarkerOffset);
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
g.drawString("30", 35, GraphYZero - GraphMarkerOffset);
g.setColor(1, 1, 1);
g.drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero);
console.log("Finished drawing chart");
}
// as drawing starts at 30 HRM decreasing measrure by 30
// recalculate for range 110-150 as only 20 pixels are available
function getY(measure) {
positionY = GraphYZero - measure + 30;
if (100 < measure < 150) {
positionY = GraphYZero - ( 100 + Math.round((measure - 100)/2) ) + 30;
g.setColor(1, 0, 0);
} else if (60 < measrure < 100) {
positionY = GraphYZero - ( 30 + Math.round((measure - 30)/2) ) + 30;
g.setColor(0, 1, 0);
}
if (positionY > GraphYZero) {
positionY = GraphYZero;
g.setColor(1, 0, 0);
}
if (positionY < GraphY100) {
positionY = GraphY100;
g.setColor(1, 0, 0);
}
return positionY;
}
function stop() {
E.showMenu();
load();
}
function graphRecord(n) {
E.showMenu({'': 'Heart Record '+n});
E.showMessage(
"Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.",
'Heart Record '+n
);
g.setFont("Vector", 10);
var lastPixel;
var lineCount = 0;
var positionX = GraphXZero;
var positionY = GraphYZero;
var startLine = 1;
var tempCount = 0;
var f = require("Storage").open(getFileNbr(n),"r");
var line = f.readLine();
var times = Array(2);
console.log("Counting lines");
while (line !== undefined) {
lineCount++;
line = f.readLine();
}
console.log(`Line count: ${lineCount}`);
if (lineCount > MaxValueCount) {
startLine = lineCount - MaxValueCount;
}
console.log(`start: ${startLine}`);
f = require("Storage").open(getFileNbr(n),"r");
line = f.readLine();
while (line !== undefined) {
currentLine = line;
line = f.readLine();
tempCount++;
if (tempCount == startLine) {
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
renderHomeIcon();
renderChart();
} else if (tempCount > startLine) {
positionX++;
if (parseInt(currentLine.split(",")[2]) >= 70) {
g.setColor(1, 1, 1);
oldPositionY = positionY;
positionY = getY(parseInt(currentLine.split(",")[1]));
if (times[0] === undefined) {
times[0] = parseInt(currentLine.split(",")[0]);
}
if (tempCount == startLine + 1) {
g.setPixel(positionX, positionY);
} else {
g.drawLine(positionX - 1, oldPositionY, positionX, positionY);
times[1] = parseInt(currentLine.split(",")[0]);
}
}
}
g.flip();
}
g.setColor(1, 1, 0);
g.setFont("Vector", 10);
console.log('start: ' + times[0]);
console.log('end: ' + times[1]);
if (times[0] !== undefined) {
g.setFontAlign(-1, -1, 0);
var startdate = new Date(times[0]*1000);
g.drawString(startdate.local().as("0h:0m").str, 15, GraphYZero + 12);
}
if (times[1] !== undefined) {
g.setFontAlign(1, -1, 0);
var enddate = new Date(times[1]*1000);
g.drawString(enddate.local().as().str, GraphXMax, GraphYZero + 12);
}
console.log("Finished rendering data");
Bangle.buzz(200, 0.3);
setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false});
}
showMainMenu(); showMainMenu();
// vim: et ts=2 sw=2

View File

@ -0,0 +1,7 @@
0.01: New App
0.02: Add different strike intervals and support for quiet time
0.03: Bug fixes for setting attributes
0.04: Add more time to strike and the strength
0.05: Add display for the next strike time
0.06: Move the next strike time to the first row of display
0.07: Change the boot function to avoid reloading the entire watch

25
apps/hourstrike/README.md Normal file
View File

@ -0,0 +1,25 @@
# Hour Strike
![icon](app-icon.png)
Time passes too fast!
This app configures your `Bangle.js` so that it buzzes on the hour or on the half hour.
This app is slightly different from [Hour Chime](https://github.com/espruino/BangleApps/tree/master/apps/widchime). `Hour Chimee` runs as a widget but `Hour Strike` runs as a background task, without showing a widget.
## Features
- Strike the hour, the half hour, the quarter hour, and more
- Set up a range of hours that clock will strike
- Set up the strength of the strike
- Preview when the next strike will happen
## Known Issues
- This app does not know or check whether your clock already chimes on the hour.
## Creator
[Weiming Hu](https://weiming-hu.github.io/), using coding from the [Default Alarm](https://github.com/espruino/BangleApps/tree/master/apps/alarm).

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkGswAHogAEBxAAHsgXFowXPCwowQFwwwQCIUjn/zmQwPFwUj/4ACDAQwMBwNDCgPwh4DBmgwMFwU/C4vzGBgMBoRECC4f/kgwLIwgXFJBgLBl4XH+QXNLwQXFMAQXPmEDC6K8DiEBAoYXSgA1DI6MxgETL6gYBgIGBC5ynDCYMQAwKnOa4YABmTXQoQXEAAUkC5dkMAx2EowXJJBBGNAAMUBwMjMAgHBoIXLiIPBDAM/+YWCokRC5gwCAAtBC5owDAAgJBC5dBiAwGoMBigXLokQgIXFA4QXMoEAAANNqAECggXR7oXXAYQXSgvUC6sEC60N6oXW6AXUinu8IXTgPuAAMQC6UEC4SsDC58OC4XgC9RHXO66nCoLXVAAoXmABQX1A"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

48
apps/hourstrike/app.js Normal file
View File

@ -0,0 +1,48 @@
const storage = require('Storage');
let settings;
function updateSettings() {
storage.write('hourstrike.json', settings);
}
function resetSettings() {
settings = {
interval: 3600,
start: 9,
end: 21,
vlevel: 0.5,
next_hour: -1,
next_minute: -1,
};
updateSettings();
}
settings = storage.readJSON('hourstrike.json', 1);
if (!settings) resetSettings();
function showMainMenu() {
var mode_txt = ['Off','1 min','5 min','10 min','1/4 h','1/2 h','1 h'];
var mode_interval = [-1,60,300,600,900,1800,3600];
const mainmenu = {'': { 'title': 'Hour Strike' }};
mainmenu['Next strike '+settings.next_hour+':'+settings.next_minute] = function(){};
mainmenu['Notify every'] = {
value: mode_interval.indexOf(settings.interval),
min: 0, max: 6, format: v => mode_txt[v],
onchange: v => {
settings.interval = mode_interval[v];
if (v===0) {settings.next_hour = -1; settings.next_minute = -1;}
updateSettings();}};
mainmenu.Start = {
value: settings.start, min: 0, max: 23, format: v=>v+':00',
onchange: v=> {settings.start = v; updateSettings();}};
mainmenu.End = {
value: settings.end, min: 0, max: 23, format: v=>v+':59',
onchange: v=> {settings.end = v; updateSettings();}};
mainmenu.Strength = {
value: settings.vlevel*10, min: 1, max: 10, format: v=>v/10,
onchange: v=> {settings.vlevel = v/10; updateSettings();}};
mainmenu['< Back'] = ()=>load();
return E.showMenu(mainmenu);
}
showMainMenu();

39
apps/hourstrike/boot.js Normal file
View File

@ -0,0 +1,39 @@
(function() {
function setup () {
var settings = require('Storage').readJSON('hourstrike.json',1)||[];
var t = new Date();
var t_min_sec = t.getMinutes()*60+t.getSeconds();
var wait_msec = settings.interval>0?(settings.interval-t_min_sec%settings.interval)*1000:-1;
if (wait_msec>0) {
t.setMilliseconds(t.getMilliseconds()+wait_msec);
var t_hour = t.getHours();
if (t_hour<settings.start||t_hour>settings.end) {
var strike = new Date(t.getTime());
strike.setHours(settings.start);
strike.setMinutes(0);
if (t_hour>settings.end) {
strike.setDate(strike.getDate()+1);
}
wait_msec += strike-t;
settings.next_hour = strike.getHours();
settings.next_minute = strike.getMinutes();
} else {
settings.next_hour = t_hour;
settings.next_minute = t.getMinutes();
}
setTimeout(strike_func, wait_msec);
} else {
settings.next_hour = -1;
settings.next_minute = -1;
}
require('Storage').write('hourstrike.json', settings);
}
function strike_func () {
var setting = require('Storage').readJSON('hourstrike.json',1)||[];
Bangle.buzz(200, setting.vlevel||0.5)
.then(() => new Promise(resolve => setTimeout(resolve,200)))
.then(() => Bangle.buzz(200, setting.vlevel||0.5));
setup();
}
setup();
})();

View File

@ -1,2 +1,3 @@
0.01: First version 0.01: First version
0.02: compass disable BTN1,BTN2 while waiting for GPS to reach RUNNING status 0.02: compass disable BTN1,BTN2 while waiting for GPS to reach RUNNING status
0.03: Don't buzz for GPS fix in Quiet Mode

View File

@ -246,7 +246,9 @@ GPS.prototype.processFix = function(fix) {
if (fix.fix) { if (fix.fix) {
//this.log_debug("Got fix - setting state to GPS_RUNNING"); //this.log_debug("Got fix - setting state to GPS_RUNNING");
this.gpsState = this.GPS_RUNNING; this.gpsState = this.GPS_RUNNING;
if (!this.last_fix.fix) Bangle.buzz(); // buzz on first position if (!this.last_fix.fix && !(require("Storage").readJSON("setting.json", 1) || {}).quiet) {
Bangle.buzz(); // buzz on first position
}
this.last_fix = fix; this.last_fix = fix;
} }
}; };

View File

@ -12,3 +12,4 @@
0.12: Add info banner message when phone (dis)connects. Display low-battery warning (<=10%) 0.12: Add info banner message when phone (dis)connects. Display low-battery warning (<=10%)
0.13: Fix drawPyramid function so pyramids are drawn in correct Y position 0.13: Fix drawPyramid function so pyramids are drawn in correct Y position
0.14: Add jumping frame for characters 0.14: Add jumping frame for characters
0.15: Disable notification buzz during Quiet Mode

File diff suppressed because it is too large Load Diff

View File

@ -4,3 +4,4 @@
0.05: Adjust position of notification src text 0.05: Adjust position of notification src text
0.06: Support background color 0.06: Support background color
0.07: Auto-calculate height, and pad text down even when there's no title (so it stays on-screen) 0.07: Auto-calculate height, and pad text down even when there's no title (so it stays on-screen)
0.08: Don't turn on screen during Quiet Mode

View File

@ -9,7 +9,7 @@ other applications or widgets to display messages.
```JS ```JS
options = { options = {
on : bool, // turn screen on, default true on : bool, // turn screen on, default true (But not if Quiet Mode is enabled)
size : int, // height of notification, default is fit to height (80 max) size : int, // height of notification, default is fit to height (80 max)
title : string, // optional title title : string, // optional title
id // optional notification ID, used with hide() id // optional notification ID, used with hide()

View File

@ -127,7 +127,9 @@ exports.show = function(options) {
options.render({x:x, y:y, w:w, h:h}); options.render({x:x, y:y, w:w, h:h});
} }
if (options.on) Bangle.setLCDPower(1); // light up if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.setLCDPower(1); // light up
}
Bangle.setLCDMode(oldMode); // clears cliprect Bangle.setLCDMode(oldMode); // clears cliprect
function anim() { function anim() {

View File

@ -5,3 +5,4 @@
0.05: Fix `g` corruption issue if .hide gets called twice 0.05: Fix `g` corruption issue if .hide gets called twice
0.06: Adjust position of notification src text and notifications without title 0.06: Adjust position of notification src text and notifications without title
0.07: Support background color 0.07: Support background color
0.08: Don't turn on screen during Quiet Mode

View File

@ -90,8 +90,9 @@ exports.show = function(options) {
const area={x:x, y:y, w:w, h:h} const area={x:x, y:y, w:w, h:h}
options.render(area); options.render(area);
} }
if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
if (options.on) Bangle.setLCDPower(1); // light up Bangle.setLCDPower(1); // light up
}
Bangle.on("touch", exports.hide); Bangle.on("touch", exports.hide);
// Create a fake graphics to hide draw attempts // Create a fake graphics to hide draw attempts
oldg = g; oldg = g;
@ -115,9 +116,11 @@ exports.hide = function(options) {
Bangle.removeListener("touch", exports.hide); Bangle.removeListener("touch", exports.hide);
g.clear(); g.clear();
Bangle.drawWidgets(); Bangle.drawWidgets();
// flipping the screen off then on often triggers a redraw - it may not! if (Bangle.isLCDOn() || !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.setLCDPower(0); // flipping the screen off then on often triggers a redraw - it may not!
Bangle.setLCDPower(1); Bangle.setLCDPower(0);
Bangle.setLCDPower(1);
}
// hack for E.showMenu/showAlert/showPrompt - can force a redraw by faking next/back // hack for E.showMenu/showAlert/showPrompt - can force a redraw by faking next/back
if (Bangle.btnWatches) { if (Bangle.btnWatches) {
global["\xff"].watches[Bangle.btnWatches[0]].callback(); global["\xff"].watches[Bangle.btnWatches[0]].callback();

1
apps/qmsched/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: First version

5
apps/qmsched/README.md Normal file
View File

@ -0,0 +1,5 @@
# Quiet Mode Schedule
Automatically turn Quiet Mode on or off at set times.
![Main menu](screenshot_main.png) ![Edit Schedule menu](screenshot_edit.png)

133
apps/qmsched/app.js Normal file
View File

@ -0,0 +1,133 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
const modeNames = ["Off", "Alarms", "Silent"];
let scheds = require("Storage").readJSON("qmsched.json", 1);
/*scheds = [
{ hr : 6.5, // hours + minutes/60
last : 0, // last day of the month we fired on - so we don't switch twice in one day!
mode : 1, // quiet mode (0/1/2)
}
];*/
if (!scheds) {
// set default schedule on first load of app
scheds = [
{"hr": 8, "mode": 0, "last": 25},
{"hr": 22, "mode": 1, "last": 25},
];
require("Storage").writeJSON("qmsched.json", scheds);
}
function formatTime(t) {
const hrs = 0|t;
const mins = Math.round((t-hrs)*60);
return (" "+hrs).substr(-2)+":"+("0"+mins).substr(-2);
}
function getCurrentHr() {
const time = new Date();
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function showMainMenu() {
const menu = {
"": {"title": "Quiet Mode"},
"Current Mode": {
value: (require("Storage").readJSON("setting.json", 1) || {}).quiet|0,
format: v => modeNames[v],
onchange: function(v) {
if (v<0) v = 2;
if (v>2) v = 0;
require("qmsched").setMode(v);
this.value = v;
},
},
};
scheds.sort((a, b) => (a.hr-b.hr));
scheds.forEach((sched, idx) => {
const name = modeNames[sched.mode];
const txt = formatTime(sched.hr)+" ".repeat(14-name.length)+name;
menu[txt] = function() {
showEditMenu(idx);
};
});
menu["Add Schedule"] = () => showEditMenu(-1);
menu["< Back"] = () => {load();};
return E.showMenu(menu);
}
function showEditMenu(index) {
const isNew = index<0;
let hrs = 12, mins = 0;
let mode = 1;
if (!isNew) {
const s = scheds[index];
hrs = 0|s.hr;
mins = Math.round((s.hr-hrs)*60);
mode = s.mode;
}
const menu = {
"": {"title": (isNew ? "Add" : "Edit")+" Schedule"},
"Hours": {
value: hrs,
onchange: function(v) {
if (v<0) v = 23;
if (v>23) v = 0;
hrs = v;
this.value = v;
}, // no arrow fn -> preserve 'this'
},
"Minutes": {
value: mins,
onchange: function(v) {
if (v<0) v = 59;
if (v>59) v = 0;
mins = v;
this.value = v;
}, // no arrow fn -> preserve 'this'
},
"Switch to": {
value: mode,
format: v => modeNames[v],
onchange: function(v) {
if (v<0) v = 2;
if (v>2) v = 0;
mode = v;
this.value = v;
}, // no arrow fn -> preserve 'this'
},
};
function getSched() {
const hr = hrs+(mins/60);
let day = 0;
// If schedule is for tomorrow not today (eg, in the past), set day
if (hr<getCurrentHr()) {
day = (new Date()).getDate();
}
return {
hr: hr,
mode: mode,
last: day,
};
}
menu["> Save"] = function() {
if (isNew) {
scheds.push(getSched());
} else {
scheds[index] = getSched();
}
require("Storage").writeJSON("qmsched.json", scheds);
showMainMenu();
};
if (!isNew) {
menu["> Delete"] = function() {
scheds.splice(index, 1);
require("Storage").writeJSON("qmsched.json", scheds);
showMainMenu();
};
}
menu["< Cancel"] = showMainMenu;
return E.showMenu(menu);
}
showMainMenu();

BIN
apps/qmsched/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

24
apps/qmsched/boot.js Normal file
View File

@ -0,0 +1,24 @@
// apply Quiet Mode schedules
(function qm() {
let scheds = require("Storage").readJSON("qmsched.json", 1) || [];
if (!scheds.length) return;
let next,idx;
scheds.forEach(function(s, i) {
if (!next || (s.hr+s.last*24)<(next.hr+next.last*24)) {
next = s;
idx = i;
}
});
const now = new Date(),
hr = now.getHours()+(now.getMinutes()/60)+(now.getSeconds()/3600);
let t = 3600000*(next.hr-hr);
if (next.last===now.getDate()) t += 86400000;
/* update quiet mode at the correct time. */
setTimeout(function() {
let scheds = require("Storage").readJSON("qmsched.json", 1) || [];
require("qmsched").setMode(scheds[idx].mode);
scheds[idx].last = (new Date()).getDate();
require("Storage").writeJSON("qmsched.json", scheds);
qm(); // schedule next update
}, t);
})();

2
apps/qmsched/icon.js Normal file
View File

@ -0,0 +1,2 @@
// https://icons8.com/icon/19324/no-reminders
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AElksF1wwtF4YwO0WiGFguBGFovfGB3MAAgwnFooxfGBAuJGEguLGEV/F5owh0YvpGH4vhGCQvd0YwQF7vMGCAveGCAvfGB4vgGBwvhGBouhGFLkIGEouIGEwvKGBguiGEQuNGEHN5owa5ouQ53P5/O5wyOGA3NDAIbBLyAUCAAQzCNBQwF0gVDXiQoBGQgAEEIILE0iSJdiozCFQw1FGBJgSABSVIeg7wQGSDDMFyQ0VCQQwdAAWcAAwPHGD4vPGD+iAAwRJGEgRLGEQRNeTwARF1wA/AH4AX"))

17
apps/qmsched/lib.js Normal file
View File

@ -0,0 +1,17 @@
/**
* Set new Quiet Mode and apply Bangle options
* @param {int} mode Quiet Mode
*/
exports.setMode = function(mode) {
let s = require("Storage").readJSON("setting.json", 1) || {};
s.quiet = mode;
require("Storage").writeJSON("setting.json", s);
if (s.options) Bangle.setOptions(s.options);
if (mode && s.qmOptions) Bangle.setOptions(s.qmOptions);
if (mode && s.qmBrightness) {
if (s.qmBrightness!=1) Bangle.setLCDBrightness(s.qmBrightness);
} else {
if (s.brightness && s.brightness!=1) Bangle.setLCDBrightness(s.brightness);
}
if (mode && s.qmTimeout) Bangle.setLCDTimeout(s.qmTimeout);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,4 +1,5 @@
0.01: First published version of app 0.01: First published version of app
0.02: Added support for locale and 12H clock 0.02: Added support for locale and 12H clock
0.03: Added HR indication to clock 0.03: Added HR indication to clock
0.04: Update font size and alignment 0.04: Update font size and alignment
0.05: Changes which circle show minutes and seconds

View File

@ -23,29 +23,29 @@
// Ssettings // Ssettings
const settings = { const settings = {
time: { time: {
color: 0xD6ED17, color: '#D6ED17',
font: 'Vector', font: 'Vector',
size: 60, size: 60,
middle: screen.middle, middle: screen.middle,
center: screen.center, center: screen.center,
}, },
date: { date: {
color: 0xD6ED17, color: '#D6ED17',
font: 'Vector', font: 'Vector',
size: 15, size: 15,
middle: screen.height-17, // at bottom of screen middle: screen.height-17, // at bottom of screen
center: screen.center, center: screen.center,
}, },
circle: { circle: {
colormin: 0x606060, colormin: '#ffffff',
colorsec: 0x656565, colorsec: '#ffffff',
width: 10, width: 10,
middle: screen.middle, middle: screen.middle,
center: screen.center, center: screen.center,
height: screen.height height: screen.height
}, },
hr: { hr: {
color: 0x333333, color: '#333333',
size: 10, size: 10,
x: screen.center, x: screen.center,
y: screen.middle + 45 y: screen.middle + 45
@ -66,18 +66,6 @@
}; };
const drawMinArc = function (sections, color) { const drawMinArc = function (sections, color) {
g.setColor(color);
rad = (settings.circle.height / 2) - 20;
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
//g.setPixel(r[0],r[1]);
r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90);
//g.setPixel(r[0],r[1]);
g.drawLine(r1[0], r1[1], r2[0], r2[1]);
g.setColor('#333333');
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4)
};
const drawSecArc = function (sections, color) {
g.setColor(color); g.setColor(color);
rad = (settings.circle.height / 2) - 40; rad = (settings.circle.height / 2) - 40;
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90); r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
@ -86,7 +74,19 @@
//g.setPixel(r[0],r[1]); //g.setPixel(r[0],r[1]);
g.drawLine(r1[0], r1[1], r2[0], r2[1]); g.drawLine(r1[0], r1[1], r2[0], r2[1]);
g.setColor('#333333'); g.setColor('#333333');
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4) g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4);
};
const drawSecArc = function (sections, color) {
g.setColor(color);
rad = (settings.circle.height / 2) - 20;
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
//g.setPixel(r[0],r[1]);
r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90);
//g.setPixel(r[0],r[1]);
g.drawLine(r1[0], r1[1], r2[0], r2[1]);
g.setColor('#333333');
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4);
}; };
const drawClock = function () { const drawClock = function () {
@ -107,15 +107,13 @@
first = false; first = false;
} }
// Reset seconds // Reset
if (seconds == 59) { if (seconds == 59) {
g.setColor('#000000'); g.setColor('#000000');
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2) - 40); g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2));
} for (count = 0; count <= minutes; count++) {
// Reset minutes drawMinArc(count, settings.circle.colormin);
if (minutes == 59 && seconds == 59) { }
g.setColor('#000000');
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2) - 20);
} }
//Get date as a string //Get date as a string

View File

@ -1 +1,2 @@
0.01: New App! 0.01: New App!
0.02: Change color from red->yellow to ease readability (fix #710)

View File

@ -143,7 +143,7 @@ var currentDist = 0;
function drawMap() { function drawMap() {
g.clearRect(0,0,239,120); g.clearRect(0,0,239,120);
g.setFontAlign(0,0); g.setFontAlign(0,0);
g.setColor(1,0,0); g.setColor(1,1,0);
g.setFontVector(40); g.setFontVector(40);
g.drawString((currentDist===undefined)?"?":(Math.round(currentDist)+"m"), 160, 30); g.drawString((currentDist===undefined)?"?":(Math.round(currentDist)+"m"), 160, 30);
g.setColor(1,1,1); g.setColor(1,1,1);
@ -151,7 +151,7 @@ function drawMap() {
g.drawString(Math.round(totalDistance)+"m", 160, 70); g.drawString(Math.round(totalDistance)+"m", 160, 70);
g.drawString((nextPtIdx/2)+"/"+coordDistance.length, 50, 20); g.drawString((nextPtIdx/2)+"/"+coordDistance.length, 50, 20);
if (!fix.fix) { if (!fix.fix) {
g.setColor(1,0,0); g.setColor(1,1,0);
g.drawString("No GPS", 50, 50); g.drawString("No GPS", 50, 50);
g.setFont("6x8",1); g.setFont("6x8",1);
g.drawString(fix.satellites+" Sats", 50, 70); g.drawString(fix.satellites+" Sats", 50, 70);
@ -161,17 +161,17 @@ function drawMap() {
g.setColor(0,0,0); g.setColor(0,0,0);
g.drawCircle(lastFix.s.x,lastFix.s.y,10); g.drawCircle(lastFix.s.x,lastFix.s.y,10);
} }
for (var i=0;i<gcoords.length;i+=2) { var c1 = g.toColor(1,1,0);
g.setColor((i<=nextPtIdx) ? 63488 : 46486); // red/grey var c2 = g.toColor(0.7,0.7,0.7);
g.fillRect(gcoords[i]-2,gcoords[i+1]-2,gcoords[i]+2,gcoords[i+1]+2); for (var i=0;i<gcoords.length;i+=2)
} g.setColor((i<=nextPtIdx) ? c1 : c2).fillRect(gcoords[i]-2,gcoords[i+1]-2,gcoords[i]+2,gcoords[i+1]+2);
g.setColor(1,0,0); // first part of path g.setColor(1,1,0); // first part of path
g.drawPoly(new Uint8Array(gcoords.buffer, 0, nextPtIdx+2)); g.drawPoly(new Uint8Array(gcoords.buffer, 0, nextPtIdx+2));
g.setColor(1,1,1); // remaining part of path g.setColor(1,1,1); // remaining part of path
g.drawPoly(new Uint8Array(gcoords.buffer, nextPtIdx)); g.drawPoly(new Uint8Array(gcoords.buffer, nextPtIdx));
if (fix && fix.fix) { if (fix && fix.fix) {
g.setColor(1,0,0); g.setColor(1,1,0);
g.drawCircle(fix.s.x,fix.s.y,10); g.drawCircle(fix.s.x,fix.s.y,10);
} }
lastFix = fix; lastFix = fix;

View File

@ -26,3 +26,4 @@
Add whitelist option (fix #78) Add whitelist option (fix #78)
0.22: Move HID to BLE menu 0.22: Move HID to BLE menu
0.23: Change max time offset to 13 for NZ summer daylight time (NZDT) 0.23: Change max time offset to 13 for NZ summer daylight time (NZDT)
0.24: Add Quiet Mode settings

View File

@ -8,6 +8,7 @@ This is Bangle.js's settings menu
* **Debug Info** should debug info be shown on the watch's screen or not? * **Debug Info** should debug info be shown on the watch's screen or not?
* **Beep** most Bangle.js do not have a speaker inside, but they can use the vibration motor to beep in different pitches. You can change the behaviour here to use a Piezo speaker if one is connected * **Beep** most Bangle.js do not have a speaker inside, but they can use the vibration motor to beep in different pitches. You can change the behaviour here to use a Piezo speaker if one is connected
* **Vibration** enable/disable the vibration motor * **Vibration** enable/disable the vibration motor
* **Quiet Mode** prevent notifications/alarms from vibrating/beeping/turning the screen on - see below
* **Locale** set time zone/whether the clock is 12/24 hour (for supported clocks) * **Locale** set time zone/whether the clock is 12/24 hour (for supported clocks)
* **Select Clock** if you have more than one clock face, select the default one * **Select Clock** if you have more than one clock face, select the default one
* **HID** When Bluetooth is enabled, Bangle.js can appear as a Bluetooth Keyboard/Joystick/etc to send keypresses to a connected device. * **HID** When Bluetooth is enabled, Bangle.js can appear as a Bluetooth Keyboard/Joystick/etc to send keypresses to a connected device.
@ -31,3 +32,17 @@ This is Bangle.js's settings menu
* **LCD Timeout** how long should the LCD stay on for if no activity is detected. 0=stay on forever * **LCD Timeout** how long should the LCD stay on for if no activity is detected. 0=stay on forever
* **Wake on X** should the given activity wake up the Bangle.js LCD? * **Wake on X** should the given activity wake up the Bangle.js LCD?
* **Twist X** these options adjust the sensitivity of `Wake on Twist` to ensure Bangle.js wakes up with just the right amount of wrist movement. * **Twist X** these options adjust the sensitivity of `Wake on Twist` to ensure Bangle.js wakes up with just the right amount of wrist movement.
## Quiet Mode
Quiet Mode is a hint to apps and widgets that you do not want to be disturbed.
The exact effects depend on the app. In general the watch will not wake up by itself, but will still respond to button presses.
* **Quiet Mode**
- Off: Normal operation
- Alarms: Stops notifications, but "alarm" apps will still work
- Silent: Blocks even alarms
* **LCD Brightness**, **LCD Timeout**, **Wake on X**:
Override default settings while Quit Mode is active (either as *Alarms* or *Silent*)

View File

@ -2,7 +2,13 @@
var settings = require('Storage').readJSON('setting.json', true); var settings = require('Storage').readJSON('setting.json', true);
if (!settings) return; if (!settings) return;
if (settings.options) Bangle.setOptions(settings.options); if (settings.options) Bangle.setOptions(settings.options);
if (settings.brightness && settings.brightness!=1) Bangle.setLCDBrightness(settings.brightness); if (settings.quiet && settings.qmOptions) Bangle.setOptions(settings.qmOptions);
if (settings.quiet && settings.qmBrightness) {
if (settings.qmBrightness!=1) Bangle.setLCDBrightness(settings.qmBrightness);
} else {
if (settings.brightness && settings.brightness!=1) Bangle.setLCDBrightness(settings.brightness);
}
if (settings.quiet && settings.qmTimeout) Bangle.setLCDTimeout(s.qmTimeout);
if (settings.passkey!==undefined && settings.passkey.length==6) NRF.setSecurity({passkey:settings.passkey, mitm:1, display:1}); if (settings.passkey!==undefined && settings.passkey.length==6) NRF.setSecurity({passkey:settings.passkey, mitm:1, display:1});
if (settings.whitelist) NRF.on('connect', function(addr) { if (!settings.whitelist.includes(addr)) NRF.disconnect(); }); if (settings.whitelist) NRF.on('connect', function(addr) { if (!settings.whitelist.includes(addr)) NRF.disconnect(); });
delete settings; delete settings;

View File

@ -6,12 +6,17 @@ let settings;
function updateSettings() { function updateSettings() {
//storage.erase('setting.json'); // - not needed, just causes extra writes if settings were the same //storage.erase('setting.json'); // - not needed, just causes extra writes if settings were the same
if (Object.keys(settings.qmOptions).length === 0) delete settings.qmOptions;
storage.write('setting.json', settings); storage.write('setting.json', settings);
if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this always exists in this file
} }
function updateOptions() { function updateOptions() {
updateSettings(); updateSettings();
Bangle.setOptions(settings.options) Bangle.setOptions(settings.options)
if (settings.quiet) {
Bangle.setOptions(settings.qmOptions)
}
} }
function gToInternal(g) { function gToInternal(g) {
@ -29,6 +34,7 @@ function resetSettings() {
ble: true, // Bluetooth enabled by default ble: true, // Bluetooth enabled by default
blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used? blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used?
log: false, // Do log messages appear on screen? log: false, // Do log messages appear on screen?
quiet: 0, // quiet mode: 0: off, 1: priority only, 2: total silence
timeout: 10, // Default LCD timeout in seconds timeout: 10, // Default LCD timeout in seconds
vibrate: true, // Vibration enabled by default. App must support vibrate: true, // Vibration enabled by default. App must support
beep: "vib", // Beep enabled by default. App must support beep: "vib", // Beep enabled by default. App must support
@ -48,13 +54,19 @@ function resetSettings() {
twistThreshold: 819.2, twistThreshold: 819.2,
twistMaxY: -800, twistMaxY: -800,
twistTimeout: 1000 twistTimeout: 1000
} },
// Quiet Mode options:
// we only set these if we want to override the default value
// qmOptions: {},
// qmBrightness: undefined,
// qmTimeout: undefined,
}; };
updateSettings(); updateSettings();
} }
settings = storage.readJSON('setting.json', 1); settings = storage.readJSON('setting.json', 1);
if (!settings) resetSettings(); if (!settings) resetSettings();
if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this always exists in here
const boolFormat = v => v ? "On" : "Off"; const boolFormat = v => v ? "On" : "Off";
@ -97,6 +109,7 @@ function showMainMenu() {
} }
} }
}, },
"Quiet Mode": ()=>showQuietModeMenu(),
'Locale': ()=>showLocaleMenu(), 'Locale': ()=>showLocaleMenu(),
'Select Clock': ()=>showClockMenu(), 'Select Clock': ()=>showClockMenu(),
'Set Time': ()=>showSetTimeMenu(), 'Set Time': ()=>showSetTimeMenu(),
@ -224,7 +237,9 @@ function showLCDMenu() {
onchange: v => { onchange: v => {
settings.brightness = v || 1; settings.brightness = v || 1;
updateSettings(); updateSettings();
Bangle.setLCDBrightness(settings.brightness); if (!(settings.quiet && "qmBrightness" in settings)) {
Bangle.setLCDBrightness(settings.brightness);
}
} }
}, },
'LCD Timeout': { 'LCD Timeout': {
@ -235,7 +250,9 @@ function showLCDMenu() {
onchange: v => { onchange: v => {
settings.timeout = 0 | v; settings.timeout = 0 | v;
updateSettings(); updateSettings();
Bangle.setLCDTimeout(settings.timeout); if (!(settings.quiet && "qmTimeout" in settings)) {
Bangle.setLCDTimeout(settings.timeout);
}
} }
}, },
'Wake on BTN1': { 'Wake on BTN1': {
@ -319,6 +336,104 @@ function showLCDMenu() {
} }
return E.showMenu(lcdMenu) return E.showMenu(lcdMenu)
} }
function showQuietModeMenu() {
// we always keep settings.quiet and settings.qmOptions
// other qm values are deleted when not set
const modes = ["Off", "Alarms", "Silent"];
const qmDisabledFormat = v => v ? "Off" : "-";
const qmMenu = {
"": {"title": "Quiet Mode"},
"< Back": () => showMainMenu(),
"Quiet Mode": {
value: settings.quiet|0,
format: v => modes[v%3],
onchange: v => {
settings.quiet = v%3;
updateSettings();
updateOptions();
},
},
"LCD Brightness": {
value: settings.qmBrightness || 0,
min: 0, // 0 = use default
max: 1,
step: 0.1,
format: v => (v>0.05) ? v : "-",
onchange: v => {
if (v>0.05) { // prevent v=0.000000000000001 bugs
settings.qmBrightness = v;
} else {
delete settings.qmBrightness;
}
updateSettings();
if (settings.qmBrightness) { // show result, even if not quiet right now
Bangle.setLCDBrightness(v);
} else {
Bangle.setLCDBrightness(settings.brightness);
}
},
},
"LCD Timeout": {
value: settings.qmTimeout || 0,
min: 0, // 0 = use default (no constant on for quiet mode)
max: 60,
step: 5,
format: v => v>1 ? v : "-",
onchange: v => {
if (v>1) {
settings.qmTimeout = v;
} else {
delete settings.qmTimeout;
}
updateSettings();
if (settings.quiet && v>1) {
Bangle.setLCDTimeout(v);
} else {
Bangle.setLCDTimeout(settings.timeout);
}
},
},
// we disable wakeOn* events by overwriting them as false in qmOptions
// not disabled = not present in qmOptions at all
"Wake on FaceUp": {
value: "wakeOnFaceUp" in settings.qmOptions,
format: qmDisabledFormat,
onchange: () => {
if ("wakeOnFaceUp" in settings.qmOptions) {
delete settings.qmOptions.wakeOnFaceUp;
} else {
settings.qmOptions.wakeOnFaceUp = false;
}
updateOptions();
},
},
"Wake on Touch": {
value: "wakeOnTouch" in settings.qmOptions,
format: qmDisabledFormat,
onchange: () => {
if ("wakeOnTouch" in settings.qmOptions) {
delete settings.qmOptions.wakeOnTouch;
} else {
settings.qmOptions.wakeOnTouch = false;
}
updateOptions();
},
},
"Wake on Twist": {
value: "wakeOnTwist" in settings.qmOptions,
format: qmDisabledFormat,
onchange: () => {
if ("wakeOnTwist" in settings.qmOptions) {
delete settings.qmOptions.wakeOnTwist;
} else {
settings.qmOptions.wakeOnTwist = false;
}
updateOptions();
},
},
};
return E.showMenu(qmMenu);
}
function showLocaleMenu() { function showLocaleMenu() {
const localemenu = { const localemenu = {

View File

@ -0,0 +1 @@
{"ble":true,"blerepl":true,"log":false,"timeout":10,"vibrate":true,"beep":"vib","timezone":0,"HID":false,"clock":null,"12hour":false,"brightness":1,"options":{"wakeOnBTN1":true,"wakeOnBTN2":true,"wakeOnBTN3":true,"wakeOnFaceUp":false,"wakeOnTouch":false,"wakeOnTwist":true,"twistThreshold":819.2,"twistMaxY":-800,"twistTimeout":1000}}

View File

@ -1 +1,2 @@
0.01: New App! 0.01: New App!
0.02: Respect Quiet Mode

View File

@ -88,6 +88,7 @@ function drawApp() {
var buzzCount = 19; var buzzCount = 19;
function buzz() { function buzz() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
Bangle.setLCDPower(1); Bangle.setLCDPower(1);
Bangle.buzz().then(()=>{ Bangle.buzz().then(()=>{
if (buzzCount--) { if (buzzCount--) {

View File

@ -1 +1,2 @@
0.01: Initial Release 0.01: Initial Release
0.02: Color Themes, Smoother scrolling

View File

@ -1,18 +1,34 @@
# Sliding Text Clock - See the time in different languages # Sliding Text Clock - See the time in different languages
Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently only English, French and Japanese are supported Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Please use the upload page to choose which languages you want loaded.
![](app.png) ![](app.png)
## Usage ## Usage
### Button 1
Use Button 1 (the top right button) to change the language Use Button 1 (the top right button) to change the language
| English | English (Traditional) | French | Japanese (Romanji) |
| ---- | ---- | ---- | ---- |
| ![](./format-01.jpg) | ![](format-02.jpg) | ![](format-03.jpg) |![](format-04.jpg) |
### Button 3
Button 3 (bottom right button) is used to change the colour
| Black | Red | Gray | Purple |
| ---- | ---- | ---- | ---- |
| ![](./color-01.jpg) | ![](color-02.jpg) | ![](color-03.jpg) | ![](color-04.jpg) |
## Further Details
For further details of design and working please visit [The Project Page](https://www.notion.so/adrianwkirk/Sliding-Text-Clock-a8fe556f03624a619656ddbc4f36f41b)
## Requests ## Requests
[Reach out to Adrian](https://www.github.com/awkirk71) if you have feature requests or notice bugs. Reach out to adrian@adriankirk.com if you have feature requests or notice bugs.
## Creator ## Creator
Made by [Adrian Kirk](https://www.github.com/awkirk71). Made by [Adrian Kirk](mailto:adrian@adriankirk.com)

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,69 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<p>Please select watch languages</p>
<table id="language_selection">
<tr>
<th>Enabled</th>
<th>Name</th>
</tr>
</table>
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
<script src="../../core/lib/customize.js"></script>
<script>
var slidingtext_languages=[
{name:"English", shortname:"en"},
{name:"English(Traditional)",shortname:"en2"},
{name:"French",shortname:"fr"},
{name:"Japanese",shortname:"jp"}
];
var selected_languages = ["en","fr","jp"];
try{
var stored = localStorage.getItem('slidingtext_stored')
if(stored) selected_languages = JSON.parse(stored);
} catch(e){
console.log("failed to load languages:" + e);
}
console.log("selected languages:" + selected_languages);
var tbl=document.getElementById("language_selection");
for (var i=0; i<slidingtext_languages.length; i++) {
var curr_language = slidingtext_languages[i];
var language_selected = selected_languages.includes(curr_language["shortname"])
var $offset = document.createElement('tr')
$offset.innerHTML = `
<td><input type="checkbox" id="enabled_${i}" ${language_selected? "checked" : ""}></td>
<td>${curr_language['name']}</td>`
tbl.append($offset);
}
// When the 'upload' button is clicked...
document.getElementById("upload").addEventListener("click", function() {
var new_selected_languages=[];
for (var i=0; i<slidingtext_languages.length; i++) {
var curr_language = slidingtext_languages[i];
var checked=document.getElementById("enabled_"+i).checked;
if (checked) {
new_selected_languages.push(curr_language.shortname);
}
}
console.log("new selected languages:" + new_selected_languages);
localStorage.setItem('slidingtext_stored',JSON.stringify(new_selected_languages));
// send finished app (in addition to contents of app.json)
sendCustomizedApp({
storage:[
{name:"slidingtext.languages.json", content:JSON.stringify(new_selected_languages)},
]
});
});
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,15 @@
class DateFormatter {
/**
* A pure virtual class which all the other date formatters will
* inherit from.
* The name will be used to declare the date format when selected
* and the date formatDate methid will return the time formated
* to the lines of text on the screen
*/
name(){return "no name";}
formatDate(date){
return ["no","date","defined"];
}
}
module.exports = DateFormatter;

View File

@ -1,21 +1,102 @@
/** /**
* Adrian Kirk 2021-02 * Adrian Kirk 2021-02
* Sliding text clock inspired by the Pebble * Sliding text clock inspired by the Pebble
* clock with the same name * clock with the same name
*/ */
const color_schemes = [
{
name: "black",
background : [0.0,0.0,0.0],
main_bar: [1.0,1.0,1.0],
other_bars: [0.85,0.85,0.85],
},
{
name: "red",
background : [1.0,0.0,0.0],
main_bar: [1.0,1.0,0.0],
other_bars: [0.85,0.85,0.85]
},
{
name: "grey",
background : [0.5,0.5,0.5],
main_bar: [1.0,1.0,1.0],
other_bars: [0.0,0.0,0.0],
},
{
name: "purple",
background : [1.0,0.0,1.0],
main_bar: [1.0,1.0,0.0],
other_bars: [0.85,0.85,0.85]
},
{
name: "blue",
background : [0.4,0.7,1.0],
main_bar: [1.0,1.0,1.0],
other_bars: [0.9,0.9,0.9]
}
];
let color_scheme_index = 0;
/**
* The Watch Display
*/
function bg_color(){
return color_schemes[color_scheme_index].background;
}
function main_color(){
return color_schemes[color_scheme_index].main_bar;
}
function other_color(){
return color_schemes[color_scheme_index].other_bars;
}
let command_stack_high_priority = [];
let command_stack_low_priority = [];
function next_command(){
command = command_stack_high_priority.pop();
if(command == null){
//console.log("Low priority command");
command = command_stack_low_priority.pop();
} else {
//console.log("High priority command");
}
if(command != null){
command.call();
} else {
//console.log("no command");
}
}
function reset_commands(){
command_stack_high_priority = [];
command_stack_low_priority = [];
}
function has_commands(){
return command_stack_high_priority.length > 0 ||
command_stack_low_priority.lenth > 0;
}
class ShiftText { class ShiftText {
/** /**
* Class Responsible for shifting text around the screen * Class Responsible for shifting text around the screen
* *
* This is a object that initializes itself with a position and * This is a object that initializes itself with a position and
* text after which you can tell it where you want to move to * text after which you can tell it where you want to move to
* using the moveTo method and it will smoothly move the text across * using the moveTo method and it will smoothly move the text across
* at the selected frame rate and speed * at the selected frame rate and speed
*/ */
constructor(x,y,txt,font_name, constructor(x,y,txt,font_name,
font_size,speed_x,speed_y,freq_millis, color){ font_size,speed_x,speed_y,freq_millis,
color,
bg_color){
this.x = x; this.x = x;
this.tgt_x = x; this.tgt_x = x;
this.init_x = x; this.init_x = x;
@ -29,29 +110,44 @@ class ShiftText {
this.speed_x = Math.abs(speed_x); this.speed_x = Math.abs(speed_x);
this.speed_y = Math.abs(speed_y); this.speed_y = Math.abs(speed_y);
this.freq_millis = freq_millis; this.freq_millis = freq_millis;
this.colour = color; this.color = color;
this.bg_color = bg_color;
this.finished_callback=null; this.finished_callback=null;
this.timeoutId = null; this.timeoutId = null;
} }
setColor(color){
this.color = color;
}
setBgColor(bg_color){
this.bg_color = bg_color;
}
reset(){ reset(){
//console.log("reset");
this.hide(); this.hide();
this.x = this.init_x; this.x = this.init_x;
this.y = this.init_y; this.y = this.init_y;
this.txt = this.init_txt; this.txt = this.init_txt;
this.show(); this.show();
if(this.timeoutId != null){ if(this.timeoutId != null){
clearTimeout(this.timeoutId); clearTimeout(this.timeoutId);
} }
} }
show() { show() {
g.setFont(this.font_name,this.font_size); g.setFont(this.font_name,this.font_size);
g.setColor(this.colour[0],this.colour[1],this.colour[2]); g.setColor(this.color[0],this.color[1],this.color[2]);
g.drawString(this.txt, this.x, this.y); g.drawString(this.txt, this.x, this.y);
} }
hide(){ hide(){
g.setFont(this.font_name,this.font_size); g.setFont(this.font_name,this.font_size);
g.setColor(0,0,0); //console.log("bgcolor:" + this.bg_color);
g.setColor(this.bg_color[0],this.bg_color[1],this.bg_color[2]);
g.drawString(this.txt, this.x, this.y); g.drawString(this.txt, this.x, this.y);
/*g.fillPoly([this.x - 1, this.y,
240, this.y,
240, this.y + this.font_size,
this.x -1 , this.y + this.font_size,
]);
*/
} }
setText(txt){ setText(txt){
this.txt = txt; this.txt = txt;
@ -92,15 +188,15 @@ class ShiftText {
this.finished_callback = finished_callback; this.finished_callback = finished_callback;
} }
/** /**
* private internal method for directing the text move. * private internal method for directing the text move.
* It will see how far away we are from the target coords * It will see how far away we are from the target coords
* and move towards the target at the defined speed. * and move towards the target at the defined speed.
*/ */
_doMove(){ _doMove(){
this.hide(); this.hide();
// move closer to the target in the x direction // move closer to the target in the x direction
diff_x = this.tgt_x - this.x; var diff_x = this.tgt_x - this.x;
finished_x = false; var finished_x = false;
if(Math.abs(diff_x) <= this.speed_x){ if(Math.abs(diff_x) <= this.speed_x){
this.x = this.tgt_x; this.x = this.tgt_x;
finished_x = true; finished_x = true;
@ -112,8 +208,8 @@ class ShiftText {
} }
} }
// move closer to the target in the y direction // move closer to the target in the y direction
diff_y = this.tgt_y - this.y; var diff_y = this.tgt_y - this.y;
finished_y = false; var finished_y = false;
if(Math.abs(diff_y) <= this.speed_y){ if(Math.abs(diff_y) <= this.speed_y){
this.y = this.tgt_y; this.y = this.tgt_y;
finished_y = true; finished_y = true;
@ -126,235 +222,90 @@ class ShiftText {
} }
this.show(); this.show();
this.timeoutId = null; this.timeoutId = null;
finished = finished_x & finished_y; var finished = finished_x & finished_y;
if(!finished){ if(!finished){
this.timeoutId = setTimeout(this._doMove.bind(this), this.freq_millis); this.timeoutId = setTimeout(this._doMove.bind(this), this.freq_millis);
} else if(this.finished_callback != null){ } else if(this.finished_callback != null){
//console.log("finished - calling:" + this.finished_callback);
this.finished_callback.call(); this.finished_callback.call();
this.finished_callback = null; this.finished_callback = null;
} }
} }
} }
class DateFormatter { const CLOCK_TEXT_SPEED_X = 10;
/** // a list of display rows
* A pure virtual class which all the other date formatters will let row_displays = [
* inherit from. new ShiftText(240,50,'',"Vector",40,CLOCK_TEXT_SPEED_X,1,10,main_color(),bg_color()),
* The name will be used to declare the date format when selected new ShiftText(240,90,'',"Vector",30,CLOCK_TEXT_SPEED_X,1,10,other_color(),bg_color()),
* and the date formatDate methid will return the time formated new ShiftText(240,120,'',"Vector",30,CLOCK_TEXT_SPEED_X,1,10,other_color(),bg_color()),
* to the lines of text on the screen new ShiftText(240,150,'',"Vector",30,CLOCK_TEXT_SPEED_X,1,10,other_color(),bg_color()),
*/ new ShiftText(240,180,'',"Vector",40,CLOCK_TEXT_SPEED_X,1,10,main_color(),bg_color())
name(){"no name";}
formatDate(date){
return ["","",""];
}
}
/**
* English date formatting
*/
// English String Numbers
const numberStr = ["ZERO","ONE", "TWO", "THREE", "FOUR", "FIVE",
"SIX", "SEVEN","EIGHT", "NINE", "TEN",
"ELEVEN", "TWELVE", "THIRTEEN", "FOURTEEN",
"FIFTEEN", "SIXTEEN", "SEVENTEEN", "EIGHTEEN",
"NINETEEN", "TWENTY"];
const tensStr = ["ZERO", "TEN", "TWENTY", "THIRTY", "FOURTY",
"FIFTY"];
function hoursToText(hours){
hours = hours % 12;
if(hours == 0){
hours = 12;
}
return numberStr[hours];
}
function numberToText(value){
word1 = '';
word2 = '';
if(value > 20){
tens = (value / 10 | 0);
word1 = tensStr[tens];
remainder = value - tens * 10;
if(remainder > 0){
word2 = numberStr[remainder];
}
} else if(value > 0) {
word1 = numberStr[value];
}
return [word1,word2];
}
class EnglishDateFormatter extends DateFormatter{
name(){return "English";}
formatDate(date){
hours_txt = hoursToText(date.getHours());
mins_txt = numberToText(date.getMinutes());
return [hours_txt,mins_txt[0],mins_txt[1]];
}
}
/**
* French date formatting
*/
const frenchNumberStr = [ "ZERO", "UNE", "DEUX", "TROIS", "QUATRE",
"CINQ", "SIX", "SEPT", "HUIT", "NEUF", "DIX",
"ONZE", "DOUZE", "TREIZE", "QUATORZE","QUINZE",
"SEIZE", "DIX SEPT", "DIX HUIT","DIX NEUF", "VINGT",
"VINGT ET UN", "VINGT DEUX", "VINGT TROIS",
"VINGT QUATRE", "VINGT CINQ", "VINGT SIX",
"VINGT SEPT", "VINGT HUIT", "VINGT NEUF"
];
function frenchHoursToText(hours){
hours = hours % 12;
if(hours == 0){
hours = 12;
}
return frenchNumberStr[hours];
}
function frenchHeures(hours){
if(hours % 12 == 1){
return 'HEURE';
} else {
return 'HEURES';
}
}
class FrenchDateFormatter extends DateFormatter {
constructor() {
super();
}
name(){return "French";}
formatDate(date){
hours = frenchHoursToText(date.getHours());
heures = frenchHeures(date.getHours());
mins = date.getMinutes();
if(mins == 0){
if(hours == 0){
return ["MINUIT", "",""];
} else if(hours == 12){
return ["MIDI", "",""];
} else {
return [hours, heures,""];
}
} else if(mins == 30){
return [hours, heures,'ET DEMIE'];
} else if(mins == 15){
return [hours, heures,'ET QUERT'];
} else if(mins == 45){
next_hour = date.getHours() + 1;
hours = frenchHoursToText(next_hour);
heures = frenchHeures(next_hour);
return [hours, heures,"MOINS",'LET QUERT'];
}
if(mins > 30){
to_mins = 60-mins;
mins_txt = frenchNumberStr[to_mins];
next_hour = date.getHours() + 1;
hours = frenchHoursToText(next_hour);
heures = frenchHeures(next_hour);
return [ hours, heures , "MOINS", mins_txt ];
} else {
mins_txt = frenchNumberStr[mins];
return [ hours, heures , mins_txt ];
}
}
}
/**
* Japanese date formatting
*/
const japaneseHourStr = [ "ZERO", "ICHII", "NI", "SAN", "YO",
"GO", "ROKU", "SHICHI", "HACHI", "KU", "JUU",
'JUU ICHI', 'JUU NI'];
const tensPrefixStr = [ "",
"JUU",
'NIJUU',
'SAN JUU',
'YON JUU',
'GO JUU'];
const japaneseMinuteStr = [ ["", "PUN"],
["IP","PUN" ],
["NI", "FUN"],
["SAN", "PUN"],
["YON","FUN"],
["GO", "HUN"],
["RO", "PUN"],
["NANA", "FUN"],
["HAP", "PUN"],
["KYU","FUN"],
["JUP", "PUN"]
];
function japaneseHoursToText(hours){
hours = hours % 12;
if(hours == 0){
hours = 12;
}
return japaneseHourStr[hours];
}
function japaneseMinsToText(mins){
if(mins == 0){
return ["",""];
} else if(mins == 30)
return ["HAN",""];
else {
units = mins % 10;
mins_txt = japaneseMinuteStr[units];
tens = mins /10 | 0;
if(tens > 0){
tens_txt = tensPrefixStr[tens];
return [tens_txt + ' ' + mins_txt[0], mins_txt[1]];
} else {
return [mins_txt[0], mins_txt[1]];
}
}
}
class JapaneseDateFormatter extends DateFormatter {
constructor() {
super();
}
name(){return "Japanese (Romanji)";}
formatDate(date){
hours_txt = japaneseHoursToText(date.getHours());
mins_txt = japaneseMinsToText(date.getMinutes());
return [hours_txt,"JI", mins_txt[0], mins_txt[1] ];
}
}
/**
* The Watch Display
*/
// a list of display rows
let row_displays = [
new ShiftText(240,60,'',"Vector",40,10,10,40,[1,1,1]),
new ShiftText(240,100,'',"Vector",20,10,10,50,[0.85,0.85,0.85]),
new ShiftText(240,120,'',"Vector",20,10,10,60,[0.85,0.85,0.85]),
new ShiftText(240,140,'',"Vector",20,10,10,70,[0.85,0.85,0.85])
]; ];
// a list of the formatters to cycle through function nextColorTheme(){
let date_formatters = [ //console.log("next color theme");
new EnglishDateFormatter(), color_scheme_index += 1;
new FrenchDateFormatter(), if(color_scheme_index >= row_displays.length){
new JapaneseDateFormatter() color_scheme_index = 0;
]; }
var color_scheme = color_schemes[color_scheme_index];
setColor(color_scheme.main_bar,
color_scheme.other_bars,
color_scheme.background);
reset_clock();
draw_clock();
}
function setColor(main_color,other_color,bg_color){
row_displays[0].setColor(main_color);
row_displays[0].setBgColor(bg_color);
for(var i=1; i<row_displays.length - 1; i++){
row_displays[i].setColor(other_color);
row_displays[i].setBgColor(bg_color);
}
row_displays[row_displays.length - 1].setColor(main_color);
row_displays[row_displays.length - 1].setBgColor(bg_color);
g.setColor(bg_color[0],bg_color[1],bg_color[2]);
g.fillPoly([0,25,
0,240,
240,240,
240,25
]);
}
// load the date formats required
LANGUAGES_FILE = "slidingtext.languages.json";
var LANGUAGES_DEFAULT = ["en","en2"];
var locales = null;
try{
locales = require("Storage").readJSON(LANGUAGES_FILE);
if(locales != null){
console.log("loaded languages:" + JSON.stringify(locales));
} else {
console.log("no languages loaded");
locales = LANGUAGES_DEFAULT;
}
} catch(e){
console.log("failed to load languages:" + e);
}
if(locales == null || locales.length == 0){
locales = LANGUAGES_DEFAULT;
console.log("defaulting languages to locale:" + locales);
}
let date_formatters = [];
for(var i=0; i< locales.length; i++){
console.log("loading locale:" + locales[i]);
var Formatter = require("slidingtext.locale." + locales[i] + ".js");
date_formatters.push(new Formatter());
}
// current index of the date formatter to display // current index of the date formatter to display
let date_formatter_idx = 0; let date_formatter_idx = 0;
let date_formatter = date_formatters[date_formatter_idx]; let date_formatter = date_formatters[date_formatter_idx];
// The small display at the top which announces the date format
let format_name_display = new ShiftText(55,0,'',"Vector",10,1,1,50,[1,1,1]);
function changeFormatter(){ function changeFormatter(){
date_formatter_idx += 1; date_formatter_idx += 1;
if(date_formatter_idx >= date_formatters.length){ if(date_formatter_idx >= date_formatters.length){
@ -364,61 +315,204 @@ function changeFormatter(){
date_formatter = date_formatters[date_formatter_idx]; date_formatter = date_formatters[date_formatter_idx];
reset_clock(); reset_clock();
draw_clock(); draw_clock();
// now announce the formatter by name command_stack_high_priority.unshift(
format_name_display.setTextYPosition(date_formatter.name(),-10); function() {
format_name_display.moveToY(15); //console.log("move in new:" + txt);
// and then move back // first select the top or bottom to display the formatter name
format_name_display.onFinished( // We choose the first spare row without text
function(){ var format_name_display = row_displays[row_displays.length - 1];
format_name_display.moveToY(-10); if (format_name_display.txt != '') {
format_name_display = row_displays[0];
}
if (format_name_display.txt != ''){
return;
}
format_name_display.speed_x = 3;
format_name_display.onFinished(function(){
format_name_display.speed_x = CLOCK_TEXT_SPEED_X;
console.log("return speed to:" + format_name_display.speed_x)
next_command();
});
format_name_display.setTextXPosition(date_formatter.name(),220);
format_name_display.moveToX(-date_formatter.name().length * format_name_display.font_size);
} }
); );
} }
function reset_clock(){ function reset_clock(){
//console.log("reset_clock"); //console.log("reset_clock");
var i; for (var i = 0; i < row_displays.length; i++) {
for (i = 0; i < row_displays.length; i++) { row_displays[i].speed_x = CLOCK_TEXT_SPEED_X;
row_displays[i].reset(); row_displays[i].reset();
} }
reset_commands();
} }
let last_draw_time = null;
const next_minute_boundary_secs = 7.5;
function draw_clock(){ function draw_clock(){
//console.log("draw_clock"); var date = new Date();
date = new Date(); if(last_draw_time != null &&
rows = date_formatter.formatDate(date); date.getTime() - last_draw_time.getTime() < next_minute_boundary_secs * 1000 &&
var i; has_commands() ){
for (i = 0; i < rows.length; i++) { console.log("skipping draw clock");
return;
} else {
last_draw_time = date;
}
reset_commands();
console.log("draw_clock:" + date.toISOString());
// we don't want the time to be displayed
// and then immediately be trigger another time
if(date.getSeconds() > 60 - next_minute_boundary_secs){
console.log("forwarding to next minute");
date = new Date(date.getTime() + next_minute_boundary_secs * 1000);
}
//date.setMinutes(37);
var rows = date_formatter.formatDate(date);
var display;
for (var i = 0; i < rows.length; i++) {
display = row_displays[i]; display = row_displays[i];
txt = rows[i]; var txt = rows[i];
//console.log(i + "->" + txt);
display_row(display,txt); display_row(display,txt);
} }
// If the dateformatter has not returned enough // If the dateformatter has not returned enough
// rows then treat the reamining rows as empty // rows then treat the reamining rows as empty
for (j = i; j < row_displays.length; j++) { for (var j = i; j < row_displays.length; j++) {
display = row_displays[j]; display = row_displays[j];
//console.log(i + "->''(empty)");
display_row(display,''); display_row(display,'');
} }
next_command();
//console.log(date); //console.log(date);
} }
function display_row(display,txt){ function display_row(display,txt){
if(display.txt == ''){ if(display == null) {
display.setTextXPosition(txt,240); console.log("no display for text:" + txt)
display.moveToX(20); return;
} else if(txt != display.txt){ }
display.moveToX(-100);
display.onFinished( if(display.txt == null || display.txt == ''){
function(){ if(txt != '') {
display.setTextXPosition(txt,240); command_stack_high_priority.unshift(
display.moveToX(20); function () {
} //console.log("move in new:" + txt);
display.onFinished(next_command);
display.setTextXPosition(txt, 240);
display.moveToX(20);
}
);
}
} else if(txt != display.txt && display.txt != null){
command_stack_high_priority.push(
function(){
//console.log("move out:" + txt);
display.onFinished(next_command);
display.moveToX(-display.txt.length * display.font_size);
}
);
command_stack_low_priority.push(
function(){
//console.log("move in:" + txt);
display.onFinished(next_command);
display.setTextXPosition(txt,240);
display.moveToX(20);
}
); );
} else { } else {
display.setTextXPosition(txt,20); command_stack_high_priority.push(
function(){
//console.log("move in2:" + txt);
display.setTextXPosition(txt,20);
next_command();
}
);
} }
} }
/**
* called from load_settings on startup to
* set the color scheme to named value
*/
function set_colorscheme(colorscheme_name){
console.log("setting color scheme:" + colorscheme_name);
for (var i=0; i < color_schemes.length; i++) {
if(color_schemes[i].name == colorscheme_name){
color_scheme_index = i;
console.log("match");
var color_scheme = color_schemes[color_scheme_index];
setColor(color_scheme.main_bar,
color_scheme.other_bars,
color_scheme.background);
break;
}
}
}
function set_dateformat(dateformat_name){
console.log("setting date format:" + dateformat_name);
for (var i=0; i < date_formatters.length; i++) {
if(date_formatters[i].name() == dateformat_name){
date_formatter_idx = i;
date_formatter = date_formatters[date_formatter_idx];
console.log("match");
}
}
}
const PREFERENCE_FILE = "slidingtext.settings.json";
/**
* Called on startup to set the watch to the last preference settings
*/
function load_settings(){
try{
settings = require("Storage").readJSON(PREFERENCE_FILE);
if(settings != null){
console.log("loaded:" + JSON.stringify(settings));
if(settings.color_scheme != null){
set_colorscheme(settings.color_scheme);
}
if(settings.date_format != null){
set_dateformat(settings.date_format);
}
} else {
console.log("no settings to load");
}
} catch(e){
console.log("failed to load settings:" + e);
}
}
/**
* Called on button press to save down the last preference settings
*/
function save_settings(){
var settings = {
date_format : date_formatter.name(),
color_scheme : color_schemes[color_scheme_index].name,
};
console.log("saving:" + JSON.stringify(settings));
require("Storage").writeJSON(PREFERENCE_FILE,settings);
}
function button1pressed() {
changeFormatter();
save_settings();
}
function button3pressed() {
console.log("button3pressed");
nextColorTheme();
reset_clock();
draw_clock();
save_settings();
}
// The interval reference for updating the clock // The interval reference for updating the clock
let intervalRef = null; let intervalRef = null;
@ -430,9 +524,9 @@ function clearTimers(){
} }
function startTimers(){ function startTimers(){
let date = new Date(); var date = new Date();
let secs = date.getSeconds(); var secs = date.getSeconds();
let nextMinuteStart = 60 - secs; var nextMinuteStart = 60 - secs;
//console.log("scheduling clock draw in " + nextMinuteStart + " seconds"); //console.log("scheduling clock draw in " + nextMinuteStart + " seconds");
setTimeout(scheduleDrawClock,nextMinuteStart * 1000); setTimeout(scheduleDrawClock,nextMinuteStart * 1000);
draw_clock(); draw_clock();
@ -457,6 +551,7 @@ Bangle.on('lcdPower', (on) => {
clearTimers(); clearTimers();
} }
}); });
Bangle.on('faceUp',function(up){ Bangle.on('faceUp',function(up){
//console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn()); //console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn());
if (up && !Bangle.isLCDOn()) { if (up && !Bangle.isLCDOn()) {
@ -467,9 +562,17 @@ Bangle.on('faceUp',function(up){
}); });
g.clear(); g.clear();
load_settings();
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
startTimers(); startTimers();
// Show launcher when middle button pressed // Show launcher when middle button pressed
setWatch(Bangle.showLauncher, BTN2,{repeat:false,edge:"falling"}); setWatch(Bangle.showLauncher, BTN2,{repeat:false,edge:"falling"});
setWatch(changeFormatter, BTN1,{repeat:true,edge:"falling"});
// Handle button 1 being pressed
setWatch(button1pressed, BTN1,{repeat:true,edge:"falling"});
// Handle button 3 being pressed
setWatch(button3pressed, BTN3,{repeat:true,edge:"falling"});

View File

@ -0,0 +1,15 @@
var DateFormatter = require("slidingtext.dtfmt.js");
const hoursToText = require("slidingtext.utils.en.js").hoursToText;
const numberToText = require("slidingtext.utils.en.js").numberToText;
class EnglishDateFormatter extends DateFormatter {
constructor() { super();}
name(){return "English";}
formatDate(date){
var hours_txt = hoursToText(date.getHours());
var mins_txt = numberToText(date.getMinutes());
return [hours_txt,mins_txt[0],mins_txt[1]];
}
}
module.exports = EnglishDateFormatter;

View File

@ -0,0 +1,53 @@
var DateFormatter = require("slidingtext.dtfmt.js");
const hoursToText = require("slidingtext.utils.en.js").hoursToText;
const numberToText = require("slidingtext.utils.en.js").numberToText;
class EnglishTraditionalDateFormatter extends DateFormatter {
constructor() {
super();
}
name(){return "English (Traditional)";}
formatDate(date){
var mins = date.getMinutes();
var hourOfDay = date.getHours();
if(mins > 30){
hourOfDay += 1;
}
var hours = hoursToText(hourOfDay);
// Deal with the special times first
if(mins == 0){
return [hours,"", "O'","CLOCK"];
} else if(mins == 30){
return ["","HALF", "PAST", "", hours];
} else if(mins == 15){
return ["","QUARTER", "PAST", "", hours];
} else if(mins == 45) {
return ["", "QUARTER", "TO", "", hours];
}
var mins_txt;
var from_to;
var mins_value;
if(mins > 30){
mins_value = 60-mins;
from_to = "TO";
mins_txt = numberToText(mins_value);
} else {
mins_value = mins;
from_to = "PAST";
mins_txt = numberToText(mins_value);
}
if(mins_txt[1] != '') {
return ['', mins_txt[0], mins_txt[1], from_to, hours];
} else {
if(mins_value % 5 == 0) {
return ['', mins_txt[0], from_to, '', hours];
} else if(mins_value == 1){
return ['', mins_txt[0], 'MINUTE', from_to, hours];
} else {
return ['', mins_txt[0], 'MINUTES', from_to, hours];
}
}
}
}
module.exports = EnglishTraditionalDateFormatter;

View File

@ -0,0 +1,70 @@
var DateFormatter = require("slidingtext.dtfmt.js");
/**
* French date formatting
*/
const frenchNumberStr = [ "ZERO", "UNE", "DEUX", "TROIS", "QUATRE",
"CINQ", "SIX", "SEPT", "HUIT", "NEUF", "DIX",
"ONZE", "DOUZE", "TREIZE", "QUATORZE","QUINZE",
"SEIZE", "DIX SEPT", "DIX HUIT","DIX NEUF", "VINGT",
"VINGT ET UN", "VINGT DEUX", "VINGT TROIS",
"VINGT QUATRE", "VINGT CINQ", "VINGT SIX",
"VINGT SEPT", "VINGT HUIT", "VINGT NEUF"
];
function frenchHoursToText(hours){
hours = hours % 12;
if(hours == 0){
hours = 12;
}
return frenchNumberStr[hours];
}
function frenchHeures(hours){
if(hours % 12 == 1){
return 'HEURE';
} else {
return 'HEURES';
}
}
class FrenchDateFormatter extends DateFormatter {
constructor() { super(); }
name(){return "French";}
formatDate(date){
var hours = frenchHoursToText(date.getHours());
var heures = frenchHeures(date.getHours());
var mins = date.getMinutes();
if(mins == 0){
if(hours == 0){
return ["MINUIT", "",""];
} else if(hours == 12){
return ["MIDI", "",""];
} else {
return [hours, heures,""];
}
} else if(mins == 30){
return [hours, heures,'ET DEMIE'];
} else if(mins == 15){
return [hours, heures,'ET QUERT'];
} else if(mins == 45){
var next_hour = date.getHours() + 1;
hours = frenchHoursToText(next_hour);
heures = frenchHeures(next_hour);
return [hours, heures,"MOINS",'LET QUERT'];
}
if(mins > 30){
var to_mins = 60-mins;
var mins_txt = frenchNumberStr[to_mins];
next_hour = date.getHours() + 1;
hours = frenchHoursToText(next_hour);
heures = frenchHeures(next_hour);
return [ hours, heures , "MOINS", mins_txt ];
} else {
mins_txt = frenchNumberStr[mins];
return [ hours, heures , mins_txt ];
}
}
}
module.exports = FrenchDateFormatter;

View File

@ -0,0 +1,71 @@
var DateFormatter = require("slidingtext.dtfmt.js");
/**
* Japanese date formatting
*/
const japaneseHourStr = [ "ZERO", "ICHII", "NI", "SAN", "YO",
"GO", "ROKU", "SHICHI", "HACHI", "KU", "JUU",
'JUU ICHI', 'JUU NI'];
const tensPrefixStr = [ "",
"JUU",
'NIJUU',
'SAN JUU',
'YON JUU',
'GO JUU'];
const japaneseMinuteStr = [ ["", "PUN"],
["IP","PUN" ],
["NI", "FUN"],
["SAN", "PUN"],
["YON","FUN"],
["GO", "HUN"],
["RO", "PUN"],
["NANA", "FUN"],
["HAP", "PUN"],
["KYU","FUN"],
["JUP", "PUN"]
];
function japaneseHoursToText(hours){
hours = hours % 12;
if(hours == 0){
hours = 12;
}
return japaneseHourStr[hours];
}
function japaneseMinsToText(mins){
if(mins == 0){
return ["",""];
} else if(mins == 30)
return ["HAN",""];
else {
var units = mins % 10;
var mins_txt = japaneseMinuteStr[units];
var tens = mins /10 | 0;
if(tens > 0){
var tens_txt = tensPrefixStr[tens];
var minutes_txt;
if(mins_txt[0] != ''){
minutes_txt = mins_txt[0] + ' ' + mins_txt[1];
} else {
minutes_txt = mins_txt[1];
}
return [tens_txt, minutes_txt];
} else {
return [mins_txt[0], mins_txt[1]];
}
}
}
class JapaneseDateFormatter extends DateFormatter {
constructor() { super(); }
name(){return "Japanese (Romanji)";}
formatDate(date){
var hours_txt = japaneseHoursToText(date.getHours());
var mins_txt = japaneseMinsToText(date.getMinutes());
return [hours_txt,"JI", mins_txt[0], mins_txt[1] ];
}
}
module.exports = JapaneseDateFormatter;

View File

@ -0,0 +1,34 @@
const numberStr = ["ZERO","ONE", "TWO", "THREE", "FOUR", "FIVE",
"SIX", "SEVEN","EIGHT", "NINE", "TEN",
"ELEVEN", "TWELVE", "THIRTEEN", "FOURTEEN",
"FIFTEEN", "SIXTEEN", "SEVENTEEN", "EIGHTEEN",
"NINETEEN", "TWENTY"];
const tensStr = ["ZERO", "TEN", "TWENTY", "THIRTY", "FOURTY",
"FIFTY"];
const hoursToText = (hours)=>{
hours = hours % 12;
if(hours == 0){
hours = 12;
}
return numberStr[hours];
}
const numberToText = (value)=> {
var word1 = '';
var word2 = '';
if(value > 20){
var tens = (value / 10 | 0);
word1 = tensStr[tens];
var remainder = value - tens * 10;
if(remainder > 0){
word2 = numberStr[remainder];
}
} else if(value > 0) {
word1 = numberStr[value];
}
return [word1,word2];
}
exports.hoursToText = hoursToText;
exports.numberToText = numberToText;

View File

@ -1 +1,2 @@
0.01: Initial Release 0.01: Initial Release
0.02: Added Colour Themes

View File

@ -6,7 +6,24 @@ The Sweep Clock provides a clock with a perfectly smooth sweep second hand with
## Usage ## Usage
Use Button 1 (the top right button) to change the numeral types (currently European and Roman) ### Button 1
Use Button 1 (the top right button) to change the numeral type
| Default clock face | Roman Numeral Font | No Digits |
| ---- | ---- | ---- |
| ![](./numeral-01.jpg) | ![](numeral-02.jpg) | ![](numeral-03.jpg) |
### Button 3
Button 3 (bottom right button) is used to change the colour
| Red | Grey | Purple |
| ---- | ---- | ---- |
| ![](./color-01.jpg) | ![](color-02.jpg) | ![](color-03.jpg) |
## Further Details ## Further Details
@ -14,8 +31,8 @@ For further details of design and working please visit [The Project Page](https:
## Requests ## Requests
[Reach out to Adrian](https://www.github.com/awkirk71) if you have feature requests or notice bugs. Reach out to adrian@adriankirk.com if you have feature requests or notice bugs.
## Creator ## Creator
Made by [Adrian Kirk](https://www.github.com/awkirk71). Made by [Adrian Kirk](mailto:adrian@adriankirk.com)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("lEowkE/4AdmU/CaMgCaUQCaPzgQmR+UDCaPxCaUwj525gJ2/CZ0vMSJ2SkEACaCJBgEPRKEAgJ3Q+cxE6Myn8yO6EggMjMSR3Q+ASBgCdRO4KJQCaMwCaPzCQQTPEwYTOJoYTO+cQCaKHCAAizLkEQYgQTF+YTHmMAkITEj//l8wl4UHkcwiIRBAQMD/8/CZKLBiUAiEigMCBAMzCwMyPI8zPQMzgMBn/yh/zgZSICoMxn5kC+ZmBPhY3CHAIAbA")) require("heatshrink").decompress(atob("lEowkA/4AGmYIHABHzmVCCaE0kUin4TPmUimQTQ+UzmcvJ6EjCaP/kYABCaEymYTl+Q7SMgITTmQTQPAK0RMgITm+QTS+ciPCcikQpPY4MjmYTO+czmcyHh4TCmcvJ54nCPCBjBJx4oECc8zJ6ATTn48RE4YTTHh4SDH4ImRFBwTGFBgTGFBgSGFBYmHUgITRmcyFBASFAoUjE5PzkQLBHJxiDAQP/GxAA=="))

View File

@ -5,10 +5,55 @@
*/ */
const screen_center_x = g.getWidth()/2; const screen_center_x = g.getWidth()/2;
const screen_center_y = g.getHeight()/2; const screen_center_y = 10 + g.getHeight()/2;
require("FontCopasetic40x58Numeric").add(Graphics); require("FontCopasetic40x58Numeric").add(Graphics);
const color_schemes = [
{
name: "black",
background : [0.0,0.0,0.0],
second_hand: [1.0,0.0,0.0],
minute_hand: [1.0,1.0,1.0],
hour_hand: [1.0,1.0,1.0],
numeral:[1.0,1.0,1.0]
},
{
name: "red",
background : [1.0,0.0,0.0],
second_hand: [1.0,1.0,0.0],
minute_hand: [1.0,1.0,1.0],
hour_hand: [1.0,1.0,1.0],
numeral:[1.0,1.0,1.0]
},
{
name: "grey",
background : [0.5,0.5,0.5],
second_hand: [0.0,0.0,0.0],
minute_hand: [1.0,1.0,1.0],
hour_hand: [1.0,1.0,1.0],
numeral:[1.0,1.0,1.0]
},
{
name: "purple",
background : [1.0,0.0,1.0],
second_hand: [1.0,1.0,0.0],
minute_hand: [1.0,1.0,1.0],
hour_hand: [1.0,1.0,1.0],
numeral:[1.0,1.0,1.0]
},
{
name: "blue",
background : [0.4,0.7,1.0],
second_hand: [0.5,0.5,0.5],
minute_hand: [1.0,1.0,1.0],
hour_hand: [1.0,1.0,1.0],
numeral:[1.0,1.0,1.0]
}
];
let color_scheme_index = 0;
class Hand { class Hand {
/** /**
* Pure virtual class for all Hand classes to extend. * Pure virtual class for all Hand classes to extend.
@ -28,16 +73,12 @@ class ThinHand extends Hand {
length, length,
tolerance, tolerance,
draw_test, draw_test,
red, color_theme){
green,
blue){
super(); super();
this.centerX = centerX; this.centerX = centerX;
this.centerY = centerY; this.centerY = centerY;
this.length = length; this.length = length;
this.red = red; this.color_theme = color_theme;
this.green = green;
this.blue = blue;
// The last x and y coordinates (not the centre) of the last draw // The last x and y coordinates (not the centre) of the last draw
this.last_x = centerX; this.last_x = centerX;
this.last_y = centerY; this.last_y = centerY;
@ -60,13 +101,14 @@ class ThinHand extends Hand {
// and then call the predicate to see if a redraw is needed // and then call the predicate to see if a redraw is needed
this.draw_test(this.angle,this.last_draw_time) ){ this.draw_test(this.angle,this.last_draw_time) ){
// rub out the old hand line // rub out the old hand line
g.setColor(0,0,0); background = color_schemes[color_scheme_index].background;
g.setColor(background[0],background[1],background[2]);
g.drawLine(this.centerX, this.centerY, this.last_x, this.last_y); g.drawLine(this.centerX, this.centerY, this.last_x, this.last_y);
// Now draw the new hand line // Now draw the new hand line
g.setColor(this.red,this.green,this.blue); hand_color = color_schemes[color_scheme_index][this.color_theme];
g.setColor(hand_color[0],hand_color[1],hand_color[2]);
x2 = this.centerX + this.length*Math.sin(angle); x2 = this.centerX + this.length*Math.sin(angle);
y2 = this.centerY - this.length*Math.cos(angle); y2 = this.centerY - this.length*Math.cos(angle);
g.setColor(this.red,this.green,this.blue);
g.drawLine(this.centerX, this.centerY, x2, y2); g.drawLine(this.centerX, this.centerY, x2, y2);
// and store the last draw details for the next call // and store the last draw details for the next call
this.last_x = x2; this.last_x = x2;
@ -90,18 +132,14 @@ class ThickHand extends Hand {
length, length,
tolerance, tolerance,
draw_test, draw_test,
red, color_theme,
green,
blue,
base_height, base_height,
thickness){ thickness){
super(); super();
this.centerX = centerX; this.centerX = centerX;
this.centerY = centerY; this.centerY = centerY;
this.length = length; this.length = length;
this.red = red; this.color_theme = color_theme;
this.green = green;
this.blue = blue;
this.thickness = thickness; this.thickness = thickness;
this.base_height = base_height; this.base_height = base_height;
// angle from the center to the top corners of the rectangle // angle from the center to the top corners of the rectangle
@ -132,7 +170,8 @@ class ThickHand extends Hand {
// method to move the hand to a new angle // method to move the hand to a new angle
moveTo(angle){ moveTo(angle){
if(Math.abs(angle - this.angle) > this.tolerance || this.draw_test(this.angle - this.delta_base,this.angle + this.delta_base ,this.last_draw_time) ){ if(Math.abs(angle - this.angle) > this.tolerance || this.draw_test(this.angle - this.delta_base,this.angle + this.delta_base ,this.last_draw_time) ){
g.setColor(0,0,0); background = color_schemes[color_scheme_index].background;
g.setColor(background[0],background[1],background[2]);
g.fillPoly([this.last_x1, g.fillPoly([this.last_x1,
this.last_y1, this.last_y1,
this.last_x2, this.last_x2,
@ -142,7 +181,6 @@ class ThickHand extends Hand {
this.last_x4, this.last_x4,
this.last_y4 this.last_y4
]); ]);
g.setColor(this.red,this.green,this.blue);
// bottom left // bottom left
x1 = this.centerX + x1 = this.centerX +
this.vertex_radius_base*Math.sin(angle - this.delta_base); this.vertex_radius_base*Math.sin(angle - this.delta_base);
@ -157,7 +195,8 @@ class ThickHand extends Hand {
// top left // top left
x4 = this.centerX + this.vertex_radius_top*Math.sin(angle - this.delta_top); x4 = this.centerX + this.vertex_radius_top*Math.sin(angle - this.delta_top);
y4 = this.centerY - this.vertex_radius_top*Math.cos(angle - this.delta_top); y4 = this.centerY - this.vertex_radius_top*Math.cos(angle - this.delta_top);
g.setColor(this.red,this.green,this.blue); hand_color = color_schemes[color_scheme_index][this.color_theme];
g.setColor(hand_color[0],hand_color[1],hand_color[2]);
g.fillPoly([x1,y1, g.fillPoly([x1,y1,
x2,y2, x2,y2,
x3,y3, x3,y3,
@ -184,10 +223,10 @@ let force_redraw = false;
// The seconds hand is the main focus and is set to redraw on every cycle // The seconds hand is the main focus and is set to redraw on every cycle
let seconds_hand = new ThinHand(screen_center_x, let seconds_hand = new ThinHand(screen_center_x,
screen_center_y, screen_center_y,
100, 95,
0, 0,
(angle, last_draw_time) => false, (angle, last_draw_time) => false,
1.0,0.0,0.0); "second_hand");
// The minute hand is set to redraw at a 250th of a circle, // The minute hand is set to redraw at a 250th of a circle,
// when the second hand is ontop or slighly overtaking // when the second hand is ontop or slighly overtaking
// or when a force_redraw is called // or when a force_redraw is called
@ -201,7 +240,7 @@ let minutes_hand = new ThinHand(screen_center_x,
80, 80,
2*Math.PI/250, 2*Math.PI/250,
minutes_hand_redraw, minutes_hand_redraw,
1.0,1.0,1.0); "minute_hand");
// The hour hand is a thick hand so we have to redraw when the minute hand // The hour hand is a thick hand so we have to redraw when the minute hand
// overlaps from its behind andle coverage to its ahead angle coverage. // overlaps from its behind andle coverage to its ahead angle coverage.
let hour_hand_redraw = function(angle_from, angle_to, last_draw_time){ let hour_hand_redraw = function(angle_from, angle_to, last_draw_time){
@ -214,12 +253,13 @@ let hours_hand = new ThickHand(screen_center_x,
40, 40,
2*Math.PI/600, 2*Math.PI/600,
hour_hand_redraw, hour_hand_redraw,
1.0,1.0,1.0, "hour_hand",
5, 5,
4); 4);
function draw_clock(){ function draw_clock(){
date = new Date(); date = new Date();
draw_background();
draw_hour_digit(date); draw_hour_digit(date);
draw_seconds(date); draw_seconds(date);
draw_mins(date); draw_mins(date);
@ -242,7 +282,7 @@ function draw_mins(date,seconds_angle){
mins_angle = 2*Math.PI*mins_frac; mins_angle = 2*Math.PI*mins_frac;
redraw = minutes_hand.moveTo(mins_angle); redraw = minutes_hand.moveTo(mins_angle);
if(redraw){ if(redraw){
console.log("redraw mins"); //console.log("redraw mins");
} }
} }
@ -252,7 +292,7 @@ function draw_hours(date){
hours_angle = 2*Math.PI*hours_frac; hours_angle = 2*Math.PI*hours_frac;
redraw = hours_hand.moveTo(hours_angle); redraw = hours_hand.moveTo(hours_angle);
if(redraw){ if(redraw){
console.log("redraw hours"); //console.log("redraw hours");
} }
} }
@ -275,6 +315,18 @@ class NumeralFont {
* method to draw text at the required coordinates * method to draw text at the required coordinates
*/ */
draw(hour_txt,x,y){ return "";} draw(hour_txt,x,y){ return "";}
/**
* Called from the settings loader to identify the font
*/
getName(){return "";}
}
class NoFont extends NumeralFont{
constructor(){super();}
getDimensions(hour){return [0,0];}
hour_txt(hour){ return ""; }
draw(hour_txt,x,y){ return "";}
getName(){return "NoFont";}
} }
class CopasetFont extends NumeralFont{ class CopasetFont extends NumeralFont{
@ -314,6 +366,7 @@ class CopasetFont extends NumeralFont{
g.setFontCopasetic40x58Numeric(); g.setFontCopasetic40x58Numeric();
g.drawString(hour_txt,x,y); g.drawString(hour_txt,x,y);
} }
getName(){return "Copaset";}
} }
@ -358,6 +411,7 @@ class RomanNumeralFont extends NumeralFont{
g.setFont("Vector",40); g.setFont("Vector",40);
g.drawString(hour_txt,x,y); g.drawString(hour_txt,x,y);
} }
getName(){return "Roman";}
} }
// The problem with the trig inverse functions on // The problem with the trig inverse functions on
@ -419,11 +473,12 @@ class HourScriber {
drawHour(hours){ drawHour(hours){
changed = false; changed = false;
if(this.curr_hours != hours || this.curr_numeral_font !=this.numeral_font){ if(this.curr_hours != hours || this.curr_numeral_font !=this.numeral_font){
g.setColor(0,0,0); background = color_schemes[color_scheme_index].background;
g.setColor(background[0],background[1],background[2]);
this.curr_numeral_font.draw(this.curr_hour_str, this.curr_numeral_font.draw(this.curr_hour_str,
this.curr_hour_x, this.curr_hour_x,
this.curr_hour_y); this.curr_hour_y);
console.log("erasing old hour"); //console.log("erasing old hour");
hours_frac = hours / 12; hours_frac = hours / 12;
angle = 2*Math.PI*hours_frac; angle = 2*Math.PI*hours_frac;
dimensions = this.numeral_font.getDimensions(hours); dimensions = this.numeral_font.getDimensions(hours);
@ -477,15 +532,16 @@ class HourScriber {
} }
if(changed || if(changed ||
this.draw_test(this.angle_from, this.angle_to, this.last_draw_time) ){ this.draw_test(this.angle_from, this.angle_to, this.last_draw_time) ){
g.setColor(1,1,1); numeral_color = color_schemes[color_scheme_index].numeral;
g.setColor(numeral_color[0],numeral_color[1],numeral_color[2]);
this.numeral_font.draw(this.curr_hour_str,this.curr_hour_x,this.curr_hour_y); this.numeral_font.draw(this.curr_hour_str,this.curr_hour_x,this.curr_hour_y);
this.last_draw_time = new Date(); this.last_draw_time = new Date();
console.log("redraw digit"); //console.log("redraw digit");
} }
} }
} }
let numeral_fonts = [new CopasetFont(), new RomanNumeralFont()]; let numeral_fonts = [new CopasetFont(), new RomanNumeralFont(), new NoFont()];
let numeral_fonts_index = 0; let numeral_fonts_index = 0;
/** /**
* predicate for deciding when the digit has to be redrawn * predicate for deciding when the digit has to be redrawn
@ -540,7 +596,93 @@ function draw_hour_digit(date){
hour_scriber.drawHour(hours); hour_scriber.drawHour(hours);
} }
// Boiler plate code for setting up the clock function draw_background(){
if(force_redraw){
background = color_schemes[color_scheme_index].background;
g.setColor(background[0],background[1],background[2]);
g.fillPoly([0,25,
0,240,
240,240,
240,25
]);
}
}
function next_colorscheme(){
color_scheme_index += 1;
color_scheme_index = color_scheme_index % color_schemes.length;
//console.log("color_scheme_index=" + color_scheme_index);
force_redraw = true;
}
/**
* called from load_settings on startup to
* set the color scheme to named value
*/
function set_colorscheme(colorscheme_name){
console.log("setting color scheme:" + colorscheme_name);
for (var i=0; i < color_schemes.length; i++) {
if(color_schemes[i].name == colorscheme_name){
color_scheme_index = i;
force_redraw = true;
console.log("match");
break;
}
}
}
/**
* called from load_settings on startup
* to set the font to named value
*/
function set_font(font_name){
console.log("setting font:" + font_name);
for (var i=0; i < numeral_fonts.length; i++) {
if(numeral_fonts[i].getName() == font_name){
numeral_fonts_index = i;
force_redraw = true;
console.log("match");
hour_scriber.setNumeralFont(numeral_fonts[numeral_fonts_index]);
break;
}
}
}
/**
* Called on startup to set the watch to the last preference settings
*/
function load_settings(){
try{
settings = require("Storage").readJSON("sweepclock.settings.json");
if(settings != null){
console.log("loaded:" + JSON.stringify(settings));
if(settings.color_scheme != null){
set_colorscheme(settings.color_scheme);
}
if(settings.font != null){
set_font(settings.font);
}
} else {
console.log("no settings to load");
}
} catch(e){
console.log("failed to load settings:" + e);
}
}
/**
* Called on button press to save down the last preference settings
*/
function save_settings(){
settings = {
font : numeral_fonts[numeral_fonts_index].getName(),
color_scheme : color_schemes[color_scheme_index].name,
};
console.log("saving:" + JSON.stringify(settings));
require("Storage").writeJSON("sweepclock.settings.json",settings);
}
// Boiler plate code for setting up the clock,
// below // below
let intervalRef = null; let intervalRef = null;
@ -593,6 +735,7 @@ Bangle.on('faceUp',function(up){
}); });
g.clear(); g.clear();
load_settings();
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
startTimers(); startTimers();
@ -602,8 +745,17 @@ setWatch(Bangle.showLauncher, BTN2,{repeat:false,edge:"falling"});
function button1pressed(){ function button1pressed(){
next_font(); next_font();
save_settings();
}
function button3pressed(){
next_colorscheme();
save_settings();
} }
// Handle button 1 being pressed // Handle button 1 being pressed
setWatch(button1pressed, BTN1,{repeat:true,edge:"falling"}); setWatch(button1pressed, BTN1,{repeat:true,edge:"falling"});
// Handle button 3 being pressed
setWatch(button3pressed, BTN3,{repeat:true,edge:"falling"});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,3 +1,4 @@
0.01: First version of the Walkers Clock 0.01: First version of the Walkers Clock
0.02: Fixed screen flicker 0.02: Fixed screen flicker
0.03: Added display of GPS fix lat/lon and course 0.03: Added display of GPS fix lat/lon and course
0.04: Don't buzz for GPS fix in Quiet Mode

View File

@ -379,7 +379,9 @@ function processFix(fix) {
if (fix.fix) { if (fix.fix) {
if (!last_fix.fix) { if (!last_fix.fix) {
Bangle.buzz(); // buzz on first position if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.buzz(); // buzz on first position
}
clearActivityArea = true; clearActivityArea = true;
} }
gpsState = GPS_RUNNING; gpsState = GPS_RUNNING;

View File

@ -4,5 +4,4 @@
0.04: Works on both standard and modified firmware 0.04: Works on both standard and modified firmware
0.05: Bug fixes w.r.t. reconnection 0.05: Bug fixes w.r.t. reconnection
0.06: Update README - Release version 0.06: Update README - Release version
0.07: Respect Quiet Mode

View File

@ -187,9 +187,11 @@
//we may already be displaying a prompt, so clear it //we may already be displaying a prompt, so clear it
E.showPrompt(); E.showPrompt();
if (screentimeout) clearTimeout(screentimeout); if (screentimeout) clearTimeout(screentimeout);
Bangle.setLCDPower(true); if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.setLCDPower(true);
}
SCREENACCESS.request(); SCREENACCESS.request();
if (!buzzing){ if (!buzzing && !(require('Storage').readJSON('setting.json',1)||{}).quiet){
buzzing=true; buzzing=true;
Bangle.buzz(500).then(()=>{buzzing=false;}); Bangle.buzz(500).then(()=>{buzzing=false;});
} }

View File

@ -1 +1,2 @@
0.01: New Battery Warning! 0.01: New Battery Warning!
0.02: Respect Quiet Mode

View File

@ -39,7 +39,10 @@
.setColor(0xF800).drawString(`${E.getBattery()}%`, a.x+8+100, a.y+a.h/2); .setColor(0xF800).drawString(`${E.getBattery()}%`, a.x+8+100, a.y+a.h/2);
}, },
}); });
if (setting("buzz")) Bangle.buzz(); if (setting("buzz")
&& !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.buzz();
}
} }
Bangle.on("charging", check); Bangle.on("charging", check);

View File

@ -8,3 +8,4 @@
0.09: Add daily goal 0.09: Add daily goal
0.10: Fix daily goal, don't store settings in separate file 0.10: Fix daily goal, don't store settings in separate file
0.11: added getSteps() method for apps to retrieve step count 0.11: added getSteps() method for apps to retrieve step count
0.12: Respect Quiet Mode

View File

@ -86,7 +86,8 @@
// TODO: could save this to PEDOMFILE for lastUpdate's day? // TODO: could save this to PEDOMFILE for lastUpdate's day?
stp_today = 1; stp_today = 1;
} }
if (stp_today === setting('goal')) { if (stp_today === setting('goal')
&& !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
let b = 3, buzz = () => { let b = 3, buzz = () => {
if (b--) Bangle.buzz().then(() => setTimeout(buzz, 100)) if (b--) Bangle.buzz().then(() => setTimeout(buzz, 100))
} }

View File

@ -54,8 +54,8 @@ const APP_KEYS = [
'sortorder', 'readme', 'custom', 'interface', 'storage', 'data', 'allow_emulator', 'sortorder', 'readme', 'custom', 'interface', 'storage', 'data', 'allow_emulator',
'dependencies' 'dependencies'
]; ];
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate']; const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite'];
const DATA_KEYS = ['name', 'wildcard', 'storageFile']; const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ]; const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];

2
core

@ -1 +1 @@
Subproject commit e65920a91f9f7178c9d8ed6551ac7d9af0a5d6e1 Subproject commit 7d04c488496c873f392c5a068f72a6c75df40f70