Update to V0.08

master
stweedo 2024-09-05 23:42:39 -05:00 committed by GitHub
parent a47fe7f944
commit 078e11173c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 314 additions and 268 deletions

View File

@ -5,3 +5,10 @@
0.05: Fixes step count not resetting after a new day starts 0.05: Fixes step count not resetting after a new day starts
0.06: Added clockbackground app functionality 0.06: Added clockbackground app functionality
0.07: Allow custom backgrounds per boxclk config and from the clockbg module 0.07: Allow custom backgrounds per boxclk config and from the clockbg module
0.08: Improves performance, responsiveness, and bug fixes
- [+] Added box size caching to reduce calculations
- [+] Improved step count with real-time updates
- [+] Improved battery level update logic to reduce unnecessary refreshes
- [+] Fixed optional seconds not displaying in time
- [+] Fixed drag handler by adding E.stopEventPropagation()
- [+] General code optimization and cleanup

View File

@ -1,59 +1,115 @@
{ {
/** // 1. Module dependencies and initial configurations
* ---------------------------------------------------------------
* 1. Module dependencies and initial configurations
* ---------------------------------------------------------------
*/
let background = require("clockbg"); let background = require("clockbg");
let storage = require("Storage"); let storage = require("Storage");
let locale = require("locale"); let locale = require("locale");
let widgets = require("widget_utils"); let widgets = require("widget_utils");
let date = new Date();
let bgImage; let bgImage;
let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0; let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0;
let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json'; let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json';
// Add a condition to check if the file exists, if it does not, default to 'boxclk.json'
if (!storage.read(fileName)) { if (!storage.read(fileName)) {
fileName = 'boxclk.json'; fileName = 'boxclk.json';
} }
let boxesConfig = storage.readJSON(fileName, 1) || {}; let boxesConfig = storage.readJSON(fileName, 1) || {};
let boxes = {}; let boxes = {};
let boxPos = {}; let isDragging = false;
let isDragging = {};
let wasDragging = {};
let doubleTapTimer = null; let doubleTapTimer = null;
let g_setColor; let g_setColor;
let saveIcon = require("heatshrink").decompress(atob("mEwwkEogA/AHdP/4AK+gWVDBQWNAAIuVGBAIB+UQdhMfGBAHBCxUAgIXHIwPyCxQwEJAgXB+MAl/zBwQGBn8ggQjBGAQXG+EA/4XI/8gBIQXTGAMPC6n/C6HzkREBC6YACC6QAFC57aHCYIXOOgLsEn4XPABIX/C6vykQAEl6/WgCQBC5imFAAT2BC5gCBI4oUCC5x0IC/4X/C4K8Bl4XJ+TCCC4wKBABkvC4tEEoMQCxcBB4IWEC4XyDBUBFwIXGJAIAOIwowDABoWGGB4uHDBwWJAH4AzA")); let saveIcon = require("heatshrink").decompress(atob("mEwwkEogA/AHdP/4AK+gWVDBQWNAAIuVGBAIB+UQdhMfGBAHBCxUAgIXHIwPyCxQwEJAgXB+MAl/zBwQGBn8ggQjBGAQXG+EA/4XI/8gBIQXTGAMPC6n/C6HzkREBC6YACC6QAFC57aHCYIXOOgLsEn4XPABIX/C6vykQAEl6/WgCQBC5imFAAT2BC5gCBI4oUCC5x0IC/4X/C4K8Bl4XJ+TCCC4wKBABkvC4tEEoMQCxcBB4IWEC4XyDBUBFwIXGJAIAOIwowDABoWGGB4uHDBwWJAH4AzA"));
/** // 2. Graphical and visual configurations
* ---------------------------------------------------------------
* 2. Graphical and visual configurations
* ---------------------------------------------------------------
*/
let w = g.getWidth(); let w = g.getWidth();
let h = g.getHeight(); let h = g.getHeight();
let totalWidth, totalHeight;
let drawTimeout; let drawTimeout;
/** // 3. Touch and drag handlers
* --------------------------------------------------------------- let touchHandler = function(zone, e) {
* 3. Touchscreen Handlers let boxTouched = false;
* --------------------------------------------------------------- let touchedBox = null;
*/
let touchHandler; boxKeys.forEach((boxKey) => {
let dragHandler; if (touchInText(e, boxes[boxKey], boxKey)) {
let movementDistance = 0; touchedBox = boxKey;
boxTouched = true;
}
});
/** if (boxTouched) {
* --------------------------------------------------------------- // Toggle the selected state of the touched box
* 4. Font loading function boxes[touchedBox].selected = !boxes[touchedBox].selected;
* ---------------------------------------------------------------
*/
// Update isDragging based on whether any box is selected
isDragging = boxKeys.some(key => boxes[key].selected);
if (isDragging) {
widgets.hide();
// Stop propagation of the touch event to prevent other handlers
E.stopEventPropagation();
} else {
widgets.show();
widgets.swipeOn();
// Call updateBoxData when transitioning from dragging to not dragging
updateBoxData();
}
} else {
// If tapped outside any box, deselect all boxes
deselectAllBoxes();
}
// Always redraw after a touch event
draw();
// Handle double tap for saving
if (!boxTouched && !isDragging) {
if (doubleTapTimer) {
clearTimeout(doubleTapTimer);
doubleTapTimer = null;
Object.keys(boxes).forEach((boxKey) => {
boxesConfig[boxKey].boxPos.x = (boxes[boxKey].pos.x / w).toFixed(3);
boxesConfig[boxKey].boxPos.y = (boxes[boxKey].pos.y / h).toFixed(3);
});
storage.write(fileName, JSON.stringify(boxesConfig));
displaySaveIcon();
return;
}
doubleTapTimer = setTimeout(() => {
doubleTapTimer = null;
}, 500);
}
};
let dragHandler = function(e) {
// Check if any box is being dragged
if (!isDragging) return;
// Stop propagation of the drag event to prevent other handlers
E.stopEventPropagation();
boxKeys.forEach(key => {
if (boxes[key].selected) {
let boxItem = boxes[key];
if (!boxItem.cachedSize) {
calcBoxSize(boxItem);
}
let newX = boxItem.pos.x + e.dx;
let newY = boxItem.pos.y + e.dy;
if (newX - boxItem.cachedSize.width / 2 >= 0 &&
newX + boxItem.cachedSize.width / 2 <= w &&
newY - boxItem.cachedSize.height / 2 >= 0 &&
newY + boxItem.cachedSize.height / 2 <= h) {
boxItem.pos.x = newX;
boxItem.pos.y = newY;
}
}
});
draw();
};
// 4. Font loading function
let loadCustomFont = function() { let loadCustomFont = function() {
Graphics.prototype.setFontBrunoAce = function() { Graphics.prototype.setFontBrunoAce = function() {
// Actual height 23 (24 - 2) // Actual height 23 (24 - 2)
@ -61,50 +117,45 @@
E.toString(require('heatshrink').decompress(atob('ABMHwADBh4DKg4bKgIPDAYUfAYV/AYX/AQMD/gmC+ADBn/AByE/GIU8AYUwLxcfAYX/8AnB//4JIP/FgMP4F+CQQBBjwJBFYRbBAd43DHoJpBh/g/xPEK4ZfDgEEORKDDAY8////wADLfZrTCgITBnhEBAYJMBAYMPw4DCM4QDjhwDCjwDBn0+AYMf/gDBh/4AYMH+ADBLpc4ToK/NGYZfnAYcfL4U/x5fBW4LvB/7vC+LvBgHAsBfIn76Cn4WBcYQDFEgJ+CQQYDyH4L/BAZbHLNYjjCAZc8ngDunycBZ4KkBa4KwBnEHY4UB+BfMgf/ZgMH/4XBc4cf4F/gE+ZgRjwAYcfj5jBM4U4M4RQBM4UA8BjIngDFEYJ8BAYUDAYQvCM4ZxBC4V+AYQvBnkBQ4M8gabBJQPAI4WAAYM/GYQaBAYJKCnqyCn5OCn4aBAYIaBAYJPCU4IABnBhIuDXCFAMD+Z/BY4IDBQwOPwEfv6TDAYUPAcwrDAYQ7BAYY/BI4cD8bLCK4RfEAA0BRYTeDcwIrFn0Pw43Bg4DugYDBjxBBU4SvDMYMH/5QBgP/LAQAP8EHN4UPwADHB4YAHA'))), E.toString(require('heatshrink').decompress(atob('ABMHwADBh4DKg4bKgIPDAYUfAYV/AYX/AQMD/gmC+ADBn/AByE/GIU8AYUwLxcfAYX/8AnB//4JIP/FgMP4F+CQQBBjwJBFYRbBAd43DHoJpBh/g/xPEK4ZfDgEEORKDDAY8////wADLfZrTCgITBnhEBAYJMBAYMPw4DCM4QDjhwDCjwDBn0+AYMf/gDBh/4AYMH+ADBLpc4ToK/NGYZfnAYcfL4U/x5fBW4LvB/7vC+LvBgHAsBfIn76Cn4WBcYQDFEgJ+CQQYDyH4L/BAZbHLNYjjCAZc8ngDunycBZ4KkBa4KwBnEHY4UB+BfMgf/ZgMH/4XBc4cf4F/gE+ZgRjwAYcfj5jBM4U4M4RQBM4UA8BjIngDFEYJ8BAYUDAYQvCM4ZxBC4V+AYQvBnkBQ4M8gabBJQPAI4WAAYM/GYQaBAYJKCnqyCn5OCn4aBAYIaBAYJPCU4IABnBhIuDXCFAMD+Z/BY4IDBQwOPwEfv6TDAYUPAcwrDAYQ7BAYY/BI4cD8bLCK4RfEAA0BRYTeDcwIrFn0Pw43Bg4DugYDBjxBBU4SvDMYMH/5QBgP/LAQAP8EHN4UPwADHB4YAHA'))),
46, 46,
atob("CBEdChgYGhgaGBsaCQ=="), atob("CBEdChgYGhgaGBsaCQ=="),
32|65536 32 | 65536
); );
}; };
}; };
/** // 5. Initial settings of boxes and their positions
* --------------------------------------------------------------- let isBool = (val, defaultVal) => val !== undefined ? Boolean(val) : defaultVal;
* 5. Initial settings of boxes and their positions
* ---------------------------------------------------------------
*/
for (let key in boxesConfig) { for (let key in boxesConfig) {
if (key === 'bg' && boxesConfig[key].img) { if (key === 'bg' && boxesConfig[key].img) {
bgImage = storage.read(boxesConfig[key].img); bgImage = storage.read(boxesConfig[key].img);
} else if (key !== 'selectedConfig') { } else if (key !== 'selectedConfig') {
boxes[key] = Object.assign({}, boxesConfig[key]); boxes[key] = Object.assign({}, boxesConfig[key]);
// Set default values for short, shortMonth, and disableSuffix
boxes[key].short = isBool(boxes[key].short, true);
boxes[key].shortMonth = isBool(boxes[key].shortMonth, true);
boxes[key].disableSuffix = isBool(boxes[key].disableSuffix, false);
// Set box position
boxes[key].pos = {
x: w * boxes[key].boxPos.x,
y: h * boxes[key].boxPos.y
};
// Cache box size
boxes[key].cachedSize = null;
} }
} }
let boxKeys = Object.keys(boxes); let boxKeys = Object.keys(boxes);
boxKeys.forEach((key) => { // 6. Text and drawing functions
let boxConfig = boxes[key];
boxPos[key] = {
x: w * boxConfig.boxPos.x,
y: h * boxConfig.boxPos.y
};
isDragging[key] = false;
wasDragging[key] = false;
});
/** /*
* --------------------------------------------------------------- Overwrite the setColor function to allow the
* 6. Text and drawing functions use of (x) in g.theme.x as a string
* --------------------------------------------------------------- in your JSON config ("fg", "bg", "fg2", "bg2", "fgH", "bgH")
*/ */
// Overwrite the setColor function to allow the
// use of (x) in g.theme.x as a string
// in your JSON config ("fg", "bg", "fg2", "bg2", "fgH", "bgH")
let modSetColor = function() { let modSetColor = function() {
// Save the original setColor function
g_setColor = g.setColor; g_setColor = g.setColor;
// Overwrite setColor with the new function
g.setColor = function(color) { g.setColor = function(color) {
if (typeof color === "string" && color in g.theme) { if (typeof color === "string" && color in g.theme) {
g_setColor.call(g, g.theme[color]); g_setColor.call(g, g.theme[color]);
@ -115,7 +166,6 @@
}; };
let restoreSetColor = function() { let restoreSetColor = function() {
// Restore the original setColor function
if (g_setColor) { if (g_setColor) {
g.setColor = g_setColor; g.setColor = g_setColor;
} }
@ -139,25 +189,6 @@
} }
}; };
let calcBoxSize = function(boxItem) {
g.reset();
g.setFontAlign(0,0);
g.setFont(boxItem.font, boxItem.fontSize);
let strWidth = g.stringWidth(boxItem.string) + 2 * boxItem.outline;
let fontHeight = g.getFontHeight() + 2 * boxItem.outline;
totalWidth = strWidth + 2 * boxItem.xPadding;
totalHeight = fontHeight + 2 * boxItem.yPadding;
};
let calcBoxPos = function(boxKey) {
return {
x1: boxPos[boxKey].x - totalWidth / 2,
y1: boxPos[boxKey].y - totalHeight / 2,
x2: boxPos[boxKey].x + totalWidth / 2,
y2: boxPos[boxKey].y + totalHeight / 2
};
};
let displaySaveIcon = function() { let displaySaveIcon = function() {
draw(boxes); draw(boxes);
g.drawImage(saveIcon, w / 2 - 24, h / 2 - 24); g.drawImage(saveIcon, w / 2 - 24, h / 2 - 24);
@ -168,33 +199,15 @@
}, 2000); }, 2000);
}; };
/** // 7. String forming helper functions
* ---------------------------------------------------------------
* 7. String forming helper functions
* ---------------------------------------------------------------
*/
let isBool = function(val, defaultVal) {
return typeof val !== 'undefined' ? Boolean(val) : defaultVal;
};
let getDate = function(short, shortMonth, disableSuffix) { let getDate = function(short, shortMonth, disableSuffix) {
const date = new Date(); const date = new Date();
const dayOfMonth = date.getDate(); const dayOfMonth = date.getDate();
const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0); const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0);
const year = date.getFullYear(); const year = date.getFullYear();
let suffix; let suffix = ["st", "nd", "rd"][(dayOfMonth - 1) % 10] || "th";
if ([1, 21, 31].includes(dayOfMonth)) { let dayOfMonthStr = disableSuffix ? dayOfMonth : `${dayOfMonth}${suffix}`;
suffix = "st"; return `${month} ${dayOfMonthStr}${short ? '' : `, ${year}`}`;
} else if ([2, 22].includes(dayOfMonth)) {
suffix = "nd";
} else if ([3, 23].includes(dayOfMonth)) {
suffix = "rd";
} else {
suffix = "th";
}
let dayOfMonthStr = disableSuffix ? dayOfMonth : dayOfMonth + suffix;
return month + " " + dayOfMonthStr + (short ? '' : (", " + year)); // not including year for short version
}; };
let getDayOfWeek = function(date, short) { let getDayOfWeek = function(date, short) {
@ -207,98 +220,179 @@
return short ? meridian[0] : meridian; return short ? meridian[0] : meridian;
}; };
let modString = function(boxItem, data) { let formatStr = function(boxItem, data) {
let prefix = boxItem.prefix || ''; return `${boxItem.prefix || ''}${data}${boxItem.suffix || ''}`;
let suffix = boxItem.suffix || '';
return prefix + data + suffix;
}; };
/** // 8. Main draw function and update logic
* --------------------------------------------------------------- let lastDay = -1;
* 8. Main draw function const BATTERY_UPDATE_INTERVAL = 300000;
* ---------------------------------------------------------------
*/
let draw = (function() { let updateBoxData = function() {
let updatePerMinute = true; let date = new Date();
let currentDay = date.getDate();
return function(boxes) { let now = Date.now();
date = new Date();
g.clear();
// Always draw backgrounds full screen
if (bgImage) { // Check for bg in boxclk config
g.drawImage(bgImage, 0, 0);
} else { // Otherwise use clockbg module
background.fillRect(0, 0, g.getWidth(), g.getHeight());
}
if (boxes.time || boxes.meridian || boxes.date || boxes.dow) {
if (boxes.time) { if (boxes.time) {
boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0).trim()); let showSeconds = !boxes.time.short;
updatePerMinute = isBool(boxes.time.short, true); let timeString = locale.time(date, 1).trim();
} if (showSeconds) {
if (boxes.meridian) { let seconds = date.getSeconds().toString().padStart(2, '0');
boxes.meridian.string = modString(boxes.meridian, locale.meridian(date, isBool(boxes.meridian.short, true))); timeString += ':' + seconds;
} }
if (boxes.date) { let newTimeString = formatStr(boxes.time, timeString);
boxes.date.string = ( if (newTimeString !== boxes.time.string) {
modString(boxes.date, boxes.time.string = newTimeString;
getDate(isBool(boxes.date.short, true), boxes.time.cachedSize = null;
isBool(boxes.date.shortMonth, true),
isBool(boxes.date.disableSuffix, false)
)));
}
if (boxes.dow) {
boxes.dow.string = modString(boxes.dow, getDayOfWeek(date, isBool(boxes.dow.short, true)));
}
if (boxes.batt) {
boxes.batt.string = modString(boxes.batt, E.getBattery());
}
if (boxes.step) {
boxes.step.string = modString(boxes.step, Bangle.getHealthStatus("day").steps);
}
boxKeys.forEach((boxKey) => {
let boxItem = boxes[boxKey];
calcBoxSize(boxItem);
const pos = calcBoxPos(boxKey);
if (isDragging[boxKey]) {
g.setColor(boxItem.border);
g.drawRect(pos.x1, pos.y1, pos.x2, pos.y2);
} }
g.drawString(
boxItem,
boxItem.string,
boxPos[boxKey].x + boxItem.xOffset,
boxPos[boxKey].y + boxItem.yOffset
);
});
if (!Object.values(isDragging).some(Boolean)) {
if (drawTimeout) clearTimeout(drawTimeout);
let interval = updatePerMinute ? 60000 - (Date.now() % 60000) : 1000;
drawTimeout = setTimeout(() => draw(boxes), interval);
} }
};
})();
/** if (boxes.meridian) {
* --------------------------------------------------------------- let newMeridianString = formatStr(boxes.meridian, locale.meridian(date, boxes.meridian.short));
* 9. Helper function for touch event if (newMeridianString !== boxes.meridian.string) {
* --------------------------------------------------------------- boxes.meridian.string = newMeridianString;
*/ boxes.meridian.cachedSize = null;
}
}
if (boxes.date && currentDay !== lastDay) {
let newDateString = formatStr(boxes.date,
getDate(boxes.date.short,
boxes.date.shortMonth,
boxes.date.noSuffix)
);
if (newDateString !== boxes.date.string) {
boxes.date.string = newDateString;
boxes.date.cachedSize = null;
}
}
if (boxes.dow) {
let newDowString = formatStr(boxes.dow, getDayOfWeek(date, boxes.dow.short));
if (newDowString !== boxes.dow.string) {
boxes.dow.string = newDowString;
boxes.dow.cachedSize = null;
}
}
lastDay = currentDay;
}
if (boxes.step) {
let newStepCount = Bangle.getHealthStatus("day").steps;
let newStepString = formatStr(boxes.step, newStepCount);
if (newStepString !== boxes.step.string) {
boxes.step.string = newStepString;
boxes.step.cachedSize = null;
}
}
if (boxes.batt) {
if (!boxes.batt.lastUpdate || now - boxes.batt.lastUpdate >= BATTERY_UPDATE_INTERVAL) {
let currentLevel = E.getBattery();
if (currentLevel !== boxes.batt.lastLevel) {
let newBattString = formatStr(boxes.batt, currentLevel);
if (newBattString !== boxes.batt.string) {
boxes.batt.string = newBattString;
boxes.batt.cachedSize = null;
boxes.batt.lastLevel = currentLevel;
}
}
boxes.batt.lastUpdate = now;
}
}
};
let draw = function() {
g.clear();
// Always draw backgrounds full screen
if (bgImage) { // Check for bg in boxclk config
g.drawImage(bgImage, 0, 0);
} else { // Otherwise use clockbg module
background.fillRect(0, 0, g.getWidth(), g.getHeight());
}
if (!isDragging) {
updateBoxData();
}
boxKeys.forEach((boxKey) => {
let boxItem = boxes[boxKey];
// Set font and alignment for each box individually
g.setFont(boxItem.font, boxItem.fontSize);
g.setFontAlign(0, 0);
// Use cached size if available, otherwise calculate and cache
if (!boxItem.cachedSize) {
calcBoxSize(boxItem);
}
const pos = {
x1: boxItem.pos.x - boxItem.cachedSize.width / 2,
y1: boxItem.pos.y - boxItem.cachedSize.height / 2,
x2: boxItem.pos.x + boxItem.cachedSize.width / 2,
y2: boxItem.pos.y + boxItem.cachedSize.height / 2
};
if (boxItem.selected) {
g.setColor(boxItem.border);
g.drawRect(pos.x1, pos.y1, pos.x2, pos.y2);
}
g.drawString(
boxItem,
boxItem.string,
boxItem.pos.x + boxItem.xOffset,
boxItem.pos.y + boxItem.yOffset
);
});
if (!isDragging) {
if (drawTimeout) clearTimeout(drawTimeout);
let updateInterval = boxes.time && !isBool(boxes.time.short, true) ? 1000 : 60000 - (Date.now() % 60000);
drawTimeout = setTimeout(draw, updateInterval);
}
};
// 9. Helper function for touch event
let calcBoxSize = function(boxItem) {
g.setFont(boxItem.font, boxItem.fontSize);
g.setFontAlign(0, 0);
let strWidth = g.stringWidth(boxItem.string) + 2 * boxItem.outline;
let fontHeight = g.getFontHeight() + 2 * boxItem.outline;
let totalWidth = strWidth + 2 * boxItem.xPadding;
let totalHeight = fontHeight + 2 * boxItem.yPadding;
boxItem.cachedSize = {
width: totalWidth,
height: totalHeight
};
};
let touchInText = function(e, boxItem, boxKey) { let touchInText = function(e, boxItem, boxKey) {
calcBoxSize(boxItem); if (!boxItem.cachedSize) {
const pos = calcBoxPos(boxKey); calcBoxSize(boxItem);
}
const pos = {
x1: boxItem.pos.x - boxItem.cachedSize.width / 2,
y1: boxItem.pos.y - boxItem.cachedSize.height / 2,
x2: boxItem.pos.x + boxItem.cachedSize.width / 2,
y2: boxItem.pos.y + boxItem.cachedSize.height / 2
};
return e.x >= pos.x1 && return e.x >= pos.x1 &&
e.x <= pos.x2 && e.x <= pos.x2 &&
e.y >= pos.y1 && e.y >= pos.y1 &&
e.y <= pos.y2; e.y <= pos.y2;
}; };
let deselectAllBoxes = function() { let deselectAllBoxes = function() {
Object.keys(isDragging).forEach((boxKey) => { isDragging = false;
isDragging[boxKey] = false; boxKeys.forEach((boxKey) => {
boxes[boxKey].selected = false;
}); });
restoreSetColor(); restoreSetColor();
widgets.show(); widgets.show();
@ -306,96 +400,37 @@
modSetColor(); modSetColor();
}; };
/** // 10. Setup function to configure event handlers
* ---------------------------------------------------------------
* 10. Setup function to configure event handlers
* ---------------------------------------------------------------
*/
let setup = function() { let setup = function() {
// ------------------------------------ Bangle.on('lock', function(isLocked) {
// Define the touchHandler function if (isLocked) {
// ------------------------------------ // Screen is about to lock, deselect all boxes
touchHandler = function(zone, e) { deselectAllBoxes();
wasDragging = Object.assign({}, isDragging); // Redraw to reflect changes
let boxTouched = false; draw();
boxKeys.forEach((boxKey) => {
if (touchInText(e, boxes[boxKey], boxKey)) {
isDragging[boxKey] = true;
wasDragging[boxKey] = true;
boxTouched = true;
}
});
if (!boxTouched) {
if (!Object.values(isDragging).some(Boolean)) { // check if no boxes are being dragged
deselectAllBoxes();
if (doubleTapTimer) {
clearTimeout(doubleTapTimer);
doubleTapTimer = null;
// Save boxesConfig on double tap outside of any box and when no boxes are being dragged
Object.keys(boxPos).forEach((boxKey) => {
boxesConfig[boxKey].boxPos.x = (boxPos[boxKey].x / w).toFixed(3);
boxesConfig[boxKey].boxPos.y = (boxPos[boxKey].y / h).toFixed(3);
});
storage.write(fileName, JSON.stringify(boxesConfig));
displaySaveIcon();
return;
}
} else {
// if any box is being dragged, just deselect all without saving
deselectAllBoxes();
}
} }
if (Object.values(wasDragging).some(Boolean) || !boxTouched) { });
draw(boxes);
}
doubleTapTimer = setTimeout(() => {
doubleTapTimer = null;
}, 500); // Increase or decrease this value based on the desired double tap timing
movementDistance = 0;
};
// ------------------------------------
// Define the dragHandler function
// ------------------------------------
dragHandler = function(e) {
// Check if any box is being dragged
if (!Object.values(isDragging).some(Boolean)) return;
// Calculate the movement distance
movementDistance += Math.abs(e.dx) + Math.abs(e.dy);
// Check if the movement distance exceeds a threshold
if (movementDistance > 1) {
boxKeys.forEach((boxKey) => {
if (isDragging[boxKey]) {
widgets.hide();
let boxItem = boxes[boxKey];
calcBoxSize(boxItem);
let newX = boxPos[boxKey].x + e.dx;
let newY = boxPos[boxKey].y + e.dy;
if (newX - totalWidth / 2 >= 0 &&
newX + totalWidth / 2 <= w &&
newY - totalHeight / 2 >= 0 &&
newY + totalHeight / 2 <= h ) {
boxPos[boxKey].x = newX;
boxPos[boxKey].y = newY;
}
const pos = calcBoxPos(boxKey);
g.clearRect(pos.x1, pos.y1, pos.x2, pos.y2);
}
});
draw(boxes);
}
};
Bangle.on('touch', touchHandler); Bangle.on('touch', touchHandler);
Bangle.on('drag', dragHandler); Bangle.on('drag', dragHandler);
if (boxes.step) {
boxes.step.string = formatStr(boxes.step, Bangle.getHealthStatus("day").steps);
}
if (boxes.batt) {
boxes.batt.lastLevel = E.getBattery();
boxes.batt.string = formatStr(boxes.batt, boxes.batt.lastLevel);
boxes.batt.lastUpdate = Date.now();
}
Bangle.setUI({ Bangle.setUI({
mode : "clock", mode: "clock",
remove : function() { remove: function() {
// Remove event handlers, stop draw timer, remove custom font if used // Remove event handlers, stop draw timer, remove custom font
Bangle.removeListener('touch', touchHandler); Bangle.removeListener('touch', touchHandler);
Bangle.removeListener('drag', dragHandler); Bangle.removeListener('drag', dragHandler);
Bangle.removeListener('step');
Bangle.removeAllListeners('lock');
if (drawTimeout) clearTimeout(drawTimeout); if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined; drawTimeout = undefined;
delete Graphics.prototype.setFontBrunoAce; delete Graphics.prototype.setFontBrunoAce;
@ -406,17 +441,21 @@
} }
}); });
loadCustomFont(); loadCustomFont();
draw(boxes); draw();
}; };
/** // 11. Main execution
* ---------------------------------------------------------------
* 11. Main execution part
* ---------------------------------------------------------------
*/
Bangle.loadWidgets(); Bangle.loadWidgets();
widgets.swipeOn(); widgets.swipeOn();
modSetColor(); modSetColor();
setup(); setup();
// Event listener for real-time step updates
Bangle.on('step', function(up) {
if (boxes.step && !isDragging) {
boxes.step.string = formatStr(boxes.step, Bangle.getHealthStatus("day").steps);
boxes.step.cachedSize = null;
draw();
}
});
} }

View File

@ -1,7 +1,7 @@
{ {
"id": "boxclk", "id": "boxclk",
"name": "Box Clock", "name": "Box Clock",
"version": "0.07", "version": "0.08",
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background", "description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
"icon": "app.png", "icon": "app.png",
"dependencies" : { "clockbg":"module" }, "dependencies" : { "clockbg":"module" },