Files
noctalia-shell/Commons/Time.qml
2025-11-30 12:35:34 +01:00

208 lines
6.1 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import qs.Commons
import qs.Services.System
Singleton {
id: root
// Current date
property var now: new Date()
// Returns a Unix Timestamp (in seconds)
readonly property int timestamp: {
return Math.floor(root.now / 1000);
}
// Timer state (for countdown/stopwatch)
property bool timerRunning: false
property bool timerStopwatchMode: false
property int timerRemainingSeconds: 0
property int timerTotalSeconds: 0
property int timerElapsedSeconds: 0
property bool timerSoundPlaying: false
property int timerStartTimestamp: 0 // Unix timestamp when timer was started
property int timerPausedAt: 0 // Value when paused (for resuming)
Timer {
id: updateTimer
interval: 1000
repeat: true
running: true
triggeredOnStart: false
onTriggered: {
var newTime = new Date();
root.now = newTime;
// Update timer if running
if (root.timerRunning && root.timerStartTimestamp > 0) {
const elapsedSinceStart = root.timestamp - root.timerStartTimestamp;
if (root.timerStopwatchMode) {
root.timerElapsedSeconds = root.timerPausedAt + elapsedSinceStart;
} else {
root.timerRemainingSeconds = root.timerTotalSeconds - elapsedSinceStart;
if (root.timerRemainingSeconds <= 0) {
root.timerOnFinished();
}
}
}
// Adjust next interval to sync with the start of the next second
var msIntoSecond = newTime.getMilliseconds();
if (msIntoSecond > 100) {
// If we're more than 100ms into the second, adjust for next time
updateTimer.interval = 1000 - msIntoSecond + 10; // +10ms buffer
updateTimer.restart();
} else {
updateTimer.interval = 1000;
}
}
}
Component.onCompleted: {
// Start by syncing to the next second boundary
var now = new Date();
var msUntilNextSecond = 1000 - now.getMilliseconds();
updateTimer.interval = msUntilNextSecond + 10; // +10ms buffer
updateTimer.restart();
}
// Formats a Date object into a YYYYMMDD-HHMMSS string.
function getFormattedTimestamp(date) {
if (!date) {
date = new Date();
}
const year = date.getFullYear();
// getMonth() is zero-based, so we add 1
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
}
// Format an easy to read approximate duration ex: 4h 32m
// Used to display the time remaining on the Battery widget, computer uptime, etc..
function formatVagueHumanReadableDuration(totalSeconds) {
if (typeof totalSeconds !== 'number' || totalSeconds < 0) {
return '0s';
}
// Floor the input to handle decimal seconds
totalSeconds = Math.floor(totalSeconds);
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const parts = [];
if (days)
parts.push(`${days}d`);
if (hours)
parts.push(`${hours}h`);
if (minutes)
parts.push(`${minutes}m`);
// Only show seconds if no hours and no minutes
if (!hours && !minutes) {
parts.push(`${seconds}s`);
}
return parts.join(' ');
}
// Format a date into
function formatRelativeTime(date) {
if (!date)
return "";
const diff = Date.now() - date.getTime();
if (diff < 60000)
return I18n.tr("notifications.time.now");
if (diff < 120000)
return I18n.tr("notifications.time.diffM");
if (diff < 3600000)
return I18n.tr("notifications.time.diffMM", {
"diff": Math.floor(diff / 60000)
});
if (diff < 7200000)
return I18n.tr("notifications.time.diffH");
if (diff < 86400000)
return I18n.tr("notifications.time.diffHH", {
"diff": Math.floor(diff / 3600000)
});
if (diff < 172800000)
return I18n.tr("notifications.time.diffD");
return I18n.tr("notifications.time.diffDD", {
"diff": Math.floor(diff / 86400000)
});
}
// Timer functions
function timerStart() {
if (root.timerStopwatchMode) {
root.timerRunning = true;
root.timerStartTimestamp = root.timestamp;
root.timerPausedAt = root.timerElapsedSeconds;
} else {
if (root.timerRemainingSeconds <= 0) {
return;
}
root.timerRunning = true;
root.timerTotalSeconds = root.timerRemainingSeconds;
root.timerStartTimestamp = root.timestamp;
root.timerPausedAt = 0;
}
}
function timerPause() {
if (root.timerRunning) {
// Save current state
if (root.timerStopwatchMode) {
root.timerPausedAt = root.timerElapsedSeconds;
} else {
root.timerPausedAt = root.timerRemainingSeconds;
}
}
root.timerRunning = false;
root.timerStartTimestamp = 0;
// Stop any repeating notification sound when pausing
SoundService.stopSound("alarm-beep.wav");
root.timerSoundPlaying = false;
}
function timerReset() {
root.timerRunning = false;
root.timerStartTimestamp = 0;
if (root.timerStopwatchMode) {
root.timerElapsedSeconds = 0;
root.timerPausedAt = 0;
} else {
root.timerRemainingSeconds = 0;
root.timerTotalSeconds = 0;
root.timerPausedAt = 0;
}
// Stop any repeating notification sound
SoundService.stopSound("alarm-beep.wav");
root.timerSoundPlaying = false;
}
function timerOnFinished() {
root.timerRunning = false;
root.timerRemainingSeconds = 0;
// Play notification sound with repeat at lower volume
root.timerSoundPlaying = true;
SoundService.playSound("alarm-beep.wav", {
repeat: true,
volume: 0.3
});
}
}