Merge branch 'espruino:master' into master
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,53 @@
|
|||
# Chrono Logger
|
||||
|
||||
Record times active on a task, course, work or anything really.
|
||||
|
||||
**Disclaimer:** No one is responsible for any loss of data you recorded with this app. If you run into problems please report as advised under **Requests** below.
|
||||
|
||||
With time on your side and a little help from your friends - you'll surely triumph over Lavos in the end!
|
||||
|
||||
      
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Click the large green button to log the start of your activity. Click the now red button again to log that you stopped.
|
||||
|
||||
## Features
|
||||
|
||||
- Saves to file on every toggling of the active state.
|
||||
- csv file contents looks like:
|
||||
```
|
||||
1,Start,2024-03-02T15:18:09 GMT+0200
|
||||
2,Note,Critical hit!
|
||||
3,Stop,2024-03-02T15:19:17 GMT+0200
|
||||
```
|
||||
- Add annotations to the log.
|
||||
- Create and switch between multiple logs.
|
||||
- Sync log files to an Android device through Gadgetbridge (Needs pending code changes to Gadgetbridge).
|
||||
- App state is restored when you start the app again.
|
||||
|
||||
## Controls
|
||||
|
||||
- Large button to toggle active state.
|
||||
- Menu icon to access additional functionality.
|
||||
- Hardware button exits menus, closes the app on the main screen.
|
||||
|
||||
## TODO and notes
|
||||
|
||||
- Delete individual tasks/logs through the app?
|
||||
- Reset everything through the app?
|
||||
- Scan for chronlog storage files that somehow no longer have tasks associated with it?
|
||||
- Complete the Gadgetbridge side of things for sync.
|
||||
- Sync to iOS?
|
||||
- Inspect log files through the app, similarly to Recorder app?
|
||||
- Changes to Android file system permissions makes it not always trivial to access the synced files.
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
Tag @thyttan in an issue to https://gitbub.com/espruino/BangleApps/issues to report problems or suggestions.
|
||||
|
||||
## Creator
|
||||
|
||||
[thyttan](https://github.com/thyttan)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///gElq3X0ELJf4AiitAAYMBqgKEgNVrgEBmtVCAQABgtVr/Agf1qtQEQlpq6QB6tpEgkVywLDywLEq2uyoLB6wEBBZAECBYda32lBYIECBZ9W3wjDAgILPquWqoACAgILEtILDAgKOEAAyQCRwIAGSAUVBY6ECBZYGD7WnAoYLF9WrBYupAoWq1QECtQLBtWdBYt21QLC1LfBBYVfA4ILBlWq1f9rWVv/q1WoBYMKCgOvTYP6AoOgBYMCAoIAFwCQCBY6nDGAIAEFwQkIEQZVCBQZRCAAcGBYeQBYoYDCwwYECw5KC0gKIAH4APA="))
|
|
@ -0,0 +1,376 @@
|
|||
// TODO:
|
||||
// - Add more /*LANG*/ tags for translations.
|
||||
// - Check if there are chronlog storage files that should be added to tasks.
|
||||
|
||||
{
|
||||
const storage = require("Storage");
|
||||
let appData = storage.readJSON("chronlog.json", true) || {
|
||||
currentTask : "default",
|
||||
tasks : {
|
||||
default: {
|
||||
file : "chronlog_default.csv", // Existing default task log file
|
||||
state : "stopped",
|
||||
lineNumber : 0,
|
||||
lastLine : "",
|
||||
lastSyncedLine : "",
|
||||
},
|
||||
// Add more tasks as needed
|
||||
},
|
||||
};
|
||||
let currentTask = appData.currentTask;
|
||||
let tasks = appData.tasks;
|
||||
delete appData;
|
||||
|
||||
let themeColors = g.theme;
|
||||
|
||||
let logEntry; // Avoid previous lint warning
|
||||
|
||||
// Function to draw the Start/Stop button with play and pause icons
|
||||
let drawButton = ()=>{
|
||||
var btnWidth = g.getWidth() - 40;
|
||||
var btnHeight = 50;
|
||||
var btnX = 20;
|
||||
var btnY = (g.getHeight() - btnHeight) / 2;
|
||||
var cornerRadius = 25;
|
||||
|
||||
var isStopped = tasks[currentTask].state === "stopped";
|
||||
g.setColor(isStopped ? "#0F0" : "#F00"); // Set color to green when stopped and red when started
|
||||
|
||||
// Draw rounded corners of the button
|
||||
g.fillCircle(btnX + cornerRadius, btnY + cornerRadius, cornerRadius);
|
||||
g.fillCircle(btnX + btnWidth - cornerRadius, btnY + cornerRadius, cornerRadius);
|
||||
g.fillCircle(btnX + cornerRadius, btnY + btnHeight - cornerRadius, cornerRadius);
|
||||
g.fillCircle(btnX + btnWidth - cornerRadius, btnY + btnHeight - cornerRadius, cornerRadius);
|
||||
|
||||
// Draw rectangles to fill in the button
|
||||
g.fillRect(btnX + cornerRadius, btnY, btnX + btnWidth - cornerRadius, btnY + btnHeight);
|
||||
g.fillRect(btnX, btnY + cornerRadius, btnX + btnWidth, btnY + btnHeight - cornerRadius);
|
||||
|
||||
g.setColor(themeColors.bg); // Set icon color to contrast against the button's color
|
||||
|
||||
// Center the icon within the button
|
||||
var iconX = btnX + btnWidth / 2;
|
||||
var iconY = btnY + btnHeight / 2;
|
||||
|
||||
if (isStopped) {
|
||||
// Draw play icon
|
||||
var playSize = 10; // Side length of the play triangle
|
||||
var offset = playSize / Math.sqrt(3) - 3;
|
||||
g.fillPoly([
|
||||
iconX - playSize, iconY - playSize + offset,
|
||||
iconX - playSize, iconY + playSize + offset,
|
||||
iconX + playSize * 2 / Math.sqrt(3), iconY + offset
|
||||
]);
|
||||
} else {
|
||||
// Draw pause icon
|
||||
var barWidth = 5; // Width of pause bars
|
||||
var barHeight = btnHeight / 2; // Height of pause bars
|
||||
var barSpacing = 5; // Spacing between pause bars
|
||||
g.fillRect(iconX - barSpacing / 2 - barWidth, iconY - barHeight / 2, iconX - barSpacing / 2, iconY + barHeight / 2);
|
||||
g.fillRect(iconX + barSpacing / 2, iconY - barHeight / 2, iconX + barSpacing / 2 + barWidth, iconY + barHeight / 2);
|
||||
}
|
||||
};
|
||||
|
||||
let drawHamburgerMenu = ()=>{
|
||||
var x = g.getWidth() / 2; // Center the hamburger menu horizontally
|
||||
var y = (7/8)*g.getHeight(); // Position it near the bottom
|
||||
var lineLength = 18; // Length of the hamburger lines
|
||||
var spacing = 6; // Space between the lines
|
||||
|
||||
g.setColor(themeColors.fg); // Set color to foreground color for the icon
|
||||
// Draw three horizontal lines
|
||||
for (var i = -1; i <= 1; i++) {
|
||||
g.fillRect(x - lineLength/2, y + i * spacing - 1, x + lineLength/2, y + i * spacing + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to draw the task name centered between the widget field and the start/stop button
|
||||
let drawTaskName = ()=>{
|
||||
g.setFont("Vector", 20); // Set a smaller font for the task name display
|
||||
|
||||
// Calculate position to center the task name horizontally
|
||||
var x = (g.getWidth()) / 2;
|
||||
|
||||
// Calculate position to center the task name vertically between the widget field and the start/stop button
|
||||
var y = g.getHeight()/4; // Center vertically
|
||||
|
||||
g.setColor(themeColors.fg).setFontAlign(0,0); // Set text color to foreground color
|
||||
g.drawString(currentTask, x, y); // Draw the task name centered on the screen
|
||||
};
|
||||
|
||||
// Function to draw the last log entry of the current task
|
||||
let drawLastLogEntry = ()=>{
|
||||
g.setFont("Vector", 10); // Set a smaller font for the task name display
|
||||
|
||||
// Calculate position to center the log entry horizontally
|
||||
var x = (g.getWidth()) / 2;
|
||||
|
||||
// Calculate position to place the log entry properly between the start/stop button and hamburger menu
|
||||
var btnBottomY = (g.getHeight() + 50) / 2; // Y-coordinate of the bottom of the start/stop button
|
||||
var menuBtnYTop = g.getHeight() * (5 / 6); // Y-coordinate of the top of the hamburger menu button
|
||||
var y = btnBottomY + (menuBtnYTop - btnBottomY) / 2 + 2; // Center vertically between button and menu
|
||||
|
||||
g.setColor(themeColors.fg).setFontAlign(0,0); // Set text color to foreground color
|
||||
g.drawString(g.wrapString(tasks[currentTask].lastLine, 150).join("\n"), x, y);
|
||||
};
|
||||
|
||||
/*
|
||||
// Helper function to read the last log entry from the current task's log file
|
||||
let updateLastLogEntry = ()=>{
|
||||
var filename = tasks[currentTask].file;
|
||||
var file = require("Storage").open(filename, "r");
|
||||
var lastLine = "";
|
||||
var line;
|
||||
while ((line = file.readLine()) !== undefined) {
|
||||
lastLine = line; // Keep reading until the last line
|
||||
}
|
||||
tasks[currentTask].lastLine = lastLine;
|
||||
};
|
||||
*/
|
||||
|
||||
// Main UI drawing function
|
||||
let drawMainMenu = ()=>{
|
||||
g.clear();
|
||||
Bangle.drawWidgets(); // Draw any active widgets
|
||||
g.setColor(themeColors.bg); // Set color to theme's background color
|
||||
g.fillRect(Bangle.appRect); // Fill the app area with the background color
|
||||
|
||||
drawTaskName(); // Draw the centered task name
|
||||
drawLastLogEntry(); // Draw the last log entry of the current task
|
||||
drawButton(); // Draw the Start/Stop toggle button
|
||||
drawHamburgerMenu(); // Draw the hamburger menu button icon
|
||||
|
||||
//g.flip(); // Send graphics to the display
|
||||
};
|
||||
|
||||
// Function to toggle the active state
|
||||
let toggleChronlog = ()=>{
|
||||
var dateObj = new Date();
|
||||
var dateObjStrSplit = dateObj.toString().split(" ");
|
||||
var currentTime = dateObj.getFullYear().toString() + "-" + (dateObj.getMonth()<10?"0":"") + dateObj.getMonth().toString() + "-" + (dateObj.getDate()<10?"0":"") + dateObj.getDate().toString() + "T" + (dateObj.getHours()<10?"0":"") + dateObj.getHours().toString() + ":" + (dateObj.getMinutes()<10?"0":"") + dateObj.getMinutes().toString() + ":" + (dateObj.getSeconds()<10?"0":"") + dateObj.getSeconds().toString() + " " + dateObjStrSplit[dateObjStrSplit.length-1];
|
||||
|
||||
tasks[currentTask].lineNumber = Number(tasks[currentTask].lineNumber) + 1;
|
||||
logEntry = tasks[currentTask].lineNumber + (tasks[currentTask].state === "stopped" ? ",Start," : ",Stop,") + currentTime + "\n";
|
||||
var filename = tasks[currentTask].file;
|
||||
|
||||
// Open the appropriate file and append the log entry
|
||||
var file = require("Storage").open(filename, "a");
|
||||
file.write(logEntry);
|
||||
tasks[currentTask].lastLine = logEntry;
|
||||
|
||||
// Toggle the state and update the button text
|
||||
tasks[currentTask].state = tasks[currentTask].state === "stopped" ? "started" : "stopped";
|
||||
drawMainMenu(); // Redraw the main UI
|
||||
};
|
||||
|
||||
// Define the touch handler function for the main menu
|
||||
let handleMainMenuTouch = (button, xy)=>{
|
||||
var btnTopY = (g.getHeight() - 50) / 2;
|
||||
var btnBottomY = btnTopY + 50;
|
||||
var menuBtnYTop = (7/8)*g.getHeight() - 15;
|
||||
var menuBtnYBottom = (7/8)*g.getHeight() + 15;
|
||||
var menuBtnXLeft = (g.getWidth() / 2) - 15;
|
||||
var menuBtnXRight = (g.getWidth() / 2) + 15;
|
||||
|
||||
// Detect if the touch is within the toggle button area
|
||||
if (xy.x >= 20 && xy.x <= (g.getWidth() - 20) && xy.y > btnTopY && xy.y < btnBottomY) {
|
||||
toggleChronlog();
|
||||
}
|
||||
// Detect if the touch is within the hamburger menu button area
|
||||
else if (xy.x >= menuBtnXLeft && xy.x <= menuBtnXRight && xy.y >= menuBtnYTop && xy.y <= menuBtnYBottom) {
|
||||
showMenu();
|
||||
}
|
||||
};
|
||||
|
||||
// Function to attach the touch event listener
|
||||
let setMainUI = ()=>{
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
back: load,
|
||||
touch: handleMainMenuTouch
|
||||
});
|
||||
};
|
||||
|
||||
let saveAppState = ()=>{
|
||||
let appData = {
|
||||
currentTask : currentTask,
|
||||
tasks : tasks,
|
||||
};
|
||||
require("Storage").writeJSON("chronlog.json", appData);
|
||||
};
|
||||
// Set up a listener for the 'kill' event
|
||||
E.on('kill', saveAppState);
|
||||
|
||||
// Function to switch to a selected task
|
||||
let switchTask = (taskName)=>{
|
||||
currentTask = taskName; // Update the current task
|
||||
|
||||
// Reinitialize the UI elements
|
||||
setMainUI();
|
||||
drawMainMenu(); // Redraw UI to reflect the task change and the button state
|
||||
};
|
||||
|
||||
// Function to create a new task
|
||||
let createNewTask = ()=>{
|
||||
// Prompt the user to input the task's name
|
||||
require("textinput").input({
|
||||
text: "" // Default empty text for new task
|
||||
}).then(result => {
|
||||
var taskName = result; // Store the result from text input
|
||||
if (taskName) {
|
||||
if (tasks.hasOwnProperty(taskName)) {
|
||||
// Task already exists, handle this case as needed
|
||||
E.showAlert(/*LANG*/"Task already exists", "Error").then(drawMainMenu);
|
||||
} else {
|
||||
// Create a new task log file for the new task
|
||||
var filename = "chronlog_" + taskName.replace(/\W+/g, "_") + ".csv";
|
||||
tasks[taskName] = {
|
||||
file : filename,
|
||||
state : "stopped",
|
||||
lineNumber : 0,
|
||||
lastLine : "",
|
||||
lastSyncedLine : "",
|
||||
};
|
||||
|
||||
currentTask = taskName;
|
||||
|
||||
setMainUI();
|
||||
drawMainMenu(); // Redraw UI with the new task
|
||||
}
|
||||
} else {
|
||||
setMainUI();
|
||||
drawMainMenu(); // User cancelled, redraw main menu
|
||||
}
|
||||
}).catch(e => {
|
||||
console.log("Text input error", e);
|
||||
setMainUI();
|
||||
drawMainMenu(); // In case of error also redraw main menu
|
||||
});
|
||||
};
|
||||
|
||||
// Function to display the list of tasks for selection
|
||||
let chooseTask = ()=>{
|
||||
// Construct the tasks menu from the tasks object
|
||||
var taskMenu = {
|
||||
"": { "title": /*LANG*/"Choose Task",
|
||||
"back" : function() {
|
||||
setMainUI(); // Reattach when the menu is closed
|
||||
drawMainMenu(); // Cancel task selection
|
||||
}
|
||||
}
|
||||
};
|
||||
for (var taskName in tasks) {
|
||||
if (!tasks.hasOwnProperty(taskName)) continue;
|
||||
taskMenu[taskName] = (function(name) {
|
||||
return function() {
|
||||
switchTask(name);
|
||||
};
|
||||
})(taskName);
|
||||
}
|
||||
|
||||
// Add a menu option for creating a new task
|
||||
taskMenu[/*LANG*/"Create New Task"] = createNewTask;
|
||||
|
||||
E.showMenu(taskMenu); // Display the task selection
|
||||
};
|
||||
|
||||
// Function to annotate the current or last work session
|
||||
let annotateTask = ()=>{
|
||||
|
||||
// Prompt the user to input the annotation text
|
||||
require("textinput").input({
|
||||
text: "" // Default empty text for annotation
|
||||
}).then(result => {
|
||||
var annotationText = result.trim();
|
||||
if (annotationText) {
|
||||
// Append annotation to the last or current log entry
|
||||
tasks[currentTask].lineNumber ++;
|
||||
var annotatedEntry = tasks[currentTask].lineNumber + /*LANG*/",Note," + annotationText + "\n";
|
||||
var filename = tasks[currentTask].file;
|
||||
var file = require("Storage").open(filename, "a");
|
||||
file.write(annotatedEntry);
|
||||
tasks[currentTask].lastLine = annotatedEntry;
|
||||
setMainUI();
|
||||
drawMainMenu(); // Redraw UI after adding the annotation
|
||||
} else {
|
||||
// User cancelled, so we do nothing and just redraw the main menu
|
||||
setMainUI();
|
||||
drawMainMenu();
|
||||
}
|
||||
}).catch(e => {
|
||||
console.log("Annotation input error", e);
|
||||
setMainUI();
|
||||
drawMainMenu(); // In case of error also redraw main menu
|
||||
});
|
||||
};
|
||||
|
||||
let syncToAndroid = (taskName, isFullSync)=>{
|
||||
let mode = "a";
|
||||
if (isFullSync) mode = "w";
|
||||
let lastSyncedLine = tasks[taskName].lastSyncedLine || 0;
|
||||
let taskNameValidFileName = taskName.replace(" ","_"); // FIXME: Should use something similar to replaceAll using a regular expression to catch all illegal characters.
|
||||
|
||||
let storageFile = require("Storage").open("chronlog_"+taskNameValidFileName+".csv", "r");
|
||||
let contents = storageFile.readLine();
|
||||
let lineNumber = contents ? contents.slice(0, contents.indexOf(",")) : 0;
|
||||
let shouldSyncLine = ()=>{return (contents && (isFullSync || (Number(lineNumber)>Number(lastSyncedLine))));};
|
||||
let doSyncLine = (mde)=>{Bluetooth.println(JSON.stringify({t:"file", n:"chronlog_"+taskNameValidFileName+".csv", c:contents, m:mde}));};
|
||||
|
||||
if (shouldSyncLine()) doSyncLine(mode);
|
||||
contents = storageFile.readLine();
|
||||
while (contents) {
|
||||
lineNumber = contents.slice(0, contents.indexOf(",")); // Could theoretically do with `lineNumber++`, but this is more robust in case numbering in file ended up irregular.
|
||||
if (shouldSyncLine()) doSyncLine("a");
|
||||
contents = storageFile.readLine();
|
||||
}
|
||||
tasks[taskName].lastSyncedLine = lineNumber;
|
||||
};
|
||||
|
||||
// Function to display the list of tasks for selection
|
||||
let syncTasks = ()=>{
|
||||
let isToDoFullSync = false;
|
||||
// Construct the tasks menu from the tasks object
|
||||
var syncMenu = {
|
||||
"": { "title": /*LANG*/"Sync Tasks",
|
||||
"back" : function() {
|
||||
setMainUI(); // Reattach when the menu is closed
|
||||
drawMainMenu(); // Cancel task selection
|
||||
}
|
||||
}
|
||||
};
|
||||
syncMenu[/*LANG*/"Full Resyncs"] = {
|
||||
value: !!isToDoFullSync, // !! converts undefined to false
|
||||
onchange: ()=>{
|
||||
isToDoFullSync = !isToDoFullSync
|
||||
},
|
||||
}
|
||||
for (var taskName in tasks) {
|
||||
if (!tasks.hasOwnProperty(taskName)) continue;
|
||||
syncMenu[taskName] = (function(name) {
|
||||
return function() {syncToAndroid(name,isToDoFullSync);};
|
||||
})(taskName);
|
||||
}
|
||||
|
||||
E.showMenu(syncMenu); // Display the task selection
|
||||
};
|
||||
|
||||
let showMenu = ()=>{
|
||||
var menu = {
|
||||
"": { "title": /*LANG*/"Menu",
|
||||
"back": function() {
|
||||
setMainUI(); // Reattach when the menu is closed
|
||||
drawMainMenu(); // Redraw the main UI when closing the menu
|
||||
},
|
||||
},
|
||||
/*LANG*/"Annotate": annotateTask, // Now calls the real annotation function
|
||||
/*LANG*/"Change Task": chooseTask, // Opens the task selection screen
|
||||
/*LANG*/"Sync to Android": syncTasks,
|
||||
};
|
||||
E.showMenu(menu);
|
||||
};
|
||||
|
||||
Bangle.loadWidgets();
|
||||
drawMainMenu(); // Draw the main UI when the app starts
|
||||
// When the application starts, attach the touch event listener
|
||||
setMainUI();
|
||||
}
|
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "chronlog",
|
||||
"name": "Chrono Logger",
|
||||
"version":"0.01",
|
||||
"description": "Record time active on a task, course, work or anything really.",
|
||||
"icon": "app.png",
|
||||
"tags": "logging, record, work, tasks",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots" : [ { "url":"dump.png"}, { "url":"dump1.png" }, { "url":"dump2.png" }, { "url":"dump3.png" }, { "url":"dump4.png" }, { "url":"dump5.png" }, { "url":"dump6.png" } ],
|
||||
"storage": [
|
||||
{"name":"chronlog.app.js","url":"app.js"},
|
||||
{"name":"chronlog.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -6,3 +6,4 @@
|
|||
0.60: Fixes typos, BTN1 to show launcher and show app icon
|
||||
0.61: Minor code improvements
|
||||
0.70: Better wrapping of the text base (dynamic instead of hardcoded)
|
||||
0.80: Add analog watch, steps and date
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "rellotge",
|
||||
"name": "Rellotge en catala",
|
||||
"shortName":"Rellotge",
|
||||
"version": "0.70",
|
||||
"version": "0.80",
|
||||
"description": "A clock with traditional naming of hours in Catalan",
|
||||
"icon": "icona.png",
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -6,13 +6,20 @@
|
|||
const dateFontSize = 2;
|
||||
const font = "12x20";
|
||||
|
||||
const xyCenter = g.getWidth() /9;
|
||||
const yposTime = 55;
|
||||
const yposDate = 130;
|
||||
const Panel = {
|
||||
STEPS: 0,
|
||||
DATE: 1
|
||||
};
|
||||
|
||||
let panel = Panel.STEPS;
|
||||
|
||||
const timeTextMagin = 15;
|
||||
const xyCenter = timeTextMagin;
|
||||
const yposTime = 45;
|
||||
const leshores = ["Les dotze","La una","Les dues","Les tres","Les quatre","Les cinc","Les sis","Les set","Les vuit","Les nou","Les deu","Les onze","Les dotze","La una","Les dues","Les tres","Les quatre","Les cinc","Les sis","Les set","Les vuit","Les nou","Les deu","Les onze","Les dotze"];
|
||||
const leshores2 = ["d'una","de dues","de tres","de quatre","de cinc","de sis","de set","de vuit","de nou","de deu","d'onze","de dotze"];
|
||||
const fontWeight = 12;
|
||||
const maxChars = Math.floor(Bangle.appRect.w / fontWeight);
|
||||
const RED = '#f00';
|
||||
const BLACK = "#000"
|
||||
|
||||
function getHora(hour) {
|
||||
if (hour >= 12) {
|
||||
|
@ -21,14 +28,18 @@
|
|||
return leshores2[hour];
|
||||
}
|
||||
|
||||
function addLineFeeds(inputString) {
|
||||
function addLineFeeds(inputString, g, posX) {
|
||||
const margin = timeTextMagin;
|
||||
const words = inputString.split(' ');
|
||||
let lines = "";
|
||||
let line = "";
|
||||
const totalWidth = g.getWidth();
|
||||
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
const word = words[i];
|
||||
if (line.length + word.length > maxChars) {
|
||||
for (const word of words) {
|
||||
const nextLine = line + word;
|
||||
const width = posX + g.stringWidth(nextLine) + margin;
|
||||
|
||||
if (width > totalWidth) {
|
||||
lines += line.trim() + "\r\n";
|
||||
line = "";
|
||||
}
|
||||
|
@ -38,16 +49,85 @@
|
|||
return lines;
|
||||
}
|
||||
|
||||
// Define the center coordinates of the watch face
|
||||
const margin = 10;
|
||||
const centerX = 40 + margin;
|
||||
const centerY = g.getHeight() - 40 - margin;
|
||||
|
||||
// Function to draw the watch face
|
||||
function drawWatchFace() {
|
||||
|
||||
const diameter = 40;
|
||||
g.setColor(BLACK);
|
||||
g.drawCircle(centerX, centerY, diameter);
|
||||
|
||||
// Draw hour markers
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const angle = (i / 12) * Math.PI * 2;
|
||||
const x1 = centerX + Math.sin(angle) * 70 / 2;
|
||||
const y1 = centerY - Math.cos(angle) * 70 / 2;
|
||||
const x2 = centerX + Math.sin(angle) * 60 / 2;
|
||||
const y2 = centerY - Math.cos(angle) * 60 / 2;
|
||||
g.drawLine(x1, y1, x2, y2);
|
||||
}
|
||||
}
|
||||
|
||||
function drawHand(centerX, centerY, hourAngle, handLength) {
|
||||
const hourHandX = centerX + Math.sin(hourAngle) * handLength;
|
||||
const hourHandY = centerY - Math.cos(hourAngle) * handLength;
|
||||
g.drawLine(centerX, centerY, hourHandX, hourHandY);
|
||||
}
|
||||
|
||||
// Function to update the watch display
|
||||
function updateWatch() {
|
||||
const now = new Date();
|
||||
const hours = now.getHours() % 12;
|
||||
const minutes = now.getMinutes();
|
||||
|
||||
// Calculate angles for hour, minute, and second hands
|
||||
const hourAngle = ((hours + minutes / 60) / 12) * Math.PI * 2;
|
||||
const minuteAngle = (minutes / 60) * Math.PI * 2;
|
||||
g.setColor(BLACK);
|
||||
|
||||
drawHand(centerX, centerY, hourAngle, 10);
|
||||
drawHand(centerX, centerY, minuteAngle, 15);
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
var steps = Bangle.getHealthStatus("day").steps;
|
||||
steps = Math.round(steps/1000);
|
||||
return steps + "k";
|
||||
}
|
||||
|
||||
function drawDate() {
|
||||
g.setFont(font, dateFontSize);
|
||||
|
||||
const date = new Date();
|
||||
const dow = require("locale").dow(date, 2).toUpperCase(); //dj.
|
||||
g.drawString(dow, g.getWidth() - 60, g.getHeight() - 60, true);
|
||||
|
||||
const mon = date.getDate() + " " + require("locale").month(date, 1);
|
||||
g.setFont(font, "4x6");
|
||||
g.drawString(mon, g.getWidth() - 70, g.getHeight() - 25, true);
|
||||
}
|
||||
|
||||
function drawSteps() {
|
||||
|
||||
g.setFont(font, dateFontSize);
|
||||
const steps = getSteps()
|
||||
g.drawString(steps, g.getWidth() - 60, g.getHeight() - 60, true);
|
||||
|
||||
g.setFont(font, "4x6");
|
||||
const text = "Passos"
|
||||
g.drawString(text, g.getWidth() - 70, g.getHeight() - 25, true);
|
||||
}
|
||||
|
||||
function drawSimpleClock() {
|
||||
g.clearRect(Bangle.appRect);
|
||||
|
||||
// get date
|
||||
var d = new Date();
|
||||
var m = d.getMinutes();
|
||||
|
||||
// drawSting centered
|
||||
g.setFontAlign(-1, 0);
|
||||
|
||||
// draw time
|
||||
let t;
|
||||
if (m >= 0 && m < 2) {
|
||||
t = leshores[d.getHours()] + " en punt";
|
||||
|
@ -98,16 +178,34 @@
|
|||
} else if (m >= 57) {
|
||||
t = "Tres quarts i mig ben tocats " + getHora(d.getHours());
|
||||
}
|
||||
t = addLineFeeds(t)
|
||||
g.clearRect(Bangle.appRect);
|
||||
// drawString centered
|
||||
g.setFontAlign(-1, 0);
|
||||
|
||||
g.setFont(font, timeFontSize);
|
||||
t = addLineFeeds(t, g, xyCenter);
|
||||
|
||||
let color;
|
||||
if (E.getBattery() < 15) {
|
||||
color = RED;
|
||||
}
|
||||
else {
|
||||
color = BLACK;
|
||||
}
|
||||
|
||||
g.setColor(color);
|
||||
g.drawString(t, xyCenter, yposTime, true);
|
||||
g.setColor(BLACK);
|
||||
if (panel == Panel.STEPS) {
|
||||
drawSteps();
|
||||
panel = Panel.DATE;
|
||||
} else {
|
||||
drawDate();
|
||||
panel = Panel.STEPS;
|
||||
}
|
||||
|
||||
// draw Hours
|
||||
g.setFont(font, dateFontSize);
|
||||
var mu = "";
|
||||
if (m < 10) {mu = "0"+m;} else {mu = m;}
|
||||
|
||||
g.drawString(d.getHours()+":"+mu, xyCenter, yposDate, true);
|
||||
drawWatchFace();
|
||||
updateWatch();
|
||||
}
|
||||
|
||||
// handle switch display on by pressing BTN1
|
||||
|
@ -118,8 +216,15 @@
|
|||
Bangle.removeListener('lcdPower', onLcd);
|
||||
}
|
||||
}
|
||||
Bangle.on('lcdPower', onLcd);
|
||||
Bangle.setUI("clock");
|
||||
Bangle.on('lcdPower', onLcd);
|
||||
Bangle.setUI({
|
||||
mode: "clockupdown"
|
||||
},
|
||||
btn => {
|
||||
// up & down even which forces panel switch
|
||||
drawSimpleClock();
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").swipeOn();
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Use locale module to display distance
|
||||
|
|
|
@ -7,6 +7,8 @@ Bangle.setLCDTimeout(undefined);
|
|||
let renderIntervalId;
|
||||
let startTime;
|
||||
|
||||
const locale = require("locale");
|
||||
|
||||
const DEFAULTS = {
|
||||
units: 0,
|
||||
};
|
||||
|
@ -29,6 +31,9 @@ var layout = new Layout( {
|
|||
back: load,
|
||||
});
|
||||
|
||||
// TODO The code in this function appears in various apps so it might be
|
||||
// nice to add something to the time_utils module. (There is already a
|
||||
// formatDuration function but that doesn't quite work the same way.)
|
||||
const getTime = function(milliseconds) {
|
||||
let hrs = Math.floor(milliseconds/3600000);
|
||||
let mins = Math.floor(milliseconds/60000)%60;
|
||||
|
@ -52,12 +57,15 @@ const getDistance = function(milliseconds) {
|
|||
let secs = milliseconds/1000;
|
||||
let distance;
|
||||
|
||||
if (settings.units === 0) {
|
||||
if (settings.units === 1) {
|
||||
let kms = Math.round((secs / 2.91) * 10) / 10;
|
||||
distance = kms.toFixed(1) + "km";
|
||||
} else {
|
||||
} else if (settings.units === 2) {
|
||||
let miles = Math.round((secs / 4.69) * 10) / 10;
|
||||
distance = miles.toFixed(1) + "mi";
|
||||
} else {
|
||||
let meters = (secs / 2.91) * 1000;
|
||||
distance = locale.distance(meters);
|
||||
}
|
||||
|
||||
return distance;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
{ "id": "thunder",
|
||||
"name": "Come on Thunder",
|
||||
"shortName":"Thunder",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Simple timer to calculate how far away lightning is",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,weather",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{ "url": "screenshot.png" }],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"thunder.app.js","url":"app.js"},
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
};
|
||||
|
||||
const showMenu = function() {
|
||||
const unitOptions = ['kms','miles'];
|
||||
const unitOptions = [/*LANG*/'Auto','kms','miles'];
|
||||
|
||||
const menu = {
|
||||
'': {'title': 'Thunder'},
|
||||
|
|