From e4b761917aec42dee92028d9cd295adbf61868b7 Mon Sep 17 00:00:00 2001 From: Nyx <189459385+nyx-4@users.noreply.github.com> Date: Sun, 3 Aug 2025 20:36:29 +0500 Subject: [PATCH] Polished the UI and added Persistent Config option. Signed-off-by: Nyx <189459385+nyx-4@users.noreply.github.com> Changelog: Added Config.options.time.pomodoro options in Config.qml Restructered the Pomodoro logic and added Long break Used timestamp instead of naively counting down. Major UI tweaks. --- .../quickshell/ii/modules/common/Config.qml | 6 + .../sidebarRight/pomodoro/PomodoroWidget.qml | 195 ++++++++---------- .config/quickshell/ii/services/Pomodoro.qml | 118 ++++++----- 3 files changed, 164 insertions(+), 155 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index bcbbd1e33..3ee804fda 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -253,6 +253,12 @@ Singleton { // https://doc.qt.io/qt-6/qtime.html#toString property string format: "hh:mm" property string dateFormat: "ddd, dd/MM" + property JsonObject pomodoro: JsonObject { + property int breaktime: 300 + property int cycle: 4 + property int focus: 1500 + property int longbreak: 1200 + } } property JsonObject windows: JsonObject { diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index b39be28ce..ff39f1b6b 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -166,102 +166,102 @@ Item { Item { ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter - spacing: 18 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Pomodoro.timeFormattedPomodoro() - font.pixelSize: 50 - color: Appearance.m3colors.m3onSurface - } + spacing: 20 RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 20 + spacing: 40 + // The Pomodoro timer circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 7 + value: { + let pomodoroTotalTime = Pomodoro.isPomodoroBreak ? Pomodoro.pomodoroBreakTime : Pomodoro.pomodoroFocusTime + return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime + } + size: 125 + secondaryColor: Appearance.colors.colSecondaryContainer + primaryColor: Appearance.m3colors.m3onSecondaryContainer + enableAnimation: true - DialogButton { - buttonText: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") - Layout.preferredWidth: 90 - Layout.preferredHeight: 35 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.togglePomodoro() - background: Rectangle { - color: Appearance.m3colors.m3onSecondary - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outline - border.width: 1 + ColumnLayout { + anchors.centerIn: parent + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + let minutes = Math.floor(Pomodoro.getPomodoroSecondsLeft / 60).toString().padStart(2, '0') + let seconds = Math.floor(Pomodoro.getPomodoroSecondsLeft % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}` + } + font.pixelSize: Appearance.font.pixelSize.hugeass + 4 + color: Appearance.m3colors.m3onSurface + } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.isPomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus") + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + } } } - StyledText { + // The Start/Stop and Reset buttons + ColumnLayout { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isPomodoroBreak ? Translation.tr("Break time") : Translation.tr("Focus time") - font.pixelSize: Appearance.font.pixelSize.largest - color: Appearance.m3colors.m3onSurface - } + spacing: 20 - DialogButton { - buttonText: Translation.tr("Reset") - Layout.preferredWidth: 90 - Layout.preferredHeight: 35 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.pomodoroReset() - background: Rectangle { - color: Appearance.m3colors.m3onError - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outline - border.width: 1 + RippleButton { + buttonText: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.togglePomodoro() + colBackground: Appearance.m3colors.m3onSecondary + colBackgroundHover: Appearance.m3colors.m3onSecondary + } + + RippleButton { + buttonText: Translation.tr("Reset") + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.pomodoroReset() + colBackground: Appearance.m3colors.m3onError + colBackgroundHover: Appearance.m3colors.m3onError } } } + // The sliders for adjusting duration ColumnLayout { Layout.alignment: Qt.AlignHCenter - spacing: 0 + spacing: 10 - StyledText { - text: Translation.tr("Focus Duration: %1 min").arg(Pomodoro.pomodoroWorkTime / 60) - color: Appearance.m3colors.m3onSurface + ConfigSpinBox { + text: Translation.tr("Focus Duration: ") + value: Pomodoro.pomodoroFocusTime / 60 + onValueChanged: { + Pomodoro.pomodoroFocusTime = value * 60 + Config.options.time.pomodoro.focus = value * 60 + } + Layout.alignment: Qt.AlignCenter } - Slider { - id: workTimeSlider - Layout.fillWidth: true - from: 5 - to: 120 - stepSize: 1 - value: Pomodoro.pomodoroWorkTime / 60 - onValueChanged: Pomodoro.pomodoroWorkTime = value * 60 - handle: Rectangle { - x: workTimeSlider.leftPadding + workTimeSlider.visualPosition * (workTimeSlider.availableWidth - width) - y: workTimeSlider.topPadding + (workTimeSlider.availableHeight - height) / 2 - implicitWidth: 20 - implicitHeight: 20 - radius: 10 - color: Appearance.m3colors.m3onSecondary - border.color: Appearance.m3colors.m3outline + + ConfigSpinBox { + text: Translation.tr("Break Duration:") + value: Pomodoro.pomodoroBreakTime / 60 + onValueChanged: { + Config.options.time.pomodoro.breaktime = value * 60 + Pomodoro.pomodoroBreakTime = value * 60 } } - StyledText { - text: Translation.tr("Break Duration: %1 min").arg(Pomodoro.pomodoroBreakTime / 60) - color: Appearance.m3colors.m3onSurface - } - Slider { - id: breakTimeSlider - Layout.fillWidth: true - from: 1 - to: 60 - stepSize: 1 - value: Pomodoro.pomodoroBreakTime / 60 - onValueChanged: Pomodoro.pomodoroBreakTime = value * 60 - handle: Rectangle { - x: breakTimeSlider.leftPadding + breakTimeSlider.visualPosition * (breakTimeSlider.availableWidth - width) - y: breakTimeSlider.topPadding + (breakTimeSlider.availableHeight - height) / 2 - implicitWidth: 20 - implicitHeight: 20 - radius: 10 - color: Appearance.m3colors.m3onSecondary - border.color: Appearance.m3colors.m3outline + ConfigSpinBox { + text: Translation.tr("Long Break Duration:") + value: Pomodoro.pomodoroLongBreakTime / 60 + onValueChanged:{ + Pomodoro.pomodoroLongBreakTime = value * 60 + Config.options.time.pomodoro.longbreak = value * 60 } } } @@ -276,7 +276,13 @@ Item { StyledText { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.timeFormattedStopwatch() + text: { + let totalSeconds = Math.floor(Pomodoro.stopwatchTime) + let hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0') + let minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${hours}:${minutes}:${seconds}` + } font.pixelSize: 50 color: Appearance.m3colors.m3onSurface } @@ -302,7 +308,7 @@ Item { StyledText { Layout.alignment: Qt.AlignHCenter text: Translation.tr("Stopwatch") - font.pixelSize: Appearance.font.pixelSize.largest + font.pixelSize: Appearance.font.pixelSize.large color: Appearance.m3colors.m3onSurface } @@ -324,33 +330,4 @@ Item { } } } - - // + FAB - StyledRectangularShadow { - target: fabButton - radius: fabButton.buttonRadius - blur: 0.6 * Appearance.sizes.elevationMargin - } - FloatingActionButton { - id: fabButton - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.rightMargin: root.fabMargins - anchors.bottomMargin: root.fabMargins - - onClicked: { - if (currentTab === 0) { - Pomodoro.togglePomodoro() - } else { - Pomodoro.toggleStopwatch() - } - } - - contentItem: MaterialSymbol { - text: (currentTab === 0 && Pomodoro.isPomodoroRunning) || (currentTab === 1 && Pomodoro.isStopwatchRunning) ? "pause" : "play_arrow" - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.huge - color: Appearance.m3colors.m3onPrimaryContainer - } - } -} \ No newline at end of file +} diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index e29ef25ab..5244e5382 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -1,10 +1,12 @@ pragma Singleton pragma ComponentBehavior: Bound +import qs import qs.modules.common -import Quickshell -import Quickshell.Io -import QtQuick + +import Quickshell; +import Quickshell.Io; +import QtQuick; /** * Simple Pomodoro time manager. @@ -12,58 +14,82 @@ import QtQuick Singleton { id: root - property int pomodoroWorkTime: 25 * 60 // 25 minutes in seconds - property int pomodoroBreakTime: 5 * 60 // 5 minutes in seconds - property int pomodoroTime: pomodoroWorkTime - property bool isPomodoroRunning: false + // TODO: read these values from a config file. + property int pomodoroFocusTime: Config.options.time.pomodoro.focus + property int pomodoroBreakTime: Config.options.time.pomodoro.breaktime + property int pomodoroLongBreakTime: Config.options.time.pomodoro.longbreak + property int pomodoroLongBreakCycle: Config.options.time.pomodoro.cycle + + property int pomodoroTimeLeft: pomodoroFocusTime + property int getPomodoroSecondsLeft: pomodoroFocusTime + property int pomodoroTimeStarted: getCurrentTime() // The time pomodoro was last Resumed property bool isPomodoroBreak: false + property bool isPomodoroRunning: false + property int pomodoroCycle: 1 + property int stopwatchTime: 0 property bool isStopwatchRunning: false + // Pause and Resume button function togglePomodoro() { - isPomodoroRunning = !isPomodoroRunning; - } - - function toggleStopwatch() { - isStopwatchRunning = !isStopwatchRunning; - } - - function pomodoroReset() { - pomodoroTime = pomodoroWorkTime; - isPomodoroRunning = false; - isPomodoroBreak = false; - } - - function stopwatchReset() { - stopwatchTime = 0; - isStopwatchRunning = false; - } - - function tickSecond() { - if (pomodoroTime > 0) { - pomodoroTime--; - } else { - isPomodoroBreak = !isPomodoroBreak; - pomodoroTime = isPomodoroBreak ? pomodoroBreakTime : pomodoroWorkTime; - if (isPomodoroBreak) { - Quickshell.execDetached(["bash", "-c", `notify-send "☕ Short Break!" "Relax for ${Math.floor(pomodoroBreakTime / 60)} minutes."`]); - } else { - Quickshell.execDetached(["bash", "-c", `notify-send "🔴 Pomodoro started!" "Focus for ${Math.floor(pomodoroWorkTime / 60)} minutes."`]); - } + isPomodoroRunning = !isPomodoroRunning + if (isPomodoroRunning) { // Pressed Start button + pomodoroTimeStarted = getCurrentTime() + } else { // Pressed Pause button + pomodoroTimeLeft -= (getCurrentTime() - pomodoroTimeStarted) } } - function timeFormattedPomodoro() { - let minutes = Math.floor(pomodoroTime / 60); - let seconds = Math.floor(pomodoroTime % 60); - return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + // Reset button + function pomodoroReset() { + pomodoroTimeLeft = pomodoroFocusTime + getPomodoroSecondsLeft = pomodoroFocusTime + isPomodoroBreak = false + isPomodoroRunning = false } - function timeFormattedStopwatch() { - let totalSeconds = Math.floor(stopwatchTime); - let hours = Math.floor(totalSeconds / 3600); - let minutes = Math.floor((totalSeconds % 3600) / 60); - let seconds = Math.floor(totalSeconds % 60); - return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + function tickSecond() { + if (getCurrentTime() >= pomodoroTimeStarted + pomodoroTimeLeft) { + isPomodoroBreak = !isPomodoroBreak + pomodoroTimeStarted += pomodoroTimeLeft + pomodoroTimeLeft = isPomodoroBreak ? pomodoroBreakTime : pomodoroFocusTime + + if (isPomodoroBreak && pomodoroCycle % pomodoroLongBreakCycle == 0) { // isPomodoroLongBreak + Quickshell.execDetached([ + "notify-send", + Translation.tr("🌿 Long Break!"), + Translation.tr(`Relax for %1 minutes.`).arg(Math.floor(pomodoroLongBreakTime / 60)) + ]) + } else if(isPomodoroBreak){ + Quickshell.execDetached([ + "notify-send", + Translation.tr("☕ Short Break!"), + Translation.tr(`Relax for %1 minutes.`).arg(Math.floor(pomodoroBreakTime / 60)) + ]) + } else { + Quickshell.execDetached([ + "notify-send", + Translation.tr("🔴 Pomodoro started!"), + Translation.tr(`Focus for %1 minutes.`).arg(Math.floor(pomodoroFocusTime / 60)) + ]) + pomodoroCycle += 1 + } + } + + getPomodoroSecondsLeft = (pomodoroTimeStarted + pomodoroTimeLeft) - getCurrentTime() + } + + function getCurrentTime() { + return Math.floor(Date.now() / 1000) + } + + // Stopwatch functions + function toggleStopwatch() { + isStopwatchRunning = !isStopwatchRunning + } + + function stopwatchReset() { + stopwatchTime = 0 + isStopwatchRunning = false } }