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
* Provide a proper error message in case JSON decode fails
* 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.
// 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/)
"content":"..." // if supplied, this content is loaded directly
"evaluate":true // if supplied, data isn't quoted into a String before upload
"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)
"noOverwrite":true // if supplied, this file will not be overwritten if it
// already exists
},
]
"data": [ // list of files the app writes to
{"name":"appid.data.json", // filename used in storage
"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
}, // this is mutually exclusive with using "name"

133
apps.json
View File

@ -80,7 +80,7 @@
"name": "Notifications (default)",
"shortName":"Notifications",
"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",
"tags": "widget",
"type": "notify",
@ -93,7 +93,7 @@
"name": "Fullscreen Notifications",
"shortName":"Notifications",
"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.",
"tags": "widget",
"type": "notify",
@ -139,7 +139,7 @@
{ "id": "gbridge",
"name": "Gadgetbridge",
"icon": "app.png",
"version":"0.21",
"version":"0.22",
"description": "The default notification handler for Gadgetbridge notifications from Android",
"tags": "tool,system,android,widget",
"readme": "README.md",
@ -171,7 +171,7 @@
{ "id": "setting",
"name": "Settings",
"icon": "settings.png",
"version":"0.23",
"version":"0.24",
"description": "A menu for setting up Bangle.js",
"tags": "tool,system",
"readme": "README.md",
@ -180,13 +180,16 @@
{"name":"setting.boot.js","url":"boot.js"},
{"name":"setting.img","url":"settings-icon.js","evaluate":true}
],
"data": [
{"name":"setting.json", "url":"settings.min.json","evaluate":true}
],
"sortorder" : -2
},
{ "id": "alarm",
"name": "Default Alarm",
"shortName":"Alarms",
"icon": "app.png",
"version":"0.10",
"version":"0.11",
"description": "Set and respond to alarms",
"tags": "tool,alarm,widget",
"storage": [
@ -216,24 +219,33 @@
{ "id": "slidingtext",
"name": "Sliding Clock",
"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",
"tags": "clock",
"type":"clock",
"allow_emulator":true,
"readme": "README.md",
"custom":"custom.html",
"storage": [
{"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",
"name": "Sweep Clock",
"icon": "sweepclock.png",
"version":"0.01",
"description": "Smooth sweep secondhand with single hour numeral. Use button1 to toggle the numeral font",
"version":"0.02",
"description": "Smooth sweep secondhand with single hour numeral. Use button1 to toggle the numeral font and button3 to change the colour theme",
"tags": "clock",
"type":"clock",
"allow_emulator":true,
"readme": "README.md",
"storage": [
{"name":"sweepclock.app.js","url":"sweepclock.js"},
{"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true}
@ -413,7 +425,7 @@
{ "id": "gpsrec",
"name": "GPS Recorder",
"icon": "app.png",
"version":"0.18",
"version":"0.19",
"interface": "interface.html",
"description": "Application that allows you to record a GPS track. Can run in background",
"tags": "tool,outdoors,gps,widget",
@ -438,14 +450,16 @@
"interface":"waypoints.html",
"storage": [
{"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}
],
"data": [
{"name":"waypoints.json","url":"waypoints.json"}
]
},
{ "id": "heart",
"name": "Heart Rate Recorder",
"icon": "app.png",
"version":"0.02",
"version":"0.04",
"interface": "interface.html",
"description": "Application that allows you to record your heart rate. Can run in background",
"tags": "tool,health,widget",
@ -558,7 +572,7 @@
"shortName": "Battery Warning",
"icon": "widget.png",
"readme": "README.md",
"version":"0.01",
"version":"0.02",
"description": "Show a warning when the battery runs low.",
"tags": "tool,battery",
"type":"widget",
@ -738,7 +752,7 @@
{ "id": "route",
"name": "Route Viewer",
"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",
"tags": "",
"custom": "custom.html",
@ -1072,7 +1086,7 @@
{ "id": "widpedom",
"name": "Pedometer widget",
"icon": "widget.png",
"version":"0.11",
"version":"0.12",
"description": "Daily pedometer widget",
"tags": "widget",
"type":"widget",
@ -1203,7 +1217,7 @@
{ "id": "marioclock",
"name": "Mario Clock",
"icon": "marioclock.png",
"version":"0.14",
"version":"0.15",
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
"tags": "clock,mario,retro",
"type": "clock",
@ -1701,9 +1715,9 @@
{
"id": "rclock",
"name": "Round clock with seconds, minutes and date",
"shortName":"Round Clock",
"shortName": "Round Clock",
"icon": "app.png",
"version":"0.04",
"version": "0.05",
"description": "Designed round clock with ticks for minutes and seconds and heart rate indication",
"tags": "clock",
"type": "clock",
@ -1712,6 +1726,20 @@
{"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",
"name": "QTH Locator / Maidenhead Locator System",
"shortName": "QTH Locator",
@ -2100,7 +2128,7 @@
"name": "SleepPhaseAlarm",
"shortName":"SleepPhaseAlarm",
"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.",
"tags": "alarm",
"storage": [
@ -2225,7 +2253,7 @@
"name": "Apple Notification Widget",
"shortName":"ANCS Widget",
"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",
"readme": "README.md",
"tags": "widget",
@ -2399,9 +2427,11 @@
"readme": "README.md",
"storage": [
{"name":"worldclock.app.js","url":"app.js"},
{"name":"worldclock.settings.json"},
{"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true}
]
],
"data": [
{"name":"worldclock.settings.json"}
]
},
{ "id": "digiclock",
"name": "Digital Clock Face",
@ -2593,7 +2623,7 @@
"name": "Hard Alarm",
"shortName":"HardAlarm",
"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",
"tags": "tool,alarm,widget",
"storage": [
@ -2644,8 +2674,10 @@
"readme": "README.md",
"storage": [
{"name":"breath.app.js","url":"app.js"},
{"name":"breath.settings.json","url":"settings.json"},
{"name":"breath.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"breath.settings.json","url":"settings.json"}
]
},
{ "id": "lazyclock",
@ -2742,9 +2774,11 @@
"storage": [
{"name":"gpsservice.app.js","url":"app.js"},
{"name":"gpsservice.settings.js","url":"settings.js"},
{"name":"gpsservice.settings.json","url":"settings.json"},
{"name":"gpsservice.wid.js","url":"widget.js"},
{"name":"gpsservice.img","url":"gpsservice-icon.js","evaluate":true}
],
"data": [
{"name":"gpsservice.settings.json","url":"settings.json"}
]
},
{ "id": "mclockplus",
@ -2841,16 +2875,18 @@
"storage": [
{"name":"gpssetup","url":"gpssetup.js"},
{"name":"gpssetup.settings.js","url":"settings.js"},
{"name":"gpssetup.settings.json","url":"settings.json"},
{"name":"gpssetup.app.js","url":"app.js"},
{"name":"gpssetup.img","url":"icon.js","evaluate":true}
],
"data": [
{"name":"gpssetup.settings.json","url":"settings.json"}
]
},
{ "id": "walkersclock",
"name": "Walkers Clock",
"shortName":"Walkers Clock",
"icon": "walkersclock48.png",
"version":"0.03",
"version":"0.04",
"description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference",
"type":"clock",
"tags": "clock, gps, tools, outdoors",
@ -2944,8 +2980,10 @@
"interface":"waypoints.html",
"storage": [
{"name":"waypointer.app.js","url":"app.js"},
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
{"name":"waypointer.img","url":"icon.js","evaluate":true}
],
"data": [
{"name":"waypoints.json","url":"waypoints.json"}
]
},
{ "id": "color_catalog",
@ -3004,7 +3042,7 @@
"name": "Gadgetbridge Music Controls",
"shortName":"Music Controls",
"icon": "icon.png",
"version":"0.01",
"version":"0.02",
"description": "Control the music on your Gadgetbridge-connected phone",
"tags": "tools,bluetooth,gadgetbridge,music",
"type":"app",
@ -3045,7 +3083,7 @@
{ "id": "kitchen",
"name": "Kitchen Combo",
"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",
"tags": "tool,outdoors,gps",
"readme": "README.md",
@ -3059,5 +3097,38 @@
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
{"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.09: Add per alarm auto-snooze option
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();
});
function buzz() {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
Bangle.buzz(100).then(()=>{
setTimeout(()=>{
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.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
### Buttons
* Button 1: Volume up (hold to repeat)
* Button 2: Toggle play/pause, long-press for menu
* Button 3: Volume down (hold to repeat, but remember that holding for too long resets your watch)
* Button 1: Volume up
* Button 2:
- Single press: toggle play/pause
- Double press: next song
- Triple press: previous song
- Long-press: open application launcher
* Button 3: Volume down
### Touch
* 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.20: Reduce memory usage
0.21: Fix HRM setting
0.22: Respect Quiet Mode

View File

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

View File

@ -20,3 +20,4 @@
0.16: Add gpsrec app to Settings menu
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.19: Fix memory usage issues inside track viewer app

View File

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

View File

@ -1 +1,2 @@
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) {
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
var msg = formatTime(alarm.hr);
var buzzCount = 20;
if (alarm.msg)

View File

@ -1,3 +1,5 @@
0.01: New App!
0.02: Don't overwrite existing settings on app update
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.drawWidgets();
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) {
return ".heart"+n.toString(36);
}
@ -35,7 +52,8 @@ function showMainMenu() {
updateSettings();
}
},
'View Records': viewRecords,
'View Records': ()=>{viewRecords()},
'Graph Records': ()=>{graphRecords()},
'< Back': ()=>{load();}
};
return E.showMenu(mainMenu);
@ -55,7 +73,7 @@ function viewRecords() {
}
if (!found)
menu["No Records Found"] = function(){};
menu['< Back'] = showMainMenu;
menu['< Back'] = ()=>{showMainMenu()};
return E.showMenu(menu);
}
@ -92,9 +110,188 @@ function viewRecord(n) {
viewRecord(n);
});
};
menu['< Back'] = viewRecords;
menu['< Back'] = ()=>{viewRecords()};
print(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();
// 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.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) {
//this.log_debug("Got fix - setting state to 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;
}
};

View File

@ -12,3 +12,4 @@
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.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.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.08: Don't turn on screen during Quiet Mode

View File

@ -9,7 +9,7 @@ other applications or widgets to display messages.
```JS
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)
title : string, // optional title
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});
}
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
function anim() {

View File

@ -5,3 +5,4 @@
0.05: Fix `g` corruption issue if .hide gets called twice
0.06: Adjust position of notification src text and notifications without title
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}
options.render(area);
}
if (options.on) Bangle.setLCDPower(1); // light up
if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.setLCDPower(1); // light up
}
Bangle.on("touch", exports.hide);
// Create a fake graphics to hide draw attempts
oldg = g;
@ -115,9 +116,11 @@ exports.hide = function(options) {
Bangle.removeListener("touch", exports.hide);
g.clear();
Bangle.drawWidgets();
// flipping the screen off then on often triggers a redraw - it may not!
Bangle.setLCDPower(0);
Bangle.setLCDPower(1);
if (Bangle.isLCDOn() || !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
// flipping the screen off then on often triggers a redraw - it may not!
Bangle.setLCDPower(0);
Bangle.setLCDPower(1);
}
// hack for E.showMenu/showAlert/showPrompt - can force a redraw by faking next/back
if (Bangle.btnWatches) {
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.02: Added support for locale and 12H 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
const settings = {
time: {
color: 0xD6ED17,
color: '#D6ED17',
font: 'Vector',
size: 60,
middle: screen.middle,
center: screen.center,
},
date: {
color: 0xD6ED17,
color: '#D6ED17',
font: 'Vector',
size: 15,
middle: screen.height-17, // at bottom of screen
center: screen.center,
},
circle: {
colormin: 0x606060,
colorsec: 0x656565,
colormin: '#ffffff',
colorsec: '#ffffff',
width: 10,
middle: screen.middle,
center: screen.center,
height: screen.height
},
hr: {
color: 0x333333,
color: '#333333',
size: 10,
x: screen.center,
y: screen.middle + 45
@ -66,18 +66,6 @@
};
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);
rad = (settings.circle.height / 2) - 40;
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
@ -86,7 +74,19 @@
//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)
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 () {
@ -107,15 +107,13 @@
first = false;
}
// Reset seconds
// Reset
if (seconds == 59) {
g.setColor('#000000');
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2) - 40);
}
// Reset minutes
if (minutes == 59 && seconds == 59) {
g.setColor('#000000');
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2) - 20);
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2));
for (count = 0; count <= minutes; count++) {
drawMinArc(count, settings.circle.colormin);
}
}
//Get date as a string

View File

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

View File

@ -26,3 +26,4 @@
Add whitelist option (fix #78)
0.22: Move HID to BLE menu
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?
* **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
* **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)
* **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.
@ -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
* **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.
## 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);
if (!settings) return;
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.whitelist) NRF.on('connect', function(addr) { if (!settings.whitelist.includes(addr)) NRF.disconnect(); });
delete settings;

View File

@ -6,12 +6,17 @@ let settings;
function updateSettings() {
//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);
if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this always exists in this file
}
function updateOptions() {
updateSettings();
Bangle.setOptions(settings.options)
if (settings.quiet) {
Bangle.setOptions(settings.qmOptions)
}
}
function gToInternal(g) {
@ -29,6 +34,7 @@ function resetSettings() {
ble: true, // Bluetooth enabled by default
blerepl: true, // Is REPL on Bluetooth - can Espruino IDE be used?
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
vibrate: true, // Vibration enabled by default. App must support
beep: "vib", // Beep enabled by default. App must support
@ -48,13 +54,19 @@ function resetSettings() {
twistThreshold: 819.2,
twistMaxY: -800,
twistTimeout: 1000
}
},
// Quiet Mode options:
// we only set these if we want to override the default value
// qmOptions: {},
// qmBrightness: undefined,
// qmTimeout: undefined,
};
updateSettings();
}
settings = storage.readJSON('setting.json', 1);
if (!settings) resetSettings();
if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this always exists in here
const boolFormat = v => v ? "On" : "Off";
@ -97,6 +109,7 @@ function showMainMenu() {
}
}
},
"Quiet Mode": ()=>showQuietModeMenu(),
'Locale': ()=>showLocaleMenu(),
'Select Clock': ()=>showClockMenu(),
'Set Time': ()=>showSetTimeMenu(),
@ -224,7 +237,9 @@ function showLCDMenu() {
onchange: v => {
settings.brightness = v || 1;
updateSettings();
Bangle.setLCDBrightness(settings.brightness);
if (!(settings.quiet && "qmBrightness" in settings)) {
Bangle.setLCDBrightness(settings.brightness);
}
}
},
'LCD Timeout': {
@ -235,7 +250,9 @@ function showLCDMenu() {
onchange: v => {
settings.timeout = 0 | v;
updateSettings();
Bangle.setLCDTimeout(settings.timeout);
if (!(settings.quiet && "qmTimeout" in settings)) {
Bangle.setLCDTimeout(settings.timeout);
}
}
},
'Wake on BTN1': {
@ -319,6 +336,104 @@ function showLCDMenu() {
}
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() {
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.02: Respect Quiet Mode

View File

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

View File

@ -1 +1,2 @@
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
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)
## Usage
### Button 1
Use Button 1 (the top right button) to change the language
| English | English (Traditional) | French | Japanese (Romanji) |
| ---- | ---- | ---- | ---- |
| ![](./format-01.jpg) | ![](format-02.jpg) | ![](format-03.jpg) |![](format-04.jpg) |
### 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
[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
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
* Sliding text clock inspired by the Pebble
* clock with the same name
*/
* Adrian Kirk 2021-02
* Sliding text clock inspired by the Pebble
* 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 Responsible for shifting text around the screen
*
* This is a object that initializes itself with a position and
* 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
* at the selected frame rate and speed
*/
* Class Responsible for shifting text around the screen
*
* This is a object that initializes itself with a position and
* 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
* at the selected frame rate and speed
*/
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.tgt_x = x;
this.init_x = x;
@ -29,29 +110,44 @@ class ShiftText {
this.speed_x = Math.abs(speed_x);
this.speed_y = Math.abs(speed_y);
this.freq_millis = freq_millis;
this.colour = color;
this.color = color;
this.bg_color = bg_color;
this.finished_callback=null;
this.timeoutId = null;
}
setColor(color){
this.color = color;
}
setBgColor(bg_color){
this.bg_color = bg_color;
}
reset(){
//console.log("reset");
this.hide();
this.x = this.init_x;
this.y = this.init_y;
this.txt = this.init_txt;
this.show();
if(this.timeoutId != null){
clearTimeout(this.timeoutId);
clearTimeout(this.timeoutId);
}
}
show() {
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);
}
hide(){
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.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){
this.txt = txt;
@ -92,15 +188,15 @@ class ShiftText {
this.finished_callback = finished_callback;
}
/**
* private internal method for directing the text move.
* It will see how far away we are from the target coords
* and move towards the target at the defined speed.
*/
* private internal method for directing the text move.
* It will see how far away we are from the target coords
* and move towards the target at the defined speed.
*/
_doMove(){
this.hide();
// move closer to the target in the x direction
diff_x = this.tgt_x - this.x;
finished_x = false;
var diff_x = this.tgt_x - this.x;
var finished_x = false;
if(Math.abs(diff_x) <= this.speed_x){
this.x = this.tgt_x;
finished_x = true;
@ -112,8 +208,8 @@ class ShiftText {
}
}
// move closer to the target in the y direction
diff_y = this.tgt_y - this.y;
finished_y = false;
var diff_y = this.tgt_y - this.y;
var finished_y = false;
if(Math.abs(diff_y) <= this.speed_y){
this.y = this.tgt_y;
finished_y = true;
@ -126,235 +222,90 @@ class ShiftText {
}
this.show();
this.timeoutId = null;
finished = finished_x & finished_y;
var finished = finished_x & finished_y;
if(!finished){
this.timeoutId = setTimeout(this._doMove.bind(this), this.freq_millis);
} else if(this.finished_callback != null){
//console.log("finished - calling:" + this.finished_callback);
this.finished_callback.call();
this.finished_callback = null;
}
}
}
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(){"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])
const CLOCK_TEXT_SPEED_X = 10;
// a list of display rows
let row_displays = [
new ShiftText(240,50,'',"Vector",40,CLOCK_TEXT_SPEED_X,1,10,main_color(),bg_color()),
new ShiftText(240,90,'',"Vector",30,CLOCK_TEXT_SPEED_X,1,10,other_color(),bg_color()),
new ShiftText(240,120,'',"Vector",30,CLOCK_TEXT_SPEED_X,1,10,other_color(),bg_color()),
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())
];
// a list of the formatters to cycle through
let date_formatters = [
new EnglishDateFormatter(),
new FrenchDateFormatter(),
new JapaneseDateFormatter()
];
function nextColorTheme(){
//console.log("next color theme");
color_scheme_index += 1;
if(color_scheme_index >= row_displays.length){
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
let date_formatter_idx = 0;
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(){
date_formatter_idx += 1;
if(date_formatter_idx >= date_formatters.length){
@ -364,61 +315,204 @@ function changeFormatter(){
date_formatter = date_formatters[date_formatter_idx];
reset_clock();
draw_clock();
// now announce the formatter by name
format_name_display.setTextYPosition(date_formatter.name(),-10);
format_name_display.moveToY(15);
// and then move back
format_name_display.onFinished(
function(){
format_name_display.moveToY(-10);
command_stack_high_priority.unshift(
function() {
//console.log("move in new:" + txt);
// first select the top or bottom to display the formatter name
// We choose the first spare row without text
var format_name_display = row_displays[row_displays.length - 1];
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(){
//console.log("reset_clock");
var i;
for (i = 0; i < row_displays.length; i++) {
for (var i = 0; i < row_displays.length; i++) {
row_displays[i].speed_x = CLOCK_TEXT_SPEED_X;
row_displays[i].reset();
}
reset_commands();
}
let last_draw_time = null;
const next_minute_boundary_secs = 7.5;
function draw_clock(){
//console.log("draw_clock");
date = new Date();
rows = date_formatter.formatDate(date);
var i;
for (i = 0; i < rows.length; i++) {
var date = new Date();
if(last_draw_time != null &&
date.getTime() - last_draw_time.getTime() < next_minute_boundary_secs * 1000 &&
has_commands() ){
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];
txt = rows[i];
var txt = rows[i];
//console.log(i + "->" + 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
for (j = i; j < row_displays.length; j++) {
for (var j = i; j < row_displays.length; j++) {
display = row_displays[j];
//console.log(i + "->''(empty)");
display_row(display,'');
}
next_command();
//console.log(date);
}
function display_row(display,txt){
if(display.txt == ''){
display.setTextXPosition(txt,240);
display.moveToX(20);
} else if(txt != display.txt){
display.moveToX(-100);
display.onFinished(
function(){
display.setTextXPosition(txt,240);
display.moveToX(20);
}
if(display == null) {
console.log("no display for text:" + txt)
return;
}
if(display.txt == null || display.txt == ''){
if(txt != '') {
command_stack_high_priority.unshift(
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 {
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
let intervalRef = null;
@ -430,9 +524,9 @@ function clearTimers(){
}
function startTimers(){
let date = new Date();
let secs = date.getSeconds();
let nextMinuteStart = 60 - secs;
var date = new Date();
var secs = date.getSeconds();
var nextMinuteStart = 60 - secs;
//console.log("scheduling clock draw in " + nextMinuteStart + " seconds");
setTimeout(scheduleDrawClock,nextMinuteStart * 1000);
draw_clock();
@ -457,6 +551,7 @@ Bangle.on('lcdPower', (on) => {
clearTimers();
}
});
Bangle.on('faceUp',function(up){
//console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn());
if (up && !Bangle.isLCDOn()) {
@ -467,9 +562,17 @@ Bangle.on('faceUp',function(up){
});
g.clear();
load_settings();
Bangle.loadWidgets();
Bangle.drawWidgets();
startTimers();
// Show launcher when middle button pressed
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.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
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
@ -14,8 +31,8 @@ For further details of design and working please visit [The Project Page](https:
## 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
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_y = g.getHeight()/2;
const screen_center_y = 10 + g.getHeight()/2;
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 {
/**
* Pure virtual class for all Hand classes to extend.
@ -28,16 +73,12 @@ class ThinHand extends Hand {
length,
tolerance,
draw_test,
red,
green,
blue){
color_theme){
super();
this.centerX = centerX;
this.centerY = centerY;
this.length = length;
this.red = red;
this.green = green;
this.blue = blue;
this.color_theme = color_theme;
// The last x and y coordinates (not the centre) of the last draw
this.last_x = centerX;
this.last_y = centerY;
@ -60,13 +101,14 @@ class ThinHand extends Hand {
// and then call the predicate to see if a redraw is needed
this.draw_test(this.angle,this.last_draw_time) ){
// 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);
// 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);
y2 = this.centerY - this.length*Math.cos(angle);
g.setColor(this.red,this.green,this.blue);
g.drawLine(this.centerX, this.centerY, x2, y2);
// and store the last draw details for the next call
this.last_x = x2;
@ -90,18 +132,14 @@ class ThickHand extends Hand {
length,
tolerance,
draw_test,
red,
green,
blue,
color_theme,
base_height,
thickness){
super();
this.centerX = centerX;
this.centerY = centerY;
this.length = length;
this.red = red;
this.green = green;
this.blue = blue;
this.color_theme = color_theme;
this.thickness = thickness;
this.base_height = base_height;
// 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
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) ){
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,
this.last_y1,
this.last_x2,
@ -142,7 +181,6 @@ class ThickHand extends Hand {
this.last_x4,
this.last_y4
]);
g.setColor(this.red,this.green,this.blue);
// bottom left
x1 = this.centerX +
this.vertex_radius_base*Math.sin(angle - this.delta_base);
@ -157,7 +195,8 @@ class ThickHand extends Hand {
// top left
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);
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,
x2,y2,
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
let seconds_hand = new ThinHand(screen_center_x,
screen_center_y,
100,
95,
0,
(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,
// when the second hand is ontop or slighly overtaking
// or when a force_redraw is called
@ -201,7 +240,7 @@ let minutes_hand = new ThinHand(screen_center_x,
80,
2*Math.PI/250,
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
// overlaps from its behind andle coverage to its ahead angle coverage.
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,
2*Math.PI/600,
hour_hand_redraw,
1.0,1.0,1.0,
"hour_hand",
5,
4);
function draw_clock(){
date = new Date();
draw_background();
draw_hour_digit(date);
draw_seconds(date);
draw_mins(date);
@ -242,7 +282,7 @@ function draw_mins(date,seconds_angle){
mins_angle = 2*Math.PI*mins_frac;
redraw = minutes_hand.moveTo(mins_angle);
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;
redraw = hours_hand.moveTo(hours_angle);
if(redraw){
console.log("redraw hours");
//console.log("redraw hours");
}
}
@ -275,6 +315,18 @@ class NumeralFont {
* method to draw text at the required coordinates
*/
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{
@ -314,6 +366,7 @@ class CopasetFont extends NumeralFont{
g.setFontCopasetic40x58Numeric();
g.drawString(hour_txt,x,y);
}
getName(){return "Copaset";}
}
@ -358,6 +411,7 @@ class RomanNumeralFont extends NumeralFont{
g.setFont("Vector",40);
g.drawString(hour_txt,x,y);
}
getName(){return "Roman";}
}
// The problem with the trig inverse functions on
@ -419,11 +473,12 @@ class HourScriber {
drawHour(hours){
changed = false;
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_hour_x,
this.curr_hour_y);
console.log("erasing old hour");
//console.log("erasing old hour");
hours_frac = hours / 12;
angle = 2*Math.PI*hours_frac;
dimensions = this.numeral_font.getDimensions(hours);
@ -477,15 +532,16 @@ class HourScriber {
}
if(changed ||
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.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;
/**
* predicate for deciding when the digit has to be redrawn
@ -540,7 +596,93 @@ function draw_hour_digit(date){
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
let intervalRef = null;
@ -593,6 +735,7 @@ Bangle.on('faceUp',function(up){
});
g.clear();
load_settings();
Bangle.loadWidgets();
Bangle.drawWidgets();
startTimers();
@ -602,8 +745,17 @@ setWatch(Bangle.showLauncher, BTN2,{repeat:false,edge:"falling"});
function button1pressed(){
next_font();
save_settings();
}
function button3pressed(){
next_colorscheme();
save_settings();
}
// Handle button 1 being pressed
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.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 (!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;
}
gpsState = GPS_RUNNING;

View File

@ -4,5 +4,4 @@
0.04: Works on both standard and modified firmware
0.05: Bug fixes w.r.t. reconnection
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
E.showPrompt();
if (screentimeout) clearTimeout(screentimeout);
Bangle.setLCDPower(true);
if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.setLCDPower(true);
}
SCREENACCESS.request();
if (!buzzing){
if (!buzzing && !(require('Storage').readJSON('setting.json',1)||{}).quiet){
buzzing=true;
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);
},
});
if (setting("buzz")) Bangle.buzz();
if (setting("buzz")
&& !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
Bangle.buzz();
}
}
Bangle.on("charging", check);

View File

@ -8,3 +8,4 @@
0.09: Add daily goal
0.10: Fix daily goal, don't store settings in separate file
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?
stp_today = 1;
}
if (stp_today === setting('goal')) {
if (stp_today === setting('goal')
&& !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
let b = 3, buzz = () => {
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',
'dependencies'
];
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate'];
const DATA_KEYS = ['name', 'wildcard', 'storageFile'];
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite'];
const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];

2
core

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