forked from FOSS/BangleApps
Updated infoclk with multiple new features and bugfixes
parent
01d4cea940
commit
333b957c74
|
@ -1,3 +1,13 @@
|
|||
0.01: New app!
|
||||
0.02-0.07: Bug fixes
|
||||
0.08: Submitted to the app loader
|
||||
0.09: Added weather dependency
|
||||
Up and down swipes can now be configured separately
|
||||
The settings menu can now handle having shortcuts configured to apps that were removed
|
||||
Default notification app is now messageui rather than messages
|
||||
Support for dual stage unlock
|
||||
Support for a calendar bar
|
||||
The clock face is redrawn less often, hoping to save some battery
|
||||
Option to show the seconds when unlocked, even when otherwise hidden by other settings
|
||||
Broke out config loading into separate file to avoid duplicating a whole bunch of code
|
||||
Added support for fast loading
|
|
@ -16,6 +16,8 @@ There are generally a few apps that the user uses far more frequently than the o
|
|||
|
||||
## Configurability
|
||||
|
||||
Dual stage unlock allows for unlocking to be split into two stages: lighting the screen upon the actual unlock, and displaying the extra information and shortcuts after a user-configurable number of taps. This may be useful if you want to quickly glance at the clock with a wrist flick in the dark, or if you want to show the time to other people. Swipe shortcuts are active even after the first stage.
|
||||
|
||||
Displaying the seconds allows for more precise timing, but waking up the CPU to refresh the display more often consumes battery. The user can enable or disable them completely, but can also configure them to be enabled or disabled automatically based on some hueristics:
|
||||
|
||||
* They can be hidden while the display is locked, if the user expects to unlock their watch when they need the seconds.
|
||||
|
|
|
@ -1,280 +1,302 @@
|
|||
const SETTINGS_FILE = "infoclk.json";
|
||||
const FONT = require('infoclk-font.js');
|
||||
{
|
||||
const FONT = require('infoclk-font.js');
|
||||
|
||||
const storage = require("Storage");
|
||||
const locale = require("locale");
|
||||
const weather = require('weather');
|
||||
const storage = require("Storage");
|
||||
const locale = require("locale");
|
||||
const weather = require('weather');
|
||||
|
||||
let config = Object.assign({
|
||||
seconds: {
|
||||
// Displaying the seconds can reduce battery life because the CPU must wake up more often to update the display.
|
||||
// The seconds will be shown unless one of these conditions is enabled here, and currently true.
|
||||
hideLocked: false, // Hide the seconds when the display is locked.
|
||||
hideBattery: 20, // Hide the seconds when the battery is at or below a defined percentage.
|
||||
hideTime: true, // Hide the seconds when between a certain period of time. Useful for when you are sleeping and don't need the seconds
|
||||
hideStart: 2200, // The time when the seconds are hidden: first 2 digits are hours on a 24 hour clock, last 2 are minutes
|
||||
hideEnd: 700, // The time when the seconds are shown again
|
||||
hideAlways: false, // Always hide (never show) the seconds
|
||||
},
|
||||
let config = require('infoclk-config.js').getConfig();
|
||||
|
||||
date: {
|
||||
// Settings related to the display of the date
|
||||
mmdd: true, // If true, display the month first. If false, display the date first.
|
||||
separator: '-', // The character that goes between the month and date
|
||||
monthName: false, // If false, display the month as a number. If true, display the name.
|
||||
monthFullName: false, // If displaying the name: If false, display an abbreviation. If true, display a full name.
|
||||
dayFullName: false, // If false, display the day of the week's abbreviation. If true, display the full name.
|
||||
},
|
||||
// Return whether the given time (as a date object) is between start and end (as a number where the first 2 digits are hours on a 24 hour clock and the last 2 are minutes), with end time wrapping to next day if necessary
|
||||
let timeInRange = function (start, time, end) {
|
||||
|
||||
bottomLocked: {
|
||||
display: 'weather' // What to display in the bottom row when locked:
|
||||
// 'weather': The current temperature and weather description
|
||||
// 'steps': Step count
|
||||
// 'health': Step count and bpm
|
||||
// 'progress': Day progress bar
|
||||
// false: Nothing
|
||||
},
|
||||
// Convert the given date object to a time number
|
||||
let timeNumber = time.getHours() * 100 + time.getMinutes();
|
||||
|
||||
shortcuts: [
|
||||
//8 shortcuts, displayed in the bottom half of the screen (2 rows of 4 shortcuts) when unlocked
|
||||
// false = no shortcut
|
||||
// '#LAUNCHER' = open the launcher
|
||||
// any other string = name of app to open
|
||||
'stlap', 'keytimer', 'pomoplus', 'alarm',
|
||||
'rpnsci', 'calendar', 'torch', 'weather'
|
||||
],
|
||||
|
||||
swipe: {
|
||||
// 3 shortcuts to launch upon swiping:
|
||||
// false = no shortcut
|
||||
// '#LAUNCHER' = open the launcher
|
||||
// any other string = name of app to open
|
||||
up: 'messages', // Swipe up or swipe down, due to limitation of event handler
|
||||
left: '#LAUNCHER',
|
||||
right: '#LAUNCHER',
|
||||
},
|
||||
|
||||
dayProgress: {
|
||||
// A progress bar representing how far through the day you are
|
||||
enabledLocked: true, // Whether this bar is enabled when the watch is locked
|
||||
enabledUnlocked: false, // Whether the bar is enabled when the watch is unlocked
|
||||
color: [0, 0, 1], // The color of the bar
|
||||
start: 700, // The time of day that the bar starts filling
|
||||
end: 2200, // The time of day that the bar becomes full
|
||||
reset: 300 // The time of day when the progress bar resets from full to empty
|
||||
},
|
||||
|
||||
lowBattColor: {
|
||||
// The text can change color to indicate that the battery is low
|
||||
level: 20, // The percentage where this happens
|
||||
color: [1, 0, 0] // The color that the text changes to
|
||||
}
|
||||
}, storage.readJSON(SETTINGS_FILE));
|
||||
|
||||
// Return whether the given time (as a date object) is between start and end (as a number where the first 2 digits are hours on a 24 hour clock and the last 2 are minutes), with end time wrapping to next day if necessary
|
||||
function timeInRange(start, time, end) {
|
||||
|
||||
// Convert the given date object to a time number
|
||||
let timeNumber = time.getHours() * 100 + time.getMinutes();
|
||||
|
||||
// Normalize to prevent the numbers from wrapping around at midnight
|
||||
if (end <= start) {
|
||||
end += 2400;
|
||||
if (timeNumber < start) timeNumber += 2400;
|
||||
}
|
||||
|
||||
return start <= timeNumber && timeNumber <= end;
|
||||
}
|
||||
|
||||
// Return whether settings should be displayed based on the user's configuration
|
||||
function shouldDisplaySeconds(now) {
|
||||
return !(
|
||||
(config.seconds.hideAlways) ||
|
||||
(config.seconds.hideLocked && Bangle.isLocked()) ||
|
||||
(E.getBattery() <= config.seconds.hideBattery) ||
|
||||
(config.seconds.hideTime && timeInRange(config.seconds.hideStart, now, config.seconds.hideEnd))
|
||||
);
|
||||
}
|
||||
|
||||
// Determine the font size needed to fit a string of the given length widthin maxWidth number of pixels, clamped between minSize and maxSize
|
||||
function getFontSize(length, maxWidth, minSize, maxSize) {
|
||||
let size = Math.floor(maxWidth / length); //Number of pixels of width available to character
|
||||
size *= (20 / 12); //Convert to height, assuming 20 pixels of height for every 12 of width
|
||||
|
||||
// Clamp to within range
|
||||
if (size < minSize) return minSize;
|
||||
else if (size > maxSize) return maxSize;
|
||||
else return Math.floor(size);
|
||||
}
|
||||
|
||||
// Get the current day of the week according to user settings
|
||||
function getDayString(now) {
|
||||
if (config.date.dayFullName) return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][now.getDay()];
|
||||
else return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][now.getDay()];
|
||||
}
|
||||
|
||||
// Pad a number with zeros to be the given number of digits
|
||||
function pad(number, digits) {
|
||||
let result = '' + number;
|
||||
while (result.length < digits) result = '0' + result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the current date formatted according to the user settings
|
||||
function getDateString(now) {
|
||||
let month;
|
||||
if (!config.date.monthName) month = pad(now.getMonth() + 1, 2);
|
||||
else if (config.date.monthFullName) month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][now.getMonth()];
|
||||
else month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.getMonth()];
|
||||
|
||||
if (config.date.mmdd) return `${month}${config.date.separator}${pad(now.getDate(), 2)}`;
|
||||
else return `${pad(now.getDate(), 2)}${config.date.separator}${month}`;
|
||||
}
|
||||
|
||||
// Get a floating point number from 0 to 1 representing how far between the user-defined start and end points we are
|
||||
function getDayProgress(now) {
|
||||
let start = config.dayProgress.start;
|
||||
let current = now.getHours() * 100 + now.getMinutes();
|
||||
let end = config.dayProgress.end;
|
||||
let reset = config.dayProgress.reset;
|
||||
|
||||
// Normalize
|
||||
if (end <= start) end += 2400;
|
||||
if (current < start) current += 2400;
|
||||
if (reset < start) reset += 2400;
|
||||
|
||||
// Convert an hhmm number into a floating-point hours
|
||||
function toDecimalHours(time) {
|
||||
let hours = Math.floor(time / 100);
|
||||
let minutes = time % 100;
|
||||
|
||||
return hours + (minutes / 60);
|
||||
}
|
||||
|
||||
start = toDecimalHours(start);
|
||||
current = toDecimalHours(current);
|
||||
end = toDecimalHours(end);
|
||||
reset = toDecimalHours(reset);
|
||||
|
||||
let progress = (current - start) / (end - start);
|
||||
|
||||
if (progress < 0 || progress > 1) {
|
||||
if (current < reset) return 1;
|
||||
else return 0;
|
||||
} else {
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
|
||||
// Get a Gadgetbridge weather string
|
||||
function getWeatherString() {
|
||||
let current = weather.get();
|
||||
if (current) return locale.temp(current.temp - 273.15) + ', ' + current.txt;
|
||||
else return 'Weather unknown!';
|
||||
}
|
||||
|
||||
// Get a second weather row showing humidity, wind speed, and wind direction
|
||||
function getWeatherRow2() {
|
||||
let current = weather.get();
|
||||
if (current) return `${current.hum}%, ${locale.speed(current.wind)} ${current.wrose}`;
|
||||
else return 'Check Gadgetbridge';
|
||||
}
|
||||
|
||||
// Get a step string
|
||||
function getStepsString() {
|
||||
return '' + Bangle.getHealthStatus('day').steps + ' steps';
|
||||
}
|
||||
|
||||
// Get a health string including daily steps and recent bpm
|
||||
function getHealthString() {
|
||||
return `${Bangle.getHealthStatus('day').steps} steps ${Bangle.getHealthStatus('last').bpm} bpm`;
|
||||
}
|
||||
|
||||
// Set the next timeout to draw the screen
|
||||
let drawTimeout;
|
||||
function setNextDrawTimeout() {
|
||||
if (drawTimeout) {
|
||||
clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
|
||||
let time;
|
||||
let now = new Date();
|
||||
if (shouldDisplaySeconds(now)) time = 1000 - (now.getTime() % 1000);
|
||||
else time = 60000 - (now.getTime() % 60000);
|
||||
|
||||
drawTimeout = setTimeout(draw, time);
|
||||
}
|
||||
|
||||
|
||||
const DIGIT_WIDTH = 40; // How much width is allocated for each digit, 37 pixels + 3 pixels of space (which will go off of the screen on the right edge)
|
||||
const COLON_WIDTH = 19; // How much width is allocated for the colon, 16 pixels + 3 pixels of space
|
||||
const HHMM_TOP = 27; // 24 pixels for widgets + 3 pixels of space
|
||||
const DIGIT_HEIGHT = 64; // How tall the digits are
|
||||
|
||||
const SECONDS_TOP = HHMM_TOP + DIGIT_HEIGHT + 3; // The top edge of the seconds, top of hours and minutes + digit height + space
|
||||
const SECONDS_LEFT = 2 * DIGIT_WIDTH + COLON_WIDTH; // The left edge of the seconds: displayed after 2 digits and the colon
|
||||
const DATE_LETTER_HEIGHT = DIGIT_HEIGHT / 2; // Each letter of the day of week and date will be half the height of the time digits
|
||||
|
||||
const DATE_CENTER_X = SECONDS_LEFT / 2; // Day of week and date will be centered between left edge of screen and where seconds start
|
||||
const DOW_CENTER_Y = SECONDS_TOP + (DATE_LETTER_HEIGHT / 2); // Day of week will be the top row
|
||||
const DATE_CENTER_Y = DOW_CENTER_Y + DATE_LETTER_HEIGHT; // Date will be the bottom row
|
||||
const DOW_DATE_CENTER_Y = SECONDS_TOP + (DIGIT_HEIGHT / 2); // When displaying both on one row, center it
|
||||
const BOTTOM_CENTER_Y = ((SECONDS_TOP + DIGIT_HEIGHT + 3) + g.getHeight()) / 2;
|
||||
|
||||
// Draw the clock
|
||||
function draw() {
|
||||
//Prepare to draw
|
||||
g.reset()
|
||||
.setFontAlign(0, 0);
|
||||
|
||||
if (E.getBattery() <= config.lowBattColor.level) {
|
||||
let color = config.lowBattColor.color;
|
||||
g.setColor(color[0], color[1], color[2]);
|
||||
}
|
||||
now = new Date();
|
||||
|
||||
if (Bangle.isLocked()) { //When the watch is locked
|
||||
g.clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||
|
||||
//Draw the hours and minutes
|
||||
let x = 0;
|
||||
|
||||
for (let digit of locale.time(now, 1)) { //apparently this is how you get an hh:mm time string adjusting for the user's 12/24 hour preference
|
||||
if (digit != ' ') g.drawImage(FONT[digit], x, HHMM_TOP);
|
||||
if (digit == ':') x += COLON_WIDTH;
|
||||
else x += DIGIT_WIDTH;
|
||||
}
|
||||
if (storage.readJSON('setting.json')['12hour']) g.drawImage(FONT[(now.getHours() < 12) ? 'am' : 'pm'], 0, HHMM_TOP);
|
||||
|
||||
//Draw the seconds if necessary
|
||||
if (shouldDisplaySeconds(now)) {
|
||||
let tens = Math.floor(now.getSeconds() / 10);
|
||||
let ones = now.getSeconds() % 10;
|
||||
g.drawImage(FONT[tens], SECONDS_LEFT, SECONDS_TOP)
|
||||
.drawImage(FONT[ones], SECONDS_LEFT + DIGIT_WIDTH, SECONDS_TOP);
|
||||
|
||||
// Draw the day of week and date assuming the seconds are displayed
|
||||
|
||||
g.setFont('Vector', getFontSize(getDayString(now).length, SECONDS_LEFT, 6, DATE_LETTER_HEIGHT))
|
||||
.drawString(getDayString(now), DATE_CENTER_X, DOW_CENTER_Y)
|
||||
.setFont('Vector', getFontSize(getDateString(now).length, SECONDS_LEFT, 6, DATE_LETTER_HEIGHT))
|
||||
.drawString(getDateString(now), DATE_CENTER_X, DATE_CENTER_Y);
|
||||
|
||||
} else {
|
||||
//Draw the day of week and date without the seconds
|
||||
|
||||
let string = getDayString(now) + ' ' + getDateString(now);
|
||||
g.setFont('Vector', getFontSize(string.length, g.getWidth(), 6, DATE_LETTER_HEIGHT))
|
||||
.drawString(string, g.getWidth() / 2, DOW_DATE_CENTER_Y);
|
||||
// Normalize to prevent the numbers from wrapping around at midnight
|
||||
if (end <= start) {
|
||||
end += 2400;
|
||||
if (timeNumber < start) timeNumber += 2400;
|
||||
}
|
||||
|
||||
// Draw the bottom area
|
||||
if (config.bottomLocked.display == 'progress') {
|
||||
let color = config.dayProgress.color;
|
||||
return start <= timeNumber && timeNumber <= end;
|
||||
}
|
||||
|
||||
// Return whether settings should be displayed based on the user's configuration
|
||||
let shouldDisplaySeconds = function (now) {
|
||||
return (config.seconds.forceWhenUnlocked > 0 && getUnlockStage() >= config.seconds.forceWhenUnlocked) || !(
|
||||
(config.seconds.hideAlways) ||
|
||||
(config.seconds.hideLocked && getUnlockStage() < 2) ||
|
||||
(E.getBattery() <= config.seconds.hideBattery) ||
|
||||
(config.seconds.hideTime && timeInRange(config.seconds.hideStart, now, config.seconds.hideEnd))
|
||||
);
|
||||
}
|
||||
|
||||
// Determine the font size needed to fit a string of the given length widthin maxWidth number of pixels, clamped between minSize and maxSize
|
||||
let getFontSize = function (length, maxWidth, minSize, maxSize) {
|
||||
let size = Math.floor(maxWidth / length); //Number of pixels of width available to character
|
||||
size *= (20 / 12); //Convert to height, assuming 20 pixels of height for every 12 of width
|
||||
|
||||
// Clamp to within range
|
||||
if (size < minSize) return minSize;
|
||||
else if (size > maxSize) return maxSize;
|
||||
else return Math.floor(size);
|
||||
}
|
||||
|
||||
// Get the current day of the week according to user settings
|
||||
let getDayString = function (now) {
|
||||
if (config.date.dayFullName) return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][now.getDay()];
|
||||
else return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][now.getDay()];
|
||||
}
|
||||
|
||||
// Pad a number with zeros to be the given number of digits
|
||||
let pad = function (number, digits) {
|
||||
let result = '' + number;
|
||||
while (result.length < digits) result = '0' + result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the current date formatted according to the user settings
|
||||
let getDateString = function (now) {
|
||||
let month;
|
||||
if (!config.date.monthName) month = pad(now.getMonth() + 1, 2);
|
||||
else if (config.date.monthFullName) month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][now.getMonth()];
|
||||
else month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.getMonth()];
|
||||
|
||||
if (config.date.mmdd) return `${month}${config.date.separator}${pad(now.getDate(), 2)}`;
|
||||
else return `${pad(now.getDate(), 2)}${config.date.separator}${month}`;
|
||||
}
|
||||
|
||||
// Get a Gadgetbridge weather string
|
||||
let getWeatherString = function () {
|
||||
let current = weather.get();
|
||||
if (current) return locale.temp(current.temp - 273.15) + ', ' + current.txt;
|
||||
else return 'Weather unknown!';
|
||||
}
|
||||
|
||||
// Get a second weather row showing humidity, wind speed, and wind direction
|
||||
let getWeatherRow2 = function () {
|
||||
let current = weather.get();
|
||||
if (current) return `${current.hum}%, ${locale.speed(current.wind)} ${current.wrose}`;
|
||||
else return 'Check Gadgetbridge';
|
||||
}
|
||||
|
||||
// Get a step string
|
||||
let getStepsString = function () {
|
||||
return '' + Bangle.getHealthStatus('day').steps + ' steps';
|
||||
}
|
||||
|
||||
// Get a health string including daily steps and recent bpm
|
||||
let getHealthString = function () {
|
||||
return `${Bangle.getHealthStatus('day').steps} steps ${Bangle.getHealthStatus('last').bpm} bpm`;
|
||||
}
|
||||
|
||||
// Set the next timeout to draw the screen
|
||||
let drawTimeout;
|
||||
let setNextDrawTimeout = function () {
|
||||
if (drawTimeout !== undefined) {
|
||||
clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
|
||||
let time;
|
||||
let now = new Date();
|
||||
if (shouldDisplaySeconds(now)) time = 1000 - (now.getTime() % 1000);
|
||||
else time = 60000 - (now.getTime() % 60000);
|
||||
|
||||
drawTimeout = setTimeout(drawLockedSeconds, time);
|
||||
}
|
||||
|
||||
/** Return one of the following values:
|
||||
* 0: Watch is locked
|
||||
* 1: Watch is unlocked, but should still be displaying the large clock (first stage unlock)
|
||||
* 2: Watch is unlocked and should be displaying the extra info and icons (second stage unlock)
|
||||
*/
|
||||
let getUnlockStage = function () {
|
||||
if (Bangle.isLocked()) return 0;
|
||||
else if (dualStageTaps < config.dualStageUnlock) return 1;
|
||||
else return 2;
|
||||
}
|
||||
|
||||
|
||||
const DIGIT_WIDTH = 40; // How much width is allocated for each digit, 37 pixels + 3 pixels of space (which will go off of the screen on the right edge)
|
||||
const COLON_WIDTH = 19; // How much width is allocated for the colon, 16 pixels + 3 pixels of space
|
||||
const HHMM_TOP = 27; // 24 pixels for widgets + 3 pixels of space
|
||||
const DIGIT_HEIGHT = 64; // How tall the digits are
|
||||
|
||||
const SECONDS_TOP = HHMM_TOP + DIGIT_HEIGHT + 3; // The top edge of the seconds, top of hours and minutes + digit height + space
|
||||
const SECONDS_LEFT = 2 * DIGIT_WIDTH + COLON_WIDTH; // The left edge of the seconds: displayed after 2 digits and the colon
|
||||
const DATE_LETTER_HEIGHT = DIGIT_HEIGHT / 2; // Each letter of the day of week and date will be half the height of the time digits
|
||||
|
||||
const DATE_CENTER_X = SECONDS_LEFT / 2; // Day of week and date will be centered between left edge of screen and where seconds start
|
||||
const DOW_CENTER_Y = SECONDS_TOP + (DATE_LETTER_HEIGHT / 2); // Day of week will be the top row
|
||||
const DATE_CENTER_Y = DOW_CENTER_Y + DATE_LETTER_HEIGHT; // Date will be the bottom row
|
||||
const DOW_DATE_CENTER_Y = SECONDS_TOP + (DIGIT_HEIGHT / 2); // When displaying both on one row, center it
|
||||
const BOTTOM_CENTER_Y = ((SECONDS_TOP + DIGIT_HEIGHT + 3) + g.getHeight()) / 2;
|
||||
|
||||
// Draw a bar with the given top and bottom position
|
||||
let drawBar = function (x1, y1, x2, y2) {
|
||||
// Draw a day progress bar at the given position with given width and height
|
||||
let drawDayProgress = function (x1, y1, x2, y2) {
|
||||
// Get a floating point number from 0 to 1 representing how far between the user-defined start and end points we are
|
||||
let getDayProgress = function (now) {
|
||||
let start = config.bar.dayProgress.start;
|
||||
let current = now.getHours() * 100 + now.getMinutes();
|
||||
let end = config.bar.dayProgress.end;
|
||||
let reset = config.bar.dayProgress.reset;
|
||||
|
||||
// Normalize
|
||||
if (end <= start) end += 2400;
|
||||
if (current < start) current += 2400;
|
||||
if (reset < start) reset += 2400;
|
||||
|
||||
// Convert an hhmm number into a floating-point hours
|
||||
let toDecimalHours = function (time) {
|
||||
let hours = Math.floor(time / 100);
|
||||
let minutes = time % 100;
|
||||
|
||||
return hours + (minutes / 60);
|
||||
}
|
||||
|
||||
start = toDecimalHours(start);
|
||||
current = toDecimalHours(current);
|
||||
end = toDecimalHours(end);
|
||||
reset = toDecimalHours(reset);
|
||||
|
||||
let progress = (current - start) / (end - start);
|
||||
|
||||
if (progress < 0 || progress > 1) {
|
||||
if (current < reset) return 1;
|
||||
else return 0;
|
||||
} else {
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
|
||||
let color = config.bar.dayProgress.color;
|
||||
g.setColor(color[0], color[1], color[2])
|
||||
.fillRect(0, SECONDS_TOP + DIGIT_HEIGHT + 3, g.getWidth() * getDayProgress(now), g.getHeight());
|
||||
} else {
|
||||
.fillRect(x1, y1, x1 + (x2 - x1) * getDayProgress(now), y2);
|
||||
}
|
||||
|
||||
// Draw a calendar bar at the given position with given width and height
|
||||
let drawCalendar = function (x1, y1, x2, y2) {
|
||||
let calendar = storage.readJSON('android.calendar.json', true) || [];
|
||||
let now = (new Date()).getTime();
|
||||
let endTime = now + config.bar.calendar.duration * 1000;
|
||||
// Events must end in the future. Requirement to end in the future rather than start is so ongoing events display partially at the left
|
||||
// Events must start before the end of the lookahead window
|
||||
// Sort longer events first, so shorter events get placed on top. Tries to prevent the situation where an event entirely within the timespan of another gets completely covered
|
||||
calendar = calendar.filter(event => ((now < 1000 * (event.timestamp + event.durationInSeconds)) && (event.timestamp * 1000 < endTime)))
|
||||
.sort((a, b) => { return b.durationInSeconds - a.durationInSeconds; });
|
||||
|
||||
pipes = []; // Cache the pipes and draw them all at once, on top of the bar
|
||||
|
||||
for (let event of calendar) {
|
||||
// left = boundary + how far event is in the future mapped from our allowed duration to a distance in pixels, clamped to x1
|
||||
let leftUnclamped = x1 + (event.timestamp * 1000 - now) * (x2 - x1) / (config.bar.calendar.duration * 1000);
|
||||
let left = Math.max(leftUnclamped, x1);
|
||||
// right = unclamped left + how long the event is mapped from seconds to a distance in pixels, clamped to x2
|
||||
let rightUnclamped = leftUnclamped + event.durationInSeconds * (x2 - x1) / (config.bar.calendar.duration)
|
||||
let right = Math.min(rightUnclamped, x2);
|
||||
|
||||
//Draw the actual bar
|
||||
if (event.color) g.setColor("#" + (0x1000000 + Number(event.color)).toString(16).padStart(6, "0")); // Line plagiarized from the agenda app
|
||||
else {
|
||||
let color = config.bar.calendar.defaultColor;
|
||||
g.setColor(color[0], color[1], color[2]);
|
||||
}
|
||||
g.fillRect(left, y1, right, y2);
|
||||
|
||||
// Cache the pipes if necessary
|
||||
if (leftUnclamped == left) pipes.push(left);
|
||||
if (rightUnclamped == right) pipes.push(right);
|
||||
}
|
||||
|
||||
// Draw the pipes
|
||||
let color = config.bar.calendar.pipeColor;
|
||||
g.setColor(color[0], color[1], color[2]);
|
||||
for (let pipe of pipes) {
|
||||
g.fillRect(pipe - 1, y1, pipe + 1, y2);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.bar.type == 'dayProgress') {
|
||||
drawDayProgress(x1, y1, x2, y2);
|
||||
} else if (config.bar.type == 'calendar') {
|
||||
drawCalendar(x1, y1, x2, y2);
|
||||
} else if (config.bar.type == 'split') {
|
||||
let xavg = (x1 + x2) / 2;
|
||||
drawDayProgress(x1, y1, xavg, y2);
|
||||
drawCalendar(xavg, y1, x2, y2);
|
||||
g.setColor(g.theme.fg).fillRect(xavg - 1, y1, xavg + 1, y2);
|
||||
}
|
||||
}
|
||||
|
||||
// Return whether low battery behavior should be used.
|
||||
// - If the watch isn't charging and the battery is low, mark it low. Once the battery is marked low, it stays marked low for subsequent calls.
|
||||
// - When the watch sees external power, unmark the low battery.
|
||||
// This allows us to redraw the full time in the low battery color to avoid only the seconds changing, but still do it once. And it avoids alternating.
|
||||
let lowBattery = false;
|
||||
let checkLowBattery = function () {
|
||||
if (!Bangle.isCharging() && E.getBattery() <= config.lowBattColor.level) lowBattery = true;
|
||||
else if (Bangle.isCharging()) lowBattery = false;
|
||||
return lowBattery;
|
||||
}
|
||||
|
||||
let onCharging = charging => {
|
||||
checkLowBattery();
|
||||
drawLockedSeconds(true);
|
||||
}
|
||||
Bangle.on('charging', onCharging);
|
||||
|
||||
// Draw the big seconds that are displayed when the screen is locked. Call drawClock if anything else needs to be updated
|
||||
let drawLockedSeconds = function (forceDrawClock) {
|
||||
// If the watch is in the second stage of unlock, call drawClock()
|
||||
if (getUnlockStage() == 2) {
|
||||
drawClock();
|
||||
setNextDrawTimeout();
|
||||
return
|
||||
}
|
||||
|
||||
now = new Date();
|
||||
|
||||
// If we should not be displaying the seconds right now, call drawClock()
|
||||
if (!shouldDisplaySeconds(now)) {
|
||||
drawClock();
|
||||
setNextDrawTimeout();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the seconds are zero, or we are forced to raw the clock, call drawClock() but also display the seconds
|
||||
else if (now.getSeconds() == 0 || forceDrawClock) {
|
||||
drawClock();
|
||||
}
|
||||
|
||||
// If none of the prior conditions are met, draw the seconds only and do not call drawClock()
|
||||
g.reset()
|
||||
.setFontAlign(0, 0)
|
||||
.clearRect(SECONDS_LEFT, SECONDS_TOP, g.getWidth(), SECONDS_TOP + DIGIT_HEIGHT);
|
||||
|
||||
// If the battery is low, redraw the clock so it can change color
|
||||
if (checkLowBattery()) {
|
||||
let color = config.lowBattColor.color;
|
||||
g.setColor(color[0], color[1], color[2]);
|
||||
}
|
||||
|
||||
let tens = Math.floor(now.getSeconds() / 10);
|
||||
let ones = now.getSeconds() % 10;
|
||||
g.drawImage(FONT[tens], SECONDS_LEFT, SECONDS_TOP)
|
||||
.drawImage(FONT[ones], SECONDS_LEFT + DIGIT_WIDTH, SECONDS_TOP);
|
||||
|
||||
setNextDrawTimeout();
|
||||
}
|
||||
|
||||
// Draw the bottom text area
|
||||
let drawBottomText = function () {
|
||||
g.clearRect(0, SECONDS_TOP + DIGIT_HEIGHT, g.getWidth(), g.getHeight());
|
||||
|
||||
if (config.bottomLocked.display == 'progress') drawBar(0, SECONDS_TOP + DIGIT_HEIGHT + 3, g.getWidth(), g.getHeight());
|
||||
else {
|
||||
let bottomString;
|
||||
|
||||
if (config.bottomLocked.display == 'weather') bottomString = getWeatherString();
|
||||
|
@ -282,124 +304,214 @@ function draw() {
|
|||
else if (config.bottomLocked.display == 'health') bottomString = getHealthString();
|
||||
else bottomString = ' ';
|
||||
|
||||
g.setFont('Vector', getFontSize(bottomString.length, 176, 6, g.getHeight() - (SECONDS_TOP + DIGIT_HEIGHT + 3)))
|
||||
g.reset()
|
||||
.setFontAlign(0, 0)
|
||||
.setFont('Vector', getFontSize(bottomString.length, 176, 6, g.getHeight() - (SECONDS_TOP + DIGIT_HEIGHT + 3)))
|
||||
.drawString(bottomString, g.getWidth() / 2, BOTTOM_CENTER_Y);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the day progress bar between the rows if necessary
|
||||
if (config.dayProgress.enabledLocked && config.bottomLocked.display != 'progress') {
|
||||
let color = config.dayProgress.color;
|
||||
g.setColor(color[0], color[1], color[2])
|
||||
.fillRect(0, HHMM_TOP + DIGIT_HEIGHT, g.getWidth() * getDayProgress(now), SECONDS_TOP);
|
||||
// Draw the clock
|
||||
let drawClock = function (now) {
|
||||
//Prepare to draw
|
||||
g.reset()
|
||||
.setFontAlign(0, 0);
|
||||
|
||||
if (checkLowBattery()) {
|
||||
let color = config.lowBattColor.color;
|
||||
g.setColor(color[0], color[1], color[2]);
|
||||
}
|
||||
} else {
|
||||
if (now == undefined) now = new Date();
|
||||
|
||||
//If the watch is unlocked
|
||||
g.clearRect(0, 24, g.getWidth(), g.getHeight() / 2);
|
||||
rows = [
|
||||
`${getDayString(now)} ${getDateString(now)} ${locale.time(now, 1)}`,
|
||||
getHealthString(),
|
||||
getWeatherString(),
|
||||
getWeatherRow2()
|
||||
];
|
||||
if (shouldDisplaySeconds(now)) rows[0] += ':' + pad(now.getSeconds(), 2);
|
||||
if (storage.readJSON('setting.json')['12hour']) rows[0] += ((now.getHours() < 12) ? ' AM' : ' PM');
|
||||
//When the watch is locked or in first stage
|
||||
if (getUnlockStage() < 2) {
|
||||
|
||||
let maxHeight = ((g.getHeight() / 2) - HHMM_TOP) / (config.dayProgress.enabledUnlocked ? (rows.length + 1) : rows.length);
|
||||
//Draw the hours and minutes
|
||||
g.clearRect(0, 24, g.getWidth(), SECONDS_TOP);
|
||||
let x = 0;
|
||||
|
||||
let y = HHMM_TOP + maxHeight / 2;
|
||||
for (let row of rows) {
|
||||
let size = getFontSize(row.length, g.getWidth(), 6, maxHeight);
|
||||
g.setFont('Vector', size)
|
||||
.drawString(row, g.getWidth() / 2, y);
|
||||
y += maxHeight;
|
||||
for (let digit of locale.time(now, 1)) { //apparently this is how you get an hh:mm time string adjusting for the user's 12/24 hour preference
|
||||
if (digit != ' ') g.drawImage(FONT[digit], x, HHMM_TOP);
|
||||
if (digit == ':') x += COLON_WIDTH;
|
||||
else x += DIGIT_WIDTH;
|
||||
}
|
||||
if (storage.readJSON('setting.json')['12hour']) g.drawImage(FONT[(now.getHours() < 12) ? 'am' : 'pm'], 0, HHMM_TOP);
|
||||
|
||||
// If the seconds should be displayed, don't use the area when drawing the date
|
||||
if (shouldDisplaySeconds(now)) {
|
||||
g.clearRect(0, SECONDS_TOP, SECONDS_LEFT, SECONDS_TOP + DIGIT_HEIGHT)
|
||||
.setFont('Vector', getFontSize(getDayString(now).length, SECONDS_LEFT, 6, DATE_LETTER_HEIGHT))
|
||||
.drawString(getDayString(now), DATE_CENTER_X, DOW_CENTER_Y)
|
||||
.setFont('Vector', getFontSize(getDateString(now).length, SECONDS_LEFT, 6, DATE_LETTER_HEIGHT))
|
||||
.drawString(getDateString(now), DATE_CENTER_X, DATE_CENTER_Y);
|
||||
}
|
||||
// Otherwise, use the seconds area
|
||||
else {
|
||||
let string = getDayString(now) + ' ' + getDateString(now);
|
||||
g.clearRect(0, SECONDS_TOP, g.getWidth(), SECONDS_TOP + DIGIT_HEIGHT)
|
||||
.setFont('Vector', getFontSize(string.length, g.getWidth(), 6, DATE_LETTER_HEIGHT))
|
||||
.drawString(string, g.getWidth() / 2, DOW_DATE_CENTER_Y);
|
||||
}
|
||||
|
||||
drawBottomText();
|
||||
|
||||
// Draw the bar between the rows if necessary
|
||||
if (config.bar.enabledLocked && config.bottomLocked.display != 'progress') drawBar(0, HHMM_TOP + DIGIT_HEIGHT, g.getWidth(), SECONDS_TOP);
|
||||
}
|
||||
// When watch in second stage
|
||||
else {
|
||||
g.clearRect(0, 24, g.getWidth(), g.getHeight() / 2);
|
||||
rows = [
|
||||
`${getDayString(now)} ${getDateString(now)} ${locale.time(now, 1)}`,
|
||||
getHealthString(),
|
||||
getWeatherString(),
|
||||
getWeatherRow2()
|
||||
];
|
||||
if (shouldDisplaySeconds(now)) rows[0] += ':' + pad(now.getSeconds(), 2);
|
||||
if (storage.readJSON('setting.json')['12hour']) rows[0] += ((now.getHours() < 12) ? ' AM' : ' PM');
|
||||
|
||||
if (config.dayProgress.enabledUnlocked) {
|
||||
let color = config.dayProgress.color;
|
||||
g.setColor(color[0], color[1], color[2])
|
||||
.fillRect(0, y - maxHeight / 2, 176 * getDayProgress(now), y + maxHeight / 2);
|
||||
let maxHeight = ((g.getHeight() / 2) - HHMM_TOP) / (config.bar.enabledUnlocked ? (rows.length + 1) : rows.length);
|
||||
|
||||
let y = HHMM_TOP + maxHeight / 2;
|
||||
for (let row of rows) {
|
||||
let size = getFontSize(row.length, g.getWidth(), 6, maxHeight);
|
||||
g.setFont('Vector', size)
|
||||
.drawString(row, g.getWidth() / 2, y);
|
||||
y += maxHeight;
|
||||
}
|
||||
|
||||
if (config.bar.enabledUnlocked) drawBar(0, y - maxHeight / 2, g.getWidth(), y + maxHeight / 2);
|
||||
}
|
||||
}
|
||||
|
||||
setNextDrawTimeout();
|
||||
}
|
||||
|
||||
// Draw the icons. This is done separately from the main draw routine to avoid having to scale and draw a bunch of images repeatedly.
|
||||
function drawIcons() {
|
||||
g.reset().clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||
for (let i = 0; i < 8; i++) {
|
||||
let x = [0, 44, 88, 132, 0, 44, 88, 132][i];
|
||||
let y = [88, 88, 88, 88, 132, 132, 132, 132][i];
|
||||
let appId = config.shortcuts[i];
|
||||
let appInfo = storage.readJSON(appId + '.info', 1);
|
||||
if (!appInfo) continue;
|
||||
icon = storage.read(appInfo.icon);
|
||||
g.drawImage(icon, x, y, {
|
||||
scale: 0.916666666667
|
||||
});
|
||||
// Draw the icons. This is done separately from the main draw routine to avoid having to scale and draw a bunch of images repeatedly.
|
||||
let drawIcons = function () {
|
||||
g.reset().clearRect(0, 24, g.getWidth(), g.getHeight());
|
||||
for (let i = 0; i < 8; i++) {
|
||||
let x = [0, 44, 88, 132, 0, 44, 88, 132][i];
|
||||
let y = [88, 88, 88, 88, 132, 132, 132, 132][i];
|
||||
let appId = config.shortcuts[i];
|
||||
let appInfo = storage.readJSON(appId + '.info', 1);
|
||||
if (!appInfo) continue;
|
||||
icon = storage.read(appInfo.icon);
|
||||
g.drawImage(icon, x, y, {
|
||||
scale: 0.916666666667
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
weather.on("update", draw);
|
||||
Bangle.on("step", draw);
|
||||
Bangle.on('lock', locked => {
|
||||
//If the watch is unlocked, draw the icons
|
||||
if (!locked) drawIcons();
|
||||
draw();
|
||||
});
|
||||
// Draw only the bottom row if we are in first or second stage unlock, otherwise call drawClock()
|
||||
let drawBottomRowOrClock = function () {
|
||||
if (getUnlockStage() < 2) drawBottomText();
|
||||
else drawClock();
|
||||
}
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
weather.on("update", drawBottomRowOrClock);
|
||||
Bangle.on("step", drawBottomRowOrClock);
|
||||
let onLock = locked => {
|
||||
//If the watch is unlocked and the necessary number of dual stage taps have been performed, draw the shortcuts
|
||||
if (!locked && dualStageTaps >= config.dualStageUnlock) drawIcons();
|
||||
|
||||
// Launch an app given the current ID. Handles special cases:
|
||||
// false: Do nothing
|
||||
// '#LAUNCHER': Open the launcher
|
||||
// nonexistent app: Do nothing
|
||||
function launch(appId) {
|
||||
if (appId == false) return;
|
||||
else if (appId == '#LAUNCHER') {
|
||||
Bangle.buzz();
|
||||
Bangle.showLauncher();
|
||||
} else {
|
||||
let appInfo = storage.readJSON(appId + '.info', 1);
|
||||
if (appInfo) {
|
||||
// If locked, reset dual stage taps to zero
|
||||
else if (locked) dualStageTaps = 0;
|
||||
|
||||
drawLockedSeconds(true);
|
||||
};
|
||||
Bangle.on('lock', onLock);
|
||||
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// Launch an app given the current ID. Handles special cases:
|
||||
// false: Do nothing
|
||||
// '#LAUNCHER': Open the launcher
|
||||
// nonexistent app: Do nothing
|
||||
let launch = function (appId, fast) {
|
||||
if (appId == false) return;
|
||||
else if (appId == '#LAUNCHER') {
|
||||
Bangle.buzz();
|
||||
load(appInfo.src);
|
||||
Bangle.showLauncher();
|
||||
} else {
|
||||
let appInfo = storage.readJSON(appId + '.info', 1);
|
||||
if (appInfo) {
|
||||
Bangle.buzz();
|
||||
if (fast) Bangle.load(appInfo.src);
|
||||
else load(appInfo.src);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Set up touch to launch the selected app
|
||||
Bangle.on('touch', function (button, xy) {
|
||||
let x = Math.floor(xy.x / 44);
|
||||
if (x < 0) x = 0;
|
||||
else if (x > 3) x = 3;
|
||||
//Set up touch to launch the selected app, and to handle dual stage unlock
|
||||
let dualStageTaps = 0;
|
||||
|
||||
let y = Math.floor(xy.y / 44);
|
||||
if (y < 0) y = -1;
|
||||
else if (y > 3) y = 1;
|
||||
else y -= 2;
|
||||
let onTouch = function (button, xy) {
|
||||
// If only the first stage has been unlocked, increase the counter
|
||||
if (dualStageTaps < config.dualStageUnlock) {
|
||||
dualStageTaps++;
|
||||
Bangle.buzz();
|
||||
|
||||
if (y < 0) {
|
||||
Bangle.buzz();
|
||||
Bangle.showLauncher();
|
||||
} else {
|
||||
let i = 4 * y + x;
|
||||
launch(config.shortcuts[i]);
|
||||
// If we reach the unlock threshold, redraw the screen because we have now done the second unlock stage
|
||||
if (dualStageTaps == config.dualStageUnlock) {
|
||||
drawIcons();
|
||||
drawClock();
|
||||
setNextDrawTimeout(); // In case we need to replace an every minute timeout with an every second timeout
|
||||
}
|
||||
|
||||
// If we have unlocked both stages, handle a shortcut tap
|
||||
} else {
|
||||
let x = Math.floor(xy.x / 44);
|
||||
if (x < 0) x = 0;
|
||||
else if (x > 3) x = 3;
|
||||
|
||||
let y = Math.floor(xy.y / 44);
|
||||
if (y < 0) y = -1;
|
||||
else if (y > 3) y = 1;
|
||||
else y -= 2;
|
||||
|
||||
if (y < 0) {
|
||||
Bangle.buzz();
|
||||
Bangle.showLauncher();
|
||||
} else {
|
||||
let i = 4 * y + x;
|
||||
launch(config.shortcuts[i], config.fastLoad.shortcuts[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
Bangle.on('touch', onTouch);
|
||||
|
||||
//Set up swipe handler
|
||||
let onSwipe = function (lr, ud) {
|
||||
if (lr == -1) launch(config.swipe.left, config.fastLoad.swipe.left);
|
||||
else if (lr == 1) launch(config.swipe.right, config.fastLoad.swipe.right);
|
||||
else if (ud == -1) launch(config.swipe.up, config.fastLoad.swipe.up);
|
||||
else if (ud == 1) launch(config.swipe.down, config.fastLoad.swipe.down);
|
||||
};
|
||||
Bangle.on('swipe', onSwipe);
|
||||
|
||||
// If the clock starts with the watch unlocked, the first stage of unlocking is skipped
|
||||
if (!Bangle.isLocked()) {
|
||||
dualStageTaps = config.dualStageUnlock;
|
||||
drawIcons();
|
||||
}
|
||||
});
|
||||
|
||||
//Set up swipe handler
|
||||
Bangle.on('swipe', function (direction) {
|
||||
if (direction == -1) launch(config.swipe.left);
|
||||
else if (direction == 0) launch(config.swipe.up);
|
||||
else launch(config.swipe.right);
|
||||
});
|
||||
// Show launcher when middle button pressed, and enable fast loading
|
||||
Bangle.setUI({
|
||||
mode: "clock", remove: () => {
|
||||
if (drawTimeout !== undefined) {
|
||||
clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
Bangle.removeListener('charging', onCharging);
|
||||
weather.removeListener('update', drawBottomRowOrClock);
|
||||
Bangle.removeListener('step', drawBottomRowOrClock);
|
||||
Bangle.removeListener('lock', onLock);
|
||||
Bangle.removeListener('touch', onTouch);
|
||||
Bangle.removeListener('swipe', onSwipe);
|
||||
g.reset();
|
||||
}
|
||||
});
|
||||
|
||||
if (!Bangle.isLocked()) drawIcons();
|
||||
drawLockedSeconds(true);
|
||||
|
||||
draw();
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
const storage = require("Storage");
|
||||
|
||||
const SETTINGS_FILE = "infoclk.json";
|
||||
|
||||
let defaultConfig = {
|
||||
dualStageUnlock: 0,
|
||||
|
||||
seconds: {
|
||||
// Displaying the seconds can reduce battery life because the CPU must wake up more often to update the display.
|
||||
// The seconds will be shown unless one of these conditions is enabled here, and currently true.
|
||||
hideLocked: false, // Hide the seconds when the display is locked.
|
||||
hideBattery: 20, // Hide the seconds when the battery is at or below a defined percentage.
|
||||
hideTime: true, // Hide the seconds when between a certain period of time. Useful for when you are sleeping and don't need the seconds
|
||||
hideStart: 2200, // The time when the seconds are hidden: first 2 digits are hours on a 24 hour clock, last 2 are minutes
|
||||
hideEnd: 700, // The time when the seconds are shown again
|
||||
hideAlways: false, // Always hide (never show) the seconds
|
||||
forceWhenUnlocked: 1, // Force the seconds to be displayed when the watch is unlocked, no matter the other settings. 0 = never, 1 = first or second stage unlock, 2 = second stage unlock only
|
||||
},
|
||||
|
||||
date: {
|
||||
// Settings related to the display of the date
|
||||
mmdd: true, // If true, display the month first. If false, display the date first.
|
||||
separator: '-', // The character that goes between the month and date
|
||||
monthName: false, // If false, display the month as a number. If true, display the name.
|
||||
monthFullName: false, // If displaying the name: If false, display an abbreviation. If true, display a full name.
|
||||
dayFullName: false, // If false, display the day of the week's abbreviation. If true, display the full name.
|
||||
},
|
||||
|
||||
bottomLocked: {
|
||||
display: 'weather' // What to display in the bottom row when locked:
|
||||
// 'weather': The current temperature and weather description
|
||||
// 'steps': Step count
|
||||
// 'health': Step count and bpm
|
||||
// 'progress': Day progress bar
|
||||
// false: Nothing
|
||||
},
|
||||
|
||||
shortcuts: [
|
||||
//8 shortcuts, displayed in the bottom half of the screen (2 rows of 4 shortcuts) when unlocked
|
||||
// false = no shortcut
|
||||
// '#LAUNCHER' = open the launcher
|
||||
// any other string = name of app to open
|
||||
'stlap', 'keytimer', 'pomoplus', 'alarm',
|
||||
'rpnsci', 'calendar', 'torch', 'weather'
|
||||
],
|
||||
|
||||
swipe: {
|
||||
// 4 shortcuts to launch upon swiping:
|
||||
// false = no shortcut
|
||||
// '#LAUNCHER' = open the launcher
|
||||
// any other string = name of app to open
|
||||
up: 'messageui', // Swipe up or swipe down, due to limitation of event handler
|
||||
down: 'messageui',
|
||||
left: '#LAUNCHER',
|
||||
right: '#LAUNCHER',
|
||||
},
|
||||
|
||||
fastLoad: {
|
||||
shortcuts: [
|
||||
false, false, false, false,
|
||||
false, false, false, false
|
||||
],
|
||||
swipe: {
|
||||
up: false,
|
||||
down: false,
|
||||
left: false,
|
||||
right: false
|
||||
}
|
||||
},
|
||||
|
||||
bar: {
|
||||
enabledLocked: true, // Whether this bar is enabled when the watch is locked
|
||||
enabledUnlocked: false, // Whether the bar is enabled when the watch is unlocked
|
||||
type: 'split', // off = no bar, dayProgress = day progress bar, calendar = calendar bar, split = both
|
||||
|
||||
dayProgress: { // A progress bar representing how far through the day you are
|
||||
color: [0, 0, 1], // The color of the bar
|
||||
start: 700, // The time of day that the bar starts filling
|
||||
end: 2200, // The time of day that the bar becomes full
|
||||
reset: 300 // The time of day when the progress bar resets from full to empty
|
||||
},
|
||||
|
||||
calendar: {
|
||||
duration: 10800,
|
||||
pipeColor: [1, 1, 1],
|
||||
defaultColor: [0, 0, 1]
|
||||
},
|
||||
},
|
||||
|
||||
lowBattColor: {
|
||||
// The text can change color to indicate that the battery is low
|
||||
level: 20, // The percentage where this happens
|
||||
color: [1, 0, 0] // The color that the text changes to
|
||||
}
|
||||
}
|
||||
|
||||
let storedConfig = storage.readJSON(SETTINGS_FILE, true) || {};
|
||||
|
||||
// Ugly slow workaround because object.constructor doesn't exist on Bangle
|
||||
function isDictionary(object) {
|
||||
return JSON.stringify(object)[0] == '{';
|
||||
}
|
||||
|
||||
/** Merge two objects recursively. (Object.assign() cannot be used here because it is NOT recursive.)
|
||||
* Any key that is in one object but not the other will be included as is.
|
||||
* Any key that is in both objects, but whose value is not a dictionary in both objects, will have the version in overlay included.
|
||||
* Any key that whose value is a dictionary in both properties will have its result be set to a recursive call to merge.
|
||||
*/
|
||||
function merge(overlay, base) {
|
||||
let result = base;
|
||||
|
||||
for (objectKey in overlay) {
|
||||
if (!Object.keys(base).includes(objectKey)) result[objectKey] = overlay[objectKey]; // If the key isn't there, add it
|
||||
else if (isDictionary(base[objectKey]) && isDictionary(overlay[objectKey])) // If the key is a dictionary in both, do recursive call
|
||||
result[objectKey] = merge(overlay[objectKey], base[objectKey]);
|
||||
else result[objectKey] = overlay[objectKey]; // Otherwise, override
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
exports.getConfig = () => {
|
||||
return merge(storedConfig, defaultConfig);
|
||||
};
|
Binary file not shown.
Before Width: | Height: | Size: 249 B After Width: | Height: | Size: 1.9 KiB |
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"id": "infoclk",
|
||||
"name": "Informational clock",
|
||||
"version": "0.08",
|
||||
"dependencies": {"weather":"app"},
|
||||
"version": "0.09",
|
||||
"description": "A configurable clock with extra info and shortcuts when unlocked, but large time when locked",
|
||||
"readme": "README.md",
|
||||
"icon": "icon.png",
|
||||
|
@ -24,6 +23,10 @@
|
|||
"name": "infoclk-font.js",
|
||||
"url": "font.js"
|
||||
},
|
||||
{
|
||||
"name": "infoclk-config.js",
|
||||
"url": "configLoad.js"
|
||||
},
|
||||
{
|
||||
"name": "infoclk.img",
|
||||
"url": "icon.js",
|
||||
|
|
|
@ -2,71 +2,7 @@
|
|||
const SETTINGS_FILE = "infoclk.json";
|
||||
const storage = require('Storage');
|
||||
|
||||
let config = Object.assign({
|
||||
seconds: {
|
||||
// Displaying the seconds can reduce battery life because the CPU must wake up more often to update the display.
|
||||
// The seconds will be shown unless one of these conditions is enabled here, and currently true.
|
||||
hideLocked: false, // Hide the seconds when the display is locked.
|
||||
hideBattery: 20, // Hide the seconds when the battery is at or below a defined percentage.
|
||||
hideTime: true, // Hide the seconds when between a certain period of time. Useful for when you are sleeping and don't need the seconds
|
||||
hideStart: 2200, // The time when the seconds are hidden: first 2 digits are hours on a 24 hour clock, last 2 are minutes
|
||||
hideEnd: 700, // The time when the seconds are shown again
|
||||
hideAlways: false, // Always hide (never show) the seconds
|
||||
},
|
||||
|
||||
date: {
|
||||
// Settings related to the display of the date
|
||||
mmdd: true, // If true, display the month first. If false, display the date first.
|
||||
separator: '-', // The character that goes between the month and date
|
||||
monthName: false, // If false, display the month as a number. If true, display the name.
|
||||
monthFullName: false, // If displaying the name: If false, display an abbreviation. If true, display a full name.
|
||||
dayFullName: false, // If false, display the day of the week's abbreviation. If true, display the full name.
|
||||
},
|
||||
|
||||
bottomLocked: {
|
||||
display: 'weather' // What to display in the bottom row when locked:
|
||||
// 'weather': The current temperature and weather description
|
||||
// 'steps': Step count
|
||||
// 'health': Step count and bpm
|
||||
// 'progress': Day progress bar
|
||||
// false: Nothing
|
||||
},
|
||||
|
||||
shortcuts: [
|
||||
//8 shortcuts, displayed in the bottom half of the screen (2 rows of 4 shortcuts) when unlocked
|
||||
// false = no shortcut
|
||||
// '#LAUNCHER' = open the launcher
|
||||
// any other string = name of app to open
|
||||
'stlap', 'keytimer', 'pomoplus', 'alarm',
|
||||
'rpnsci', 'calendar', 'torch', 'weather'
|
||||
],
|
||||
|
||||
swipe: {
|
||||
// 3 shortcuts to launch upon swiping:
|
||||
// false = no shortcut
|
||||
// '#LAUNCHER' = open the launcher
|
||||
// any other string = name of app to open
|
||||
up: 'messages', // Swipe up or swipe down, due to limitation of event handler
|
||||
left: '#LAUNCHER',
|
||||
right: '#LAUNCHER',
|
||||
},
|
||||
|
||||
dayProgress: {
|
||||
// A progress bar representing how far through the day you are
|
||||
enabledLocked: true, // Whether this bar is enabled when the watch is locked
|
||||
enabledUnlocked: false, // Whether the bar is enabled when the watch is unlocked
|
||||
color: [0, 0, 1], // The color of the bar
|
||||
start: 700, // The time of day that the bar starts filling
|
||||
end: 2200, // The time of day that the bar becomes full
|
||||
reset: 300 // The time of day when the progress bar resets from full to empty
|
||||
},
|
||||
|
||||
lowBattColor: {
|
||||
// The text can change color to indicate that the battery is low
|
||||
level: 20, // The percentage where this happens
|
||||
color: [1, 0, 0] // The color that the text changes to
|
||||
}
|
||||
}, storage.readJSON(SETTINGS_FILE));
|
||||
let config = require('infoclk-config.js').getConfig();
|
||||
|
||||
function saveSettings() {
|
||||
storage.writeJSON(SETTINGS_FILE, config);
|
||||
|
@ -172,6 +108,18 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
'...unconditionally when unlocked': {
|
||||
value: config.seconds.forceWhenUnlocked,
|
||||
format: value => ['No', 'First or second stage', 'Second stage only'][value],
|
||||
onchange: value => {
|
||||
config.seconds.forceWhenUnlocked = value;
|
||||
saveSettings();
|
||||
},
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 1,
|
||||
wrap: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -190,7 +138,7 @@
|
|||
{ name: 'Weather', val: 'weather' },
|
||||
{ name: 'Step count', val: 'steps' },
|
||||
{ name: 'Steps + BPM', val: 'health' },
|
||||
{ name: 'Day progresss bar', val: 'progress' },
|
||||
{ name: 'Bar', val: 'progress' },
|
||||
{ name: 'Nothing', val: false }
|
||||
];
|
||||
|
||||
|
@ -213,7 +161,7 @@
|
|||
name: appInfo.name,
|
||||
val: appInfo.id
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
E.showMenu({
|
||||
'': {
|
||||
|
@ -222,128 +170,260 @@
|
|||
},
|
||||
'Top first': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[0]),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.shortcuts[0] = shortcutOptions[value].val;
|
||||
config.fastLoad.shortcuts[0] = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Top second': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[1]),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.shortcuts[1] = shortcutOptions[value].val;
|
||||
config.fastLoad.shortcuts[1] = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Top third': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[2]),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.shortcuts[2] = shortcutOptions[value].val;
|
||||
config.fastLoad.shortcuts[2] = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Top fourth': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[3]),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.shortcuts[3] = shortcutOptions[value].val;
|
||||
config.fastLoad.shortcuts[3] = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Bottom first': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[4]),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.shortcuts[4] = shortcutOptions[value].val;
|
||||
config.fastLoad.shortcuts[4] = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Bottom second': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[5]),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.shortcuts[5] = shortcutOptions[value].val;
|
||||
config.fastLoad.shortcuts[5] = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Bottom third': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[6]),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.shortcuts[6] = shortcutOptions[value].val;
|
||||
config.fastLoad.shortcuts[6] = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Bottom fourth': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.shortcuts[7]),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.shortcuts[7] = shortcutOptions[value].val;
|
||||
config.fastLoad.shortcuts[7] = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Swipe up': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.swipe.up),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.swipe.up = shortcutOptions[value].val;
|
||||
config.fastLoad.swipe.up = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Swipe down': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.swipe.down),
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.swipe.down = shortcutOptions[value].val;
|
||||
config.fastLoad.swipe.down = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Swipe left': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.swipe.left),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.swipe.left = shortcutOptions[value].val;
|
||||
config.fastLoad.swipe.left = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Swipe right': {
|
||||
value: shortcutOptions.map(item => item.val).indexOf(config.swipe.right),
|
||||
format: value => shortcutOptions[value].name,
|
||||
format: value => (value == -1) ? 'Unknown app!' : shortcutOptions[value].name,
|
||||
min: 0,
|
||||
max: shortcutOptions.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.swipe.right = shortcutOptions[value].val;
|
||||
config.fastLoad.swipe.right = false;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The menu for configuring which apps can be fast loaded
|
||||
function showFastLoadMenu() {
|
||||
E.showMenu();
|
||||
E.showAlert(/*LANG*/"WARNING! Only enable fast loading for apps that use widgets.").then(() => {
|
||||
E.showMenu({
|
||||
'': {
|
||||
'title': 'Shortcuts',
|
||||
'back': showMainMenu
|
||||
},
|
||||
'Top first': {
|
||||
value: config.fastLoad.shortcuts[0],
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.shortcuts[0] = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Top second': {
|
||||
value: config.fastLoad.shortcuts[1],
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.shortcuts[1] = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Top third': {
|
||||
value: config.fastLoad.shortcuts[2],
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.shortcuts[2] = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Top fourth': {
|
||||
value: config.fastLoad.shortcuts[3],
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.shortcuts[3] = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Bottom first': {
|
||||
value: config.fastLoad.shortcuts[4],
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.shortcuts[4] = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Bottom second': {
|
||||
value: config.fastLoad.shortcuts[5],
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.shortcuts[5] = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Bottom third': {
|
||||
value: config.fastLoad.shortcuts[6],
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.shortcuts[6] = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Bottom fourth': {
|
||||
value: config.fastLoad.shortcuts[7],
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.shortcuts[7] = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Swipe up': {
|
||||
value: config.fastLoad.swipe.up,
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.swipe.up = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Swipe down': {
|
||||
value: config.fastLoad.swipe.down,
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.swipe.down = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Swipe left': {
|
||||
value: config.fastLoad.swipe.left,
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.swipe.left = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Swipe right': {
|
||||
value: config.fastLoad.swipe.right,
|
||||
format: value => value ? 'Fast' : 'Slow',
|
||||
onchange: value => {
|
||||
config.fastLoad.swipe.right = value;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
const COLOR_OPTIONS = [
|
||||
{ name: 'Black', val: [0, 0, 0] },
|
||||
{ name: 'Blue', val: [0, 0, 1] },
|
||||
|
@ -355,11 +435,197 @@
|
|||
{ name: 'White', val: [1, 1, 1] }
|
||||
];
|
||||
|
||||
const BAR_MODE_OPTIONS = [
|
||||
{ name: 'None', val: 'off' },
|
||||
{ name: 'Day progress only', val: 'dayProgress' },
|
||||
{ name: 'Calendar only', val: 'calendar' },
|
||||
{ name: 'Split', val: 'split' }
|
||||
];
|
||||
|
||||
// Workaround for being unable to use == on arrays: convert them into strings
|
||||
function colorString(color) {
|
||||
return `${color[0]} ${color[1]} ${color[2]}`;
|
||||
}
|
||||
|
||||
//Menu to configure the bar
|
||||
function showBarMenu() {
|
||||
E.showMenu({
|
||||
'': {
|
||||
'title': 'Bar',
|
||||
'back': showMainMenu
|
||||
},
|
||||
'Enable while locked': {
|
||||
value: config.bar.enabledLocked,
|
||||
onchange: value => {
|
||||
config.bar.enableLocked = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Enable while unlocked': {
|
||||
value: config.bar.enabledUnlocked,
|
||||
onchange: value => {
|
||||
config.bar.enabledUnlocked = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Mode': {
|
||||
value: BAR_MODE_OPTIONS.map(item => item.val).indexOf(config.bar.type),
|
||||
format: value => BAR_MODE_OPTIONS[value].name,
|
||||
onchange: value => {
|
||||
config.bar.type = BAR_MODE_OPTIONS[value].val;
|
||||
saveSettings();
|
||||
},
|
||||
min: 0,
|
||||
max: BAR_MODE_OPTIONS.length - 1,
|
||||
wrap: true
|
||||
},
|
||||
'Day progress': () => {
|
||||
E.showMenu({
|
||||
'': {
|
||||
'title': 'Day progress',
|
||||
'back': showBarMenu
|
||||
},
|
||||
'Color': {
|
||||
value: COLOR_OPTIONS.map(item => colorString(item.val)).indexOf(colorString(config.bar.dayProgress.color)),
|
||||
format: value => COLOR_OPTIONS[value].name,
|
||||
min: 0,
|
||||
max: COLOR_OPTIONS.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.bar.dayProgress.color = COLOR_OPTIONS[value].val;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Start hour': {
|
||||
value: Math.floor(config.bar.dayProgress.start / 100),
|
||||
format: hourToString,
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: hour => {
|
||||
minute = config.bar.dayProgress.start % 100;
|
||||
config.bar.dayProgress.start = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Start minute': {
|
||||
value: config.bar.dayProgress.start % 100,
|
||||
min: 0,
|
||||
max: 59,
|
||||
wrap: true,
|
||||
onchange: minute => {
|
||||
hour = Math.floor(config.bar.dayProgress.start / 100);
|
||||
config.bar.dayProgress.start = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'End hour': {
|
||||
value: Math.floor(config.bar.dayProgress.end / 100),
|
||||
format: hourToString,
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: hour => {
|
||||
minute = config.bar.dayProgress.end % 100;
|
||||
config.bar.dayProgress.end = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'End minute': {
|
||||
value: config.bar.dayProgress.end % 100,
|
||||
min: 0,
|
||||
max: 59,
|
||||
wrap: true,
|
||||
onchange: minute => {
|
||||
hour = Math.floor(config.bar.dayProgress.end / 100);
|
||||
config.bar.dayProgress.end = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Reset hour': {
|
||||
value: Math.floor(config.bar.dayProgress.reset / 100),
|
||||
format: hourToString,
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: hour => {
|
||||
minute = config.bar.dayProgress.reset % 100;
|
||||
config.bar.dayProgress.reset = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Reset minute': {
|
||||
value: config.bar.dayProgress.reset % 100,
|
||||
min: 0,
|
||||
max: 59,
|
||||
wrap: true,
|
||||
onchange: minute => {
|
||||
hour = Math.floor(config.bar.dayProgress.reset / 100);
|
||||
config.bar.dayProgress.reset = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
'Calendar bar': () => {
|
||||
E.showMenu({
|
||||
'': {
|
||||
'title': 'Calendar bar',
|
||||
'back': showBarMenu
|
||||
},
|
||||
'Look ahead duration': {
|
||||
value: config.bar.calendar.duration,
|
||||
format: value => {
|
||||
let hours = value / 3600;
|
||||
let minutes = (value % 3600) / 60;
|
||||
let seconds = value % 60;
|
||||
|
||||
let result = (hours == 0) ? '' : `${hours} hr`;
|
||||
if (minutes != 0) {
|
||||
if (result == '') result = `${minutes} min`;
|
||||
else result += `, ${minutes} min`;
|
||||
}
|
||||
if (seconds != 0) {
|
||||
if (result == '') result = `${seconds} sec`;
|
||||
else result += `, ${seconds} sec`;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
onchange: value => {
|
||||
config.bar.calendar.duration = value;
|
||||
saveSettings();
|
||||
},
|
||||
min: 900,
|
||||
max: 86400,
|
||||
step: 900
|
||||
},
|
||||
'Pipe color': {
|
||||
value: COLOR_OPTIONS.map(color => colorString(color.val)).indexOf(colorString(config.bar.calendar.pipeColor)),
|
||||
format: value => COLOR_OPTIONS[value].name,
|
||||
onchange: value => {
|
||||
config.bar.calendar.pipeColor = COLOR_OPTIONS[value].val;
|
||||
saveSettings();
|
||||
},
|
||||
min: 0,
|
||||
max: COLOR_OPTIONS.length - 1,
|
||||
wrap: true
|
||||
},
|
||||
'Default color': {
|
||||
value: COLOR_OPTIONS.map(color => colorString(color.val)).indexOf(colorString(config.bar.calendar.defaultColor)),
|
||||
format: value => COLOR_OPTIONS[value].name,
|
||||
onchange: value => {
|
||||
config.bar.calendar.defaultColor = COLOR_OPTIONS[value].val;
|
||||
saveSettings();
|
||||
},
|
||||
min: 0,
|
||||
max: COLOR_OPTIONS.length - 1,
|
||||
wrap: true
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Shows the top level menu
|
||||
function showMainMenu() {
|
||||
E.showMenu({
|
||||
|
@ -367,6 +633,16 @@
|
|||
'title': 'Informational Clock',
|
||||
'back': back
|
||||
},
|
||||
'Dual stage unlock': {
|
||||
value: config.dualStageUnlock,
|
||||
format: value => (value == 0) ? "Off" : `${value} taps`,
|
||||
min: 0,
|
||||
step: 1,
|
||||
onchange: value => {
|
||||
config.dualStageUnlock = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Seconds display': showSecondsMenu,
|
||||
'Day of week format': {
|
||||
value: config.date.dayFullName,
|
||||
|
@ -433,108 +709,8 @@
|
|||
}
|
||||
},
|
||||
'Shortcuts': showShortcutMenu,
|
||||
'Day progress': () => {
|
||||
E.showMenu({
|
||||
'': {
|
||||
'title': 'Day progress',
|
||||
'back': showMainMenu
|
||||
},
|
||||
'Enable while locked': {
|
||||
value: config.dayProgress.enabledLocked,
|
||||
onchange: value => {
|
||||
config.dayProgress.enableLocked = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Enable while unlocked': {
|
||||
value: config.dayProgress.enabledUnlocked,
|
||||
onchange: value => {
|
||||
config.dayProgress.enabledUnlocked = value;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Color': {
|
||||
value: COLOR_OPTIONS.map(item => colorString(item.val)).indexOf(colorString(config.dayProgress.color)),
|
||||
format: value => COLOR_OPTIONS[value].name,
|
||||
min: 0,
|
||||
max: COLOR_OPTIONS.length - 1,
|
||||
wrap: false,
|
||||
onchange: value => {
|
||||
config.dayProgress.color = COLOR_OPTIONS[value].val;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Start hour': {
|
||||
value: Math.floor(config.dayProgress.start / 100),
|
||||
format: hourToString,
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: hour => {
|
||||
minute = config.dayProgress.start % 100;
|
||||
config.dayProgress.start = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Start minute': {
|
||||
value: config.dayProgress.start % 100,
|
||||
min: 0,
|
||||
max: 59,
|
||||
wrap: true,
|
||||
onchange: minute => {
|
||||
hour = Math.floor(config.dayProgress.start / 100);
|
||||
config.dayProgress.start = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'End hour': {
|
||||
value: Math.floor(config.dayProgress.end / 100),
|
||||
format: hourToString,
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: hour => {
|
||||
minute = config.dayProgress.end % 100;
|
||||
config.dayProgress.end = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'End minute': {
|
||||
value: config.dayProgress.end % 100,
|
||||
min: 0,
|
||||
max: 59,
|
||||
wrap: true,
|
||||
onchange: minute => {
|
||||
hour = Math.floor(config.dayProgress.end / 100);
|
||||
config.dayProgress.end = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Reset hour': {
|
||||
value: Math.floor(config.dayProgress.reset / 100),
|
||||
format: hourToString,
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: hour => {
|
||||
minute = config.dayProgress.reset % 100;
|
||||
config.dayProgress.reset = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
},
|
||||
'Reset minute': {
|
||||
value: config.dayProgress.reset % 100,
|
||||
min: 0,
|
||||
max: 59,
|
||||
wrap: true,
|
||||
onchange: minute => {
|
||||
hour = Math.floor(config.dayProgress.reset / 100);
|
||||
config.dayProgress.reset = (100 * hour) + minute;
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
'Fast load shortcuts': showFastLoadMenu,
|
||||
'Bar': showBarMenu,
|
||||
'Low battery color': () => {
|
||||
E.showMenu({
|
||||
'': {
|
||||
|
|
Loading…
Reference in New Issue