From 0b9717c2a5f3fd044bfa38f8ae2b99c7c4773b51 Mon Sep 17 00:00:00 2001 From: Nyx <189459385+nyx-4@users.noreply.github.com> Date: Sat, 2 Aug 2025 22:17:56 +0500 Subject: [PATCH 01/23] Added pomodoro timer in sidebarRight, closes #1477 Signed-off-by: Nyx <189459385+nyx-4@users.noreply.github.com> --- .../sidebarRight/BottomWidgetGroup.qml | 13 +- .../sidebarRight/pomodoro/PomodoroWidget.qml | 356 ++++++++++++++++++ .config/quickshell/ii/services/Pomodoro.qml | 69 ++++ 3 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 .config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml create mode 100644 .config/quickshell/ii/services/Pomodoro.qml diff --git a/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml index 384011634..cd5385c75 100644 --- a/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml @@ -4,6 +4,7 @@ import qs import qs.services import "./calendar" import "./todo" +import "./pomodoro" import QtQuick import QtQuick.Layouts @@ -17,7 +18,8 @@ Rectangle { property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed property var tabs: [ {"type": "calendar", "name": Translation.tr("Calendar"), "icon": "calendar_month", "widget": calendarWidget}, - {"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": todoWidget} + {"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": todoWidget}, + {"type": "timer", "name": Translation.tr("Timer"), "icon": "schedule", "widget": pomodoroWidget}, ] Behavior on implicitHeight { @@ -238,4 +240,13 @@ Rectangle { anchors.margins: 5 } } + + // Pomodoro component + Component { + id: pomodoroWidget + PomodoroWidget { + anchors.fill: parent + anchors.margins: 5 + } + } } \ No newline at end of file diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml new file mode 100644 index 000000000..b39be28ce --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -0,0 +1,356 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + property int currentTab: 0 + property var tabButtonList: [ + {"name": Translation.tr("Pomodoro"), "icon": "timer_play"}, + {"name": Translation.tr("Stopwatch"), "icon": "timer"} + ] + property bool showDialog: false + property int dialogMargins: 20 + property int fabSize: 48 + property int fabMargins: 14 + + + // These are keybinds, make sure to change them. + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { + if (event.key === Qt.Key_PageDown) { + currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) + } else if (event.key === Qt.Key_PageUp) { + currentTab = Math.max(currentTab - 1, 0) + } + event.accepted = true + } else if (event.key === Qt.Key_Space && !showDialog) { + // Toggle start/pause with Space key + if (currentTab === 0) { + Pomodoro.togglePomodoro() + } else { + Pomodoro.toggleStopwatch() + } + event.accepted = true + } else if (event.key === Qt.Key_R && !showDialog) { + // Reset with R key + if (currentTab === 0) { + Pomodoro.pomodoroReset() + } else { + Pomodoro.stopwatchReset() + } + event.accepted = true + } else if (event.key === Qt.Key_Escape && showDialog) { + showDialog = false + event.accepted = true + } + } + + Timer { + id: pomodoroTimer + interval: 1000 + running: Pomodoro.isPomodoroRunning + repeat: true + onTriggered: Pomodoro.tickSecond() + } + + Timer { + id: stopwatchTimer + interval: 1000 + running: Pomodoro.isStopwatchRunning + repeat: true + onTriggered: { + Pomodoro.stopwatchTime += 1 + } + } + + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + TabBar { + id: tabBar + Layout.fillWidth: true + currentIndex: currentTab + onCurrentIndexChanged: currentTab = currentIndex + + background: Item { + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) + else if (event.angleDelta.y > 0) + tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + } + + Repeater { + model: root.tabButtonList + delegate: SecondaryTabButton { + selected: (index == currentTab) + buttonText: modelData.name + buttonIcon: modelData.icon + } + } + } + + Item { // Tab indicator + id: tabIndicator + Layout.fillWidth: true + height: 3 + property bool enableIndicatorAnimation: false + Connections { + target: root + function onCurrentTabChanged() { + tabIndicator.enableIndicatorAnimation = true + } + } + + Rectangle { + id: indicator + property int tabCount: root.tabButtonList.length + property real fullTabSize: root.width / tabCount + property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth + + implicitWidth: targetWidth + anchors { + top: parent.top + bottom: parent.bottom + } + + x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2 + + color: Appearance.colors.colPrimary + radius: Appearance.rounding.full + + Behavior on x { + enabled: tabIndicator.enableIndicatorAnimation + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + + Behavior on implicitWidth { + enabled: tabIndicator.enableIndicatorAnimation + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + } + } + + Rectangle { // Tabbar bottom border + id: tabBarBottomBorder + Layout.fillWidth: true + height: 1 + color: Appearance.colors.colOutlineVariant + } + + SwipeView { + id: swipeView + Layout.topMargin: 10 + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 10 + clip: true + currentIndex: currentTab + onCurrentIndexChanged: { + tabIndicator.enableIndicatorAnimation = true + currentTab = currentIndex + } + + // Pomodoro Timer Tab + Item { + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 18 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.timeFormattedPomodoro() + font.pixelSize: 50 + color: Appearance.m3colors.m3onSurface + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 20 + + 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 + } + } + + StyledText { + 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 + } + + 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 + } + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 0 + + StyledText { + text: Translation.tr("Focus Duration: %1 min").arg(Pomodoro.pomodoroWorkTime / 60) + color: Appearance.m3colors.m3onSurface + } + 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 + } + } + + 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 + } + } + } + } + } + + // Stopwatch Tab + Item { + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 18 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.timeFormattedStopwatch() + font.pixelSize: 50 + color: Appearance.m3colors.m3onSurface + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 20 + + DialogButton { + buttonText: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Translation.tr("Start") + Layout.preferredWidth: 90 + Layout.preferredHeight: 35 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.toggleStopwatch() + background: Rectangle { + color: Appearance.m3colors.m3onSecondary + radius: Appearance.rounding.normal + border.color: Appearance.m3colors.m3outline + border.width: 1 + } + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Stopwatch") + font.pixelSize: Appearance.font.pixelSize.largest + color: Appearance.m3colors.m3onSurface + } + + DialogButton { + buttonText: Translation.tr("Reset") + Layout.preferredWidth: 90 + Layout.preferredHeight: 35 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.stopwatchReset() + background: Rectangle { + color: Appearance.m3colors.m3onError + radius: Appearance.rounding.normal + border.color: Appearance.m3colors.m3outline + border.width: 1 + } + } + } + } + } + } + } + + // + 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 new file mode 100644 index 000000000..e29ef25ab --- /dev/null +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -0,0 +1,69 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import qs.modules.common +import Quickshell +import Quickshell.Io +import QtQuick + +/** + * Simple Pomodoro time manager. + */ +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 + property bool isPomodoroBreak: false + property int stopwatchTime: 0 + property bool isStopwatchRunning: false + + 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."`]); + } + } + } + + 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')}`; + } + + 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')}`; + } +} 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 02/23] 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 } } From bfb7ccffb5e13b941510d058ae5ae0a89eac95b6 Mon Sep 17 00:00:00 2001 From: Nyx <189459385+nyx-4@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:06:22 +0500 Subject: [PATCH 03/23] Reworked on Pomodoro's UI and added Stopwatch Signed-off-by: Nyx <189459385+nyx-4@users.noreply.github.com> --- .../quickshell/ii/modules/common/Config.qml | 5 +- .../sidebarRight/pomodoro/PomodoroWidget.qml | 329 +++++++++++++----- .config/quickshell/ii/services/Pomodoro.qml | 89 +++-- 3 files changed, 300 insertions(+), 123 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 3ee804fda..032fd441f 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -254,10 +254,11 @@ Singleton { property string format: "hh:mm" property string dateFormat: "ddd, dd/MM" property JsonObject pomodoro: JsonObject { - property int breaktime: 300 + property int breakTime: 300 property int cycle: 4 property int focus: 1500 - property int longbreak: 1200 + property int longBreak: 1200 + property bool running: false } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index ff39f1b6b..b3c360665 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -2,9 +2,13 @@ import qs import qs.services import qs.modules.common import qs.modules.common.widgets +import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell + + Item { id: root @@ -13,10 +17,8 @@ Item { {"name": Translation.tr("Pomodoro"), "icon": "timer_play"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} ] - property bool showDialog: false - property int dialogMargins: 20 - property int fabSize: 48 - property int fabMargins: 14 + property int lapsListItemPadding: 8 + property int lapsListItemSpacing: 5 // These are keybinds, make sure to change them. @@ -29,7 +31,7 @@ Item { } event.accepted = true } else if (event.key === Qt.Key_Space && !showDialog) { - // Toggle start/pause with Space key + // Toggle start/stop with Space key if (currentTab === 0) { Pomodoro.togglePomodoro() } else { @@ -52,20 +54,18 @@ Item { Timer { id: pomodoroTimer - interval: 1000 - running: Pomodoro.isPomodoroRunning + interval: 200 + running: Config.options.time.pomodoro.running repeat: true onTriggered: Pomodoro.tickSecond() } Timer { id: stopwatchTimer - interval: 1000 + interval: 10 running: Pomodoro.isStopwatchRunning repeat: true - onTriggered: { - Pomodoro.stopwatchTime += 1 - } + onTriggered: Pomodoro.tick10ms() } @@ -174,17 +174,19 @@ Item { CircularProgress { Layout.alignment: Qt.AlignHCenter lineWidth: 7 + gapAngle: Math.PI / 14 value: { let pomodoroTotalTime = Pomodoro.isPomodoroBreak ? Pomodoro.pomodoroBreakTime : Pomodoro.pomodoroFocusTime return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime } size: 125 - secondaryColor: Appearance.colors.colSecondaryContainer primaryColor: Appearance.m3colors.m3onSecondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer enableAnimation: true ColumnLayout { anchors.centerIn: parent + spacing: 0 StyledText { Layout.alignment: Qt.AlignHCenter @@ -208,20 +210,30 @@ Item { // The Start/Stop and Reset buttons ColumnLayout { Layout.alignment: Qt.AlignHCenter - spacing: 20 + spacing: 10 RippleButton { - buttonText: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isPomodoroRunning ? Translation.tr("Stop") : Translation.tr("Start") + color: Appearance.colors.colSecondary + } Layout.preferredHeight: 35 Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger onClicked: Pomodoro.togglePomodoro() - colBackground: Appearance.m3colors.m3onSecondary - colBackgroundHover: Appearance.m3colors.m3onSecondary + colBackground: Appearance.colors.colSecondaryContainer + colBackgroundHover: Appearance.colors.colSecondaryContainer } RippleButton { - buttonText: Translation.tr("Reset") + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Translation.tr("Reset") + color: Appearance.colors.colSecondary + } Layout.preferredHeight: 35 Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger @@ -232,36 +244,89 @@ Item { } } - // The sliders for adjusting duration + // The SpinBoxes for adjusting duration ColumnLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 10 + RowLayout { + Layout.fillWidth: true + spacing: 20 - ConfigSpinBox { - text: Translation.tr("Focus Duration: ") - value: Pomodoro.pomodoroFocusTime / 60 - onValueChanged: { - Pomodoro.pomodoroFocusTime = value * 60 - Config.options.time.pomodoro.focus = value * 60 + StyledText { + id: focusTextBox + Layout.leftMargin: focusSpinBox.implicitWidth / 2 - 7 + text: Translation.tr("Focus") } - Layout.alignment: Qt.AlignCenter - } - - ConfigSpinBox { - text: Translation.tr("Break Duration:") - value: Pomodoro.pomodoroBreakTime / 60 - onValueChanged: { - Config.options.time.pomodoro.breaktime = value * 60 - Pomodoro.pomodoroBreakTime = value * 60 + StyledText { + Layout.leftMargin: breakSpinBox.implicitWidth / 2 + 10 + text: Translation.tr("Break") } } - ConfigSpinBox { - text: Translation.tr("Long Break Duration:") - value: Pomodoro.pomodoroLongBreakTime / 60 - onValueChanged:{ - Pomodoro.pomodoroLongBreakTime = value * 60 - Config.options.time.pomodoro.longbreak = value * 60 + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 0 + + ConfigSpinBox { + id: focusSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.focus / 60 + onValueChanged: { + Config.options.time.pomodoro.focus = value * 60 + } + } + + ConfigSpinBox { + id: breakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.breakTime / 60 + onValueChanged: { + Config.options.time.pomodoro.breakTime = value * 60 + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 20 + + StyledText { + Layout.leftMargin: focusSpinBox.implicitWidth / 2 - 6 + text: Translation.tr("Cycle") + } + StyledText { + Layout.leftMargin: breakSpinBox.implicitWidth / 2 + text: Translation.tr("Long break") + } + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 0 + + ConfigSpinBox { + id: cycleSpinBox + spacing: 0 + from: 1 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.cycle + onValueChanged: { + Config.options.time.pomodoro.cycle = value + } + } + + ConfigSpinBox { + id: longBreakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.longBreak / 60 + onValueChanged: { + Config.options.time.pomodoro.longBreak = value * 60 + } } } } @@ -270,59 +335,155 @@ Item { // Stopwatch Tab Item { + Layout.fillWidth: true + ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter - spacing: 18 - - StyledText { - Layout.alignment: Qt.AlignHCenter - 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 - } + spacing: 20 + Layout.fillWidth: true RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 20 + spacing: 40 + // The Stopwatch circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 7 + gapAngle: Math.PI / 18 + value: { + return Pomodoro.stopwatchTime % 6000 / 6000 // The seconds in percent + } + size: 125 + primaryColor: Math.floor(Pomodoro.stopwatchTime / 6000) % 2 ? Appearance.colors.colSecondaryContainer : Appearance.m3colors.m3onSecondaryContainer + secondaryColor: Math.floor(Pomodoro.stopwatchTime / 6000) % 2 ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colSecondaryContainer + enableAnimation: false // The animation seems weird after each cycle - DialogButton { - buttonText: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Translation.tr("Start") - Layout.preferredWidth: 90 - Layout.preferredHeight: 35 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.toggleStopwatch() - background: Rectangle { - color: Appearance.m3colors.m3onSecondary - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outline - border.width: 1 + ColumnLayout { + anchors.centerIn: parent + spacing: 0 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + let totalSeconds = Math.floor(Pomodoro.stopwatchTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 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: { + return (Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0') + } + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + } } } - StyledText { + // The Start/Stop and Reset buttons + ColumnLayout { Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Stopwatch") - font.pixelSize: Appearance.font.pixelSize.large - color: Appearance.m3colors.m3onSurface - } + spacing: 10 - DialogButton { - buttonText: Translation.tr("Reset") - Layout.preferredWidth: 90 - Layout.preferredHeight: 35 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.stopwatchReset() - background: Rectangle { - color: Appearance.m3colors.m3onError - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outline - border.width: 1 + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isStopwatchRunning ? Translation.tr("Stop") : Translation.tr("Start") + color: Appearance.colors.colSecondary + } + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.toggleStopwatch() + colBackground: Appearance.colors.colSecondaryContainer + colBackgroundHover: Appearance.colors.colSecondaryContainer + } + + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: Appearance.colors.colSecondary + } + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.stopwatchReset() + colBackground: Appearance.m3colors.m3onError + colBackgroundHover: Appearance.m3colors.m3onError + } + } + } + + StyledListView { + id: lapsList + Layout.fillWidth: true + Layout.preferredHeight: contentHeight + spacing: lapsListItemSpacing + clip: true + model: Pomodoro.stopwatchLaps + + delegate: Rectangle { + width: lapsList.width + implicitHeight: lapsContentText.implicitHeight + lapsListItemPadding + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.small + + StyledText { + id: lapsContentText + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + leftPadding: lapsListItemPadding + rightPadding: lapsListItemPadding + topPadding: lapsListItemPadding / 2 + bottomPadding: lapsListItemPadding / 2 + font.pixelSize: Appearance.font.pixelSize.normal + + text: { + let lapIndex = index + 1 + let lapTime = modelData + // if (index > 0) { + // lapTime = modelData - Pomodoro.stopwatchLaps[index - 1] + // } + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}.${_10ms}` + } + } + + StyledText { + id: lapsDiffText + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + leftPadding: lapsListItemPadding + rightPadding: lapsListItemPadding * 2 + topPadding: lapsListItemPadding / 2 + bottomPadding: lapsListItemPadding / 2 + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colPrimary + + text: { + let lapTime = modelData + if (index != Pomodoro.stopwatchLaps.length - 1) { // except first lap + lapTime = modelData - Pomodoro.stopwatchLaps[index + 1] + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `+${minutes}:${seconds}.${_10ms}` + } else { + return `` // Nothing for first lap + } + } } } } diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 5244e5382..3387081d5 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -4,9 +4,9 @@ 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. @@ -14,29 +14,30 @@ import QtQuick; Singleton { id: root - // 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 pomodoroBreakTime: Config.options.time.pomodoro.breakTime + property int pomodoroLongBreakTime: Config.options.time.pomodoro.longBreak property int pomodoroLongBreakCycle: Config.options.time.pomodoro.cycle + property bool isPomodoroRunning: Config.options.time.pomodoro.running property int pomodoroTimeLeft: pomodoroFocusTime property int getPomodoroSecondsLeft: pomodoroFocusTime - property int pomodoroTimeStarted: getCurrentTime() // The time pomodoro was last Resumed + property int pomodoroTimeStarted: getCurrentTimeInSeconds() // 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 + property int stopwatchStartTime: 0 + property var stopwatchLaps: [] - // Pause and Resume button + // Start and Stop button function togglePomodoro() { - isPomodoroRunning = !isPomodoroRunning + Config.options.time.pomodoro.running = !isPomodoroRunning if (isPomodoroRunning) { // Pressed Start button - pomodoroTimeStarted = getCurrentTime() - } else { // Pressed Pause button - pomodoroTimeLeft -= (getCurrentTime() - pomodoroTimeStarted) + pomodoroTimeStarted = getCurrentTimeInSeconds() + } else { // Pressed Stop button + pomodoroTimeLeft -= (getCurrentTimeInSeconds() - pomodoroTimeStarted) } } @@ -45,51 +46,65 @@ Singleton { pomodoroTimeLeft = pomodoroFocusTime getPomodoroSecondsLeft = pomodoroFocusTime isPomodoroBreak = false - isPomodoroRunning = false + Config.options.time.pomodoro.running = false + pomodoroCycle = 1 } function tickSecond() { - if (getCurrentTime() >= pomodoroTimeStarted + pomodoroTimeLeft) { + if (getCurrentTimeInSeconds() >= pomodoroTimeStarted + pomodoroTimeLeft) { isPomodoroBreak = !isPomodoroBreak pomodoroTimeStarted += pomodoroTimeLeft - pomodoroTimeLeft = isPomodoroBreak ? pomodoroBreakTime : pomodoroFocusTime + 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)) - ]) + let notificationTitle, notificationMessage + + if (isPomodoroBreak && pomodoroCycle % pomodoroLongBreakCycle === 0) { // isPomodoroLongBreak + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(pomodoroLongBreakTime / 60)) + } else if (isPomodoroBreak) { + notificationMessage = 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)) - ]) + notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(pomodoroFocusTime / 60)) pomodoroCycle += 1 } + + Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) } - getPomodoroSecondsLeft = (pomodoroTimeStarted + pomodoroTimeLeft) - getCurrentTime() + // A nice abstraction for resume logic by updating the TimeStarted + getPomodoroSecondsLeft = (pomodoroTimeStarted + pomodoroTimeLeft) - getCurrentTimeInSeconds() } - function getCurrentTime() { + function getCurrentTimeInSeconds() { // Pomodoro uses Seconds return Math.floor(Date.now() / 1000) } + function getCurrentTimeIn10ms() { // Stopwatch uses 10ms + return Math.floor(Date.now() / 10) + } + + function tick10ms() { // stopwatch stores time in 10ms + stopwatchTime = getCurrentTimeIn10ms() - stopwatchStartTime + } + // Stopwatch functions function toggleStopwatch() { isStopwatchRunning = !isStopwatchRunning + if (isStopwatchRunning) { + // Resume from paused time by adjusting start time + stopwatchStartTime = getCurrentTimeIn10ms() - stopwatchTime + } } function stopwatchReset() { - stopwatchTime = 0 - isStopwatchRunning = false + if (isStopwatchRunning) { // Clicked on Lap + stopwatchLaps.unshift(stopwatchTime) // Last lap goes first on list + // Reassign to trigger onListChanged, idk copied from Todo.qml + root.stopwatchLaps = stopwatchLaps.slice(0) + } else { // Clicked on Reset + isStopwatchRunning = false + stopwatchTime = 0 + stopwatchStartTime = 0 + stopwatchLaps = [] + } } } From 91fee0d6e993b3bbe12396c9c75b50c04d7b4a1c Mon Sep 17 00:00:00 2001 From: Nyx <189459385+nyx-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:51:03 +0500 Subject: [PATCH 04/23] Minor tweaks and better variables names Signed-off-by: Nyx <189459385+nyx-4@users.noreply.github.com> --- .../quickshell/ii/modules/common/Config.qml | 3 +- .../sidebarRight/pomodoro/PomodoroWidget.qml | 46 ++++----- .config/quickshell/ii/services/Pomodoro.qml | 94 +++++++++++-------- 3 files changed, 82 insertions(+), 61 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 032fd441f..73930f938 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -254,11 +254,12 @@ Singleton { property string format: "hh:mm" property string dateFormat: "ddd, dd/MM" property JsonObject pomodoro: JsonObject { + property string alertSound: "" + property bool autoRun: false property int breakTime: 300 property int cycle: 4 property int focus: 1500 property int longBreak: 1200 - property bool running: false } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index b3c360665..6ab70cc7d 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -21,7 +21,7 @@ Item { property int lapsListItemSpacing: 5 - // These are keybinds, make sure to change them. + // These are keybinds for stopwatch and pomodoro Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageDown) { @@ -30,15 +30,15 @@ Item { currentTab = Math.max(currentTab - 1, 0) } event.accepted = true - } else if (event.key === Qt.Key_Space && !showDialog) { - // Toggle start/stop with Space key + } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { + // Toggle start/stop with Space or S key if (currentTab === 0) { Pomodoro.togglePomodoro() } else { Pomodoro.toggleStopwatch() } event.accepted = true - } else if (event.key === Qt.Key_R && !showDialog) { + } else if (event.key === Qt.Key_R) { // Reset with R key if (currentTab === 0) { Pomodoro.pomodoroReset() @@ -46,18 +46,18 @@ Item { Pomodoro.stopwatchReset() } event.accepted = true - } else if (event.key === Qt.Key_Escape && showDialog) { - showDialog = false - event.accepted = true + } else if (event.key === Qt.Key_L) { + // record Stopwatch lap with L key, regardless of current Tab + Pomodoro.recordLaps() } } Timer { id: pomodoroTimer interval: 200 - running: Config.options.time.pomodoro.running + running: Pomodoro.isPomodoroRunning repeat: true - onTriggered: Pomodoro.tickSecond() + onTriggered: Pomodoro.refreshPomodoro() } Timer { @@ -65,7 +65,7 @@ Item { interval: 10 running: Pomodoro.isStopwatchRunning repeat: true - onTriggered: Pomodoro.tick10ms() + onTriggered: Pomodoro.refreshStopwatch() } @@ -116,7 +116,7 @@ Item { Rectangle { id: indicator property int tabCount: root.tabButtonList.length - property real fullTabSize: root.width / tabCount + property real fullTabSize: root.width / tabCount; property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth implicitWidth: targetWidth @@ -176,7 +176,7 @@ Item { lineWidth: 7 gapAngle: Math.PI / 14 value: { - let pomodoroTotalTime = Pomodoro.isPomodoroBreak ? Pomodoro.pomodoroBreakTime : Pomodoro.pomodoroFocusTime + let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime } size: 125 @@ -200,7 +200,7 @@ Item { } StyledText { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isPomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus") + text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSurface } @@ -273,6 +273,10 @@ Item { value: Config.options.time.pomodoro.focus / 60 onValueChanged: { Config.options.time.pomodoro.focus = value * 60 + if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state + Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime + Pomodoro.timeLeft = Pomodoro.focusTime + } } } @@ -335,7 +339,9 @@ Item { // Stopwatch Tab Item { + id: stopwatchTab Layout.fillWidth: true + Layout.fillHeight: true ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter @@ -346,6 +352,7 @@ Item { spacing: 40 // The Stopwatch circle CircularProgress { + id: stopwatchClock Layout.alignment: Qt.AlignHCenter lineWidth: 7 gapAngle: Math.PI / 18 @@ -413,7 +420,7 @@ Item { Layout.preferredHeight: 35 Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.stopwatchReset() + onClicked: Pomodoro.stopwatchResetOrLaps() colBackground: Appearance.m3colors.m3onError colBackgroundHover: Appearance.m3colors.m3onError } @@ -423,9 +430,10 @@ Item { StyledListView { id: lapsList Layout.fillWidth: true - Layout.preferredHeight: contentHeight + Layout.preferredHeight: stopwatchTab.height - stopwatchClock.height - 20 spacing: lapsListItemSpacing clip: true + popin: true model: Pomodoro.stopwatchLaps delegate: Rectangle { @@ -446,11 +454,8 @@ Item { font.pixelSize: Appearance.font.pixelSize.normal text: { - let lapIndex = index + 1 let lapTime = modelData - // if (index > 0) { - // lapTime = modelData - Pomodoro.stopwatchLaps[index - 1] - // } + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') let totalSeconds = Math.floor(lapTime) / 100 let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') @@ -472,9 +477,8 @@ Item { color: Appearance.colors.colPrimary text: { - let lapTime = modelData if (index != Pomodoro.stopwatchLaps.length - 1) { // except first lap - lapTime = modelData - Pomodoro.stopwatchLaps[index + 1] + let lapTime = modelData - Pomodoro.stopwatchLaps[index + 1] let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') let totalSeconds = Math.floor(lapTime) / 100 let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 3387081d5..9b7b68da4 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -14,64 +14,72 @@ import QtQuick Singleton { id: root - 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 bool isPomodoroRunning: Config.options.time.pomodoro.running + property int focusTime: Config.options.time.pomodoro.focus + property int breakTime: Config.options.time.pomodoro.breakTime + property int longBreakTime: Config.options.time.pomodoro.longBreak + property int longBreakCycle: Config.options.time.pomodoro.cycle + property string alertSound: Config.options.time.pomodoro.alertSound - property int pomodoroTimeLeft: pomodoroFocusTime - property int getPomodoroSecondsLeft: pomodoroFocusTime - property int pomodoroTimeStarted: getCurrentTimeInSeconds() // The time pomodoro was last Resumed - property bool isPomodoroBreak: false + property bool isPomodoroRunning: Config.options.time.pomodoro.autoRun + property bool isBreak: false + property bool isPomodoroReset: !isPomodoroRunning + property int timeLeft: focusTime + property int getPomodoroSecondsLeft: focusTime + property int pomodoroStartTime: getCurrentTimeInSeconds() // The time pomodoro was last Resumed property int pomodoroCycle: 1 - property int stopwatchTime: 0 property bool isStopwatchRunning: false - property int stopwatchStartTime: 0 + property int stopwatchTime: 0 + property int stopwatchStart: 0 property var stopwatchLaps: [] // Start and Stop button function togglePomodoro() { - Config.options.time.pomodoro.running = !isPomodoroRunning + isPomodoroReset = false + isPomodoroRunning = !isPomodoroRunning if (isPomodoroRunning) { // Pressed Start button - pomodoroTimeStarted = getCurrentTimeInSeconds() + pomodoroStartTime = getCurrentTimeInSeconds() } else { // Pressed Stop button - pomodoroTimeLeft -= (getCurrentTimeInSeconds() - pomodoroTimeStarted) + timeLeft -= (getCurrentTimeInSeconds() - pomodoroStartTime) } } // Reset button function pomodoroReset() { - pomodoroTimeLeft = pomodoroFocusTime - getPomodoroSecondsLeft = pomodoroFocusTime - isPomodoroBreak = false - Config.options.time.pomodoro.running = false + isPomodoroRunning = false + isBreak = false + isPomodoroReset = true + timeLeft = focusTime + pomodoroStartTime = getCurrentTimeInSeconds() pomodoroCycle = 1 + refreshPomodoro() } - function tickSecond() { - if (getCurrentTimeInSeconds() >= pomodoroTimeStarted + pomodoroTimeLeft) { - isPomodoroBreak = !isPomodoroBreak - pomodoroTimeStarted += pomodoroTimeLeft - pomodoroTimeLeft = isPomodoroBreak ? pomodoroBreakTime : pomodoroFocusTime + function refreshPomodoro() { + if (getCurrentTimeInSeconds() >= pomodoroStartTime + timeLeft) { + isBreak = !isBreak + pomodoroStartTime += timeLeft + timeLeft = isBreak ? breakTime : focusTime let notificationTitle, notificationMessage - if (isPomodoroBreak && pomodoroCycle % pomodoroLongBreakCycle === 0) { // isPomodoroLongBreak - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(pomodoroLongBreakTime / 60)) - } else if (isPomodoroBreak) { - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(pomodoroBreakTime / 60)) + if (isBreak && pomodoroCycle % longBreakCycle === 0) { // isPomodoroLongBreak + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)) + } else if (isBreak) { + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)) } else { - notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(pomodoroFocusTime / 60)) + notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)) pomodoroCycle += 1 } Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) + if (alertSound) { // Play sound only if alertSound is explicitly specified + Quickshell.execDetached(["bash", "-c", `ffplay -nodisp -autoexit ${alertSound}`]) + } } // A nice abstraction for resume logic by updating the TimeStarted - getPomodoroSecondsLeft = (pomodoroTimeStarted + pomodoroTimeLeft) - getCurrentTimeInSeconds() + getPomodoroSecondsLeft = (pomodoroStartTime + timeLeft) - getCurrentTimeInSeconds() } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds @@ -82,8 +90,8 @@ Singleton { return Math.floor(Date.now() / 10) } - function tick10ms() { // stopwatch stores time in 10ms - stopwatchTime = getCurrentTimeIn10ms() - stopwatchStartTime + function refreshStopwatch() { // stopwatch stores time in 10ms + stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart } // Stopwatch functions @@ -91,20 +99,28 @@ Singleton { isStopwatchRunning = !isStopwatchRunning if (isStopwatchRunning) { // Resume from paused time by adjusting start time - stopwatchStartTime = getCurrentTimeIn10ms() - stopwatchTime + stopwatchStart = getCurrentTimeIn10ms() - stopwatchTime + } + } + + function stopwatchResetOrLaps() { + if (isStopwatchRunning) { // Clicked on Lap + recordLaps() + } else { // Clicked on Reset + stopwatchReset() } } function stopwatchReset() { - if (isStopwatchRunning) { // Clicked on Lap + isStopwatchRunning = false + stopwatchTime = 0 + stopwatchStart = 0 + stopwatchLaps = [] + } + + function recordLaps() { stopwatchLaps.unshift(stopwatchTime) // Last lap goes first on list // Reassign to trigger onListChanged, idk copied from Todo.qml root.stopwatchLaps = stopwatchLaps.slice(0) - } else { // Clicked on Reset - isStopwatchRunning = false - stopwatchTime = 0 - stopwatchStartTime = 0 - stopwatchLaps = [] - } } } From 91dae8ad85c816a0b3eebe14e01d2c2a67b56ef9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 10:42:50 +0700 Subject: [PATCH 05/23] pomodoro widget: use grid instead of row/col spam --- .../sidebarRight/pomodoro/PomodoroWidget.qml | 144 ++++++++---------- 1 file changed, 65 insertions(+), 79 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 6ab70cc7d..446a32977 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -8,8 +8,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell - - Item { id: root property int currentTab: 0 @@ -245,92 +243,80 @@ Item { } // The SpinBoxes for adjusting duration - ColumnLayout { - RowLayout { - Layout.fillWidth: true - spacing: 20 + GridLayout { + Layout.alignment: Qt.AlignHCenter + columns: 2 + uniformCellWidths: true + columnSpacing: 20 + rowSpacing: 6 - StyledText { - id: focusTextBox - Layout.leftMargin: focusSpinBox.implicitWidth / 2 - 7 - text: Translation.tr("Focus") - } - StyledText { - Layout.leftMargin: breakSpinBox.implicitWidth / 2 + 10 - text: Translation.tr("Break") - } - } - - RowLayout { + StyledText { Layout.alignment: Qt.AlignHCenter - spacing: 0 - - ConfigSpinBox { - id: focusSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.focus / 60 - onValueChanged: { - Config.options.time.pomodoro.focus = value * 60 - if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime - Pomodoro.timeLeft = Pomodoro.focusTime - } - } - } - - ConfigSpinBox { - id: breakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.breakTime / 60 - onValueChanged: { - Config.options.time.pomodoro.breakTime = value * 60 - } - } + text: Translation.tr("Focus") } - RowLayout { - Layout.fillWidth: true - spacing: 20 - - StyledText { - Layout.leftMargin: focusSpinBox.implicitWidth / 2 - 6 - text: Translation.tr("Cycle") - } - StyledText { - Layout.leftMargin: breakSpinBox.implicitWidth / 2 - text: Translation.tr("Long break") - } - } - - RowLayout { + StyledText { Layout.alignment: Qt.AlignHCenter - spacing: 0 + text: Translation.tr("Break") + } - ConfigSpinBox { - id: cycleSpinBox - spacing: 0 - from: 1 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.cycle - onValueChanged: { - Config.options.time.pomodoro.cycle = value + ConfigSpinBox { + id: focusSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.focus / 60 + onValueChanged: { + Config.options.time.pomodoro.focus = value * 60 + if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state + Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime + Pomodoro.timeLeft = Pomodoro.focusTime } } + } - ConfigSpinBox { - id: longBreakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.longBreak / 60 - onValueChanged: { - Config.options.time.pomodoro.longBreak = value * 60 - } + ConfigSpinBox { + id: breakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.breakTime / 60 + onValueChanged: { + Config.options.time.pomodoro.breakTime = value * 60 + } + } + + StyledText { + Layout.topMargin: 6 + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Cycle") + } + StyledText { + Layout.topMargin: 6 + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Long break") + } + + ConfigSpinBox { + id: cycleSpinBox + spacing: 0 + from: 1 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.cycle + onValueChanged: { + Config.options.time.pomodoro.cycle = value + } + } + + ConfigSpinBox { + id: longBreakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.longBreak / 60 + onValueChanged: { + Config.options.time.pomodoro.longBreak = value * 60 } } } From 9bc4ff16a7b3aa502776658fc403deecdc531c9e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 12:54:37 +0700 Subject: [PATCH 06/23] make pomodoro timer persistent --- .config/quickshell/ii/modules/common/Config.qml | 1 - .../quickshell/ii/modules/common/Persistent.qml | 7 +++++++ .config/quickshell/ii/services/Pomodoro.qml | 14 +++++++------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 73930f938..9580c3bc0 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -255,7 +255,6 @@ Singleton { property string dateFormat: "ddd, dd/MM" property JsonObject pomodoro: JsonObject { property string alertSound: "" - property bool autoRun: false property int breakTime: 300 property int cycle: 4 property int focus: 1500 diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index abd062d73..a650dc0bb 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -44,6 +44,13 @@ Singleton { property bool allowNsfw: false property string provider: "yandere" } + + property JsonObject timer: JsonObject { + property JsonObject pomodoro: JsonObject { + property bool running: false + property int start: 0 + } + } } } } diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 9b7b68da4..073280d65 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -20,12 +20,12 @@ Singleton { property int longBreakCycle: Config.options.time.pomodoro.cycle property string alertSound: Config.options.time.pomodoro.alertSound - property bool isPomodoroRunning: Config.options.time.pomodoro.autoRun + property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running property bool isBreak: false property bool isPomodoroReset: !isPomodoroRunning property int timeLeft: focusTime property int getPomodoroSecondsLeft: focusTime - property int pomodoroStartTime: getCurrentTimeInSeconds() // The time pomodoro was last Resumed + property int pomodoroStartTime: Persistent.states.timer.pomodoro.start property int pomodoroCycle: 1 property bool isStopwatchRunning: false @@ -36,9 +36,9 @@ Singleton { // Start and Stop button function togglePomodoro() { isPomodoroReset = false - isPomodoroRunning = !isPomodoroRunning + Persistent.states.timer.pomodoro.running = !isPomodoroRunning if (isPomodoroRunning) { // Pressed Start button - pomodoroStartTime = getCurrentTimeInSeconds() + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() } else { // Pressed Stop button timeLeft -= (getCurrentTimeInSeconds() - pomodoroStartTime) } @@ -46,11 +46,11 @@ Singleton { // Reset button function pomodoroReset() { - isPomodoroRunning = false + Persistent.states.timer.pomodoro.running = false isBreak = false isPomodoroReset = true timeLeft = focusTime - pomodoroStartTime = getCurrentTimeInSeconds() + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() pomodoroCycle = 1 refreshPomodoro() } @@ -58,7 +58,7 @@ Singleton { function refreshPomodoro() { if (getCurrentTimeInSeconds() >= pomodoroStartTime + timeLeft) { isBreak = !isBreak - pomodoroStartTime += timeLeft + Persistent.states.timer.pomodoro.start += timeLeft timeLeft = isBreak ? breakTime : focusTime let notificationTitle, notificationMessage From de759b5120004806323955493e163ce97e604750 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 12:54:53 +0700 Subject: [PATCH 07/23] redesign stopwatch --- .../sidebarRight/pomodoro/PomodoroWidget.qml | 168 +++++++++--------- 1 file changed, 83 insertions(+), 85 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 446a32977..8e0d65f74 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -15,9 +15,6 @@ Item { {"name": Translation.tr("Pomodoro"), "icon": "timer_play"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} ] - property int lapsListItemPadding: 8 - property int lapsListItemSpacing: 5 - // These are keybinds for stopwatch and pomodoro Keys.onPressed: (event) => { @@ -214,7 +211,7 @@ Item { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Stop") : Translation.tr("Start") + text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") color: Appearance.colors.colSecondary } Layout.preferredHeight: 35 @@ -330,62 +327,53 @@ Item { Layout.fillHeight: true ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter + anchors { + fill: parent + leftMargin: 20 + rightMargin: 20 + } spacing: 20 - Layout.fillWidth: true - RowLayout { - spacing: 40 - // The Stopwatch circle - CircularProgress { - id: stopwatchClock + ColumnLayout { + spacing: 8 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + + RowLayout { // Elapsed + id: elapsedIndicator Layout.alignment: Qt.AlignHCenter - lineWidth: 7 - gapAngle: Math.PI / 18 - value: { - return Pomodoro.stopwatchTime % 6000 / 6000 // The seconds in percent - } - size: 125 - primaryColor: Math.floor(Pomodoro.stopwatchTime / 6000) % 2 ? Appearance.colors.colSecondaryContainer : Appearance.m3colors.m3onSecondaryContainer - secondaryColor: Math.floor(Pomodoro.stopwatchTime / 6000) % 2 ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colSecondaryContainer - enableAnimation: false // The animation seems weird after each cycle - - ColumnLayout { - anchors.centerIn: parent - spacing: 0 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: { - let totalSeconds = Math.floor(Pomodoro.stopwatchTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `${minutes}:${seconds}` - } - font.pixelSize: Appearance.font.pixelSize.hugeass + 4 - color: Appearance.m3colors.m3onSurface + spacing: 0 + StyledText { + Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface + text: { + let totalSeconds = Math.floor(Pomodoro.stopwatchTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}` } - StyledText { - Layout.alignment: Qt.AlignHCenter - text: { - return (Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0') - } - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3onSurface + } + StyledText { + Layout.fillWidth: true + font.pixelSize: 40 + color: Appearance.colors.colSubtext + text: { + return `:${(Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}` } } } // The Start/Stop and Reset buttons - ColumnLayout { + RowLayout { Layout.alignment: Qt.AlignHCenter - spacing: 10 + spacing: 4 RippleButton { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isStopwatchRunning ? Translation.tr("Stop") : Translation.tr("Start") + text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") color: Appearance.colors.colSecondary } Layout.preferredHeight: 35 @@ -413,65 +401,75 @@ Item { } } + // Laps StyledListView { id: lapsList Layout.fillWidth: true - Layout.preferredHeight: stopwatchTab.height - stopwatchClock.height - 20 + Layout.fillHeight: true spacing: lapsListItemSpacing clip: true popin: true - model: Pomodoro.stopwatchLaps + + model: ScriptModel { + values: Pomodoro.stopwatchLaps + } delegate: Rectangle { + id: lapItem + required property int index + required property var modelData + property var horizontalPadding: 10 + property var verticalPadding: 6 width: lapsList.width - implicitHeight: lapsContentText.implicitHeight + lapsListItemPadding + implicitHeight: lapRow.implicitHeight + verticalPadding * 2 + implicitWidth: lapRow.implicitWidth + horizontalPadding * 2 color: Appearance.colors.colLayer2 radius: Appearance.rounding.small - StyledText { - id: lapsContentText - anchors.left: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - leftPadding: lapsListItemPadding - rightPadding: lapsListItemPadding - topPadding: lapsListItemPadding / 2 - bottomPadding: lapsListItemPadding / 2 - font.pixelSize: Appearance.font.pixelSize.normal - - text: { - let lapTime = modelData - - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `${minutes}:${seconds}.${_10ms}` + RowLayout { + id: lapRow + anchors { + fill: parent + leftMargin: lapItem.horizontalPadding + rightMargin: lapItem.horizontalPadding + topMargin: lapItem.verticalPadding + bottomMargin: lapItem.verticalPadding } - } - StyledText { - id: lapsDiffText - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - leftPadding: lapsListItemPadding - rightPadding: lapsListItemPadding * 2 - topPadding: lapsListItemPadding / 2 - bottomPadding: lapsListItemPadding / 2 - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colPrimary + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colSubtext + text: `${Pomodoro.stopwatchLaps.length - lapItem.index}.` + } - text: { - if (index != Pomodoro.stopwatchLaps.length - 1) { // except first lap - let lapTime = modelData - Pomodoro.stopwatchLaps[index + 1] + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: { + let lapTime = lapItem.modelData let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') let totalSeconds = Math.floor(lapTime) / 100 let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `+${minutes}:${seconds}.${_10ms}` - } else { - return `` // Nothing for first lap + return `${minutes}:${seconds}.${_10ms}` + } + } + + Item { Layout.fillWidth: true } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colPrimary + text: { + if (lapItem.index != Pomodoro.stopwatchLaps.length - 1) { // except first lap + let lapTime = lapItem.modelData - Pomodoro.stopwatchLaps[lapItem.index + 1] + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` + } else { + return `` // Nothing for first lap + } } } } From 0ee9afba4f3f3efa4d93bc57570ac6eba8108741 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:28:17 +0700 Subject: [PATCH 08/23] pomodoro: move tabs to their own files, adjust colors --- .../ii/modules/common/Appearance.qml | 8 + .../modules/common/widgets/RippleButton.qml | 1 + .../sidebarRight/pomodoro/PomodoroTimer.qml | 176 ++++++++++ .../sidebarRight/pomodoro/PomodoroWidget.qml | 327 +----------------- .../sidebarRight/pomodoro/Stopwatch.qml | 170 +++++++++ 5 files changed, 359 insertions(+), 323 deletions(-) create mode 100644 .config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml create mode 100644 .config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml diff --git a/.config/quickshell/ii/modules/common/Appearance.qml b/.config/quickshell/ii/modules/common/Appearance.qml index 9c9374860..c16574e0f 100644 --- a/.config/quickshell/ii/modules/common/Appearance.qml +++ b/.config/quickshell/ii/modules/common/Appearance.qml @@ -146,6 +146,14 @@ Singleton { property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5) property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7) property color colOutlineVariant: m3colors.m3outlineVariant + property color colError: m3colors.m3error + property color colErrorHover: ColorUtils.mix(m3colors.m3error, colLayer1Hover, 0.85) + property color colErrorActive: ColorUtils.mix(m3colors.m3error, colLayer1Active, 0.7) + property color colOnError: m3colors.m3onError + property color colErrorContainer: m3colors.m3errorContainer + property color colErrorContainerHover: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.90) + property color colErrorContainerActive: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.70) + property color colOnErrorContainer: m3colors.m3onErrorContainer } rounding: QtObject { diff --git a/.config/quickshell/ii/modules/common/widgets/RippleButton.qml b/.config/quickshell/ii/modules/common/widgets/RippleButton.qml index 7487203ae..750e9a318 100644 --- a/.config/quickshell/ii/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/ii/modules/common/widgets/RippleButton.qml @@ -29,6 +29,7 @@ Button { property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" + opacity: root.enabled ? 1 : 0.4 property color buttonColor: root.enabled ? (root.toggled ? (root.hovered ? colBackgroundToggledHover : colBackgroundToggled) : diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml new file mode 100644 index 000000000..57bb4a9dc --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -0,0 +1,176 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Item { + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 20 + + RowLayout { + spacing: 40 + // The Pomodoro timer circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 7 + gapAngle: Math.PI / 14 + value: { + let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime + return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime + } + size: 125 + primaryColor: Appearance.m3colors.m3onSecondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer + enableAnimation: true + + ColumnLayout { + anchors.centerIn: parent + spacing: 0 + + 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.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + } + } + } + + // The Start/Stop and Reset buttons + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 10 + + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.getPomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + } + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.togglePomodoro() + colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + } + + RippleButton { + implicitHeight: 35 + implicitWidth: 90 + + onClicked: Pomodoro.pomodoroReset() + enabled: (Pomodoro.getPomodoroSecondsLeft < Pomodoro.focusTime) + + font.pixelSize: Appearance.font.pixelSize.larger + colBackground: Appearance.colors.colErrorContainer + colBackgroundHover: Appearance.colors.colErrorContainerHover + colRipple: Appearance.colors.colErrorContainerActive + + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Translation.tr("Reset") + color: Appearance.colors.colOnErrorContainer + } + } + } + } + + // The SpinBoxes for adjusting duration + GridLayout { + Layout.alignment: Qt.AlignHCenter + columns: 2 + uniformCellWidths: true + columnSpacing: 20 + rowSpacing: 4 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Focus") + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Break") + } + + ConfigSpinBox { + id: focusSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.focus / 60 + onValueChanged: { + Config.options.time.pomodoro.focus = value * 60 + if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state + Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime + Pomodoro.timeLeft = Pomodoro.focusTime + } + } + } + + ConfigSpinBox { + id: breakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.breakTime / 60 + onValueChanged: { + Config.options.time.pomodoro.breakTime = value * 60 + } + } + + StyledText { + Layout.topMargin: 6 + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Cycle") + } + StyledText { + Layout.topMargin: 6 + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Long break") + } + + ConfigSpinBox { + id: cycleSpinBox + spacing: 0 + from: 1 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.cycle + onValueChanged: { + Config.options.time.pomodoro.cycle = value + } + } + + ConfigSpinBox { + id: longBreakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.longBreak / 60 + onValueChanged: { + Config.options.time.pomodoro.longBreak = value * 60 + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 8e0d65f74..594533f72 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -2,17 +2,15 @@ import qs import qs.services import qs.modules.common import qs.modules.common.widgets -import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Quickshell Item { id: root property int currentTab: 0 property var tabButtonList: [ - {"name": Translation.tr("Pomodoro"), "icon": "timer_play"}, + {"name": Translation.tr("Pomodoro"), "icon": "search_activity"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} ] @@ -157,326 +155,9 @@ Item { currentTab = currentIndex } - // Pomodoro Timer Tab - Item { - ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter - spacing: 20 - - RowLayout { - spacing: 40 - // The Pomodoro timer circle - CircularProgress { - Layout.alignment: Qt.AlignHCenter - lineWidth: 7 - gapAngle: Math.PI / 14 - value: { - let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime - return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime - } - size: 125 - primaryColor: Appearance.m3colors.m3onSecondaryContainer - secondaryColor: Appearance.colors.colSecondaryContainer - enableAnimation: true - - ColumnLayout { - anchors.centerIn: parent - spacing: 0 - - 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.isBreak ? Translation.tr("Break") : Translation.tr("Focus") - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3onSurface - } - } - } - - // The Start/Stop and Reset buttons - ColumnLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 10 - - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") - color: Appearance.colors.colSecondary - } - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.togglePomodoro() - colBackground: Appearance.colors.colSecondaryContainer - colBackgroundHover: Appearance.colors.colSecondaryContainer - } - - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Translation.tr("Reset") - color: Appearance.colors.colSecondary - } - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.pomodoroReset() - colBackground: Appearance.m3colors.m3onError - colBackgroundHover: Appearance.m3colors.m3onError - } - } - } - - // The SpinBoxes for adjusting duration - GridLayout { - Layout.alignment: Qt.AlignHCenter - columns: 2 - uniformCellWidths: true - columnSpacing: 20 - rowSpacing: 6 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Focus") - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Break") - } - - ConfigSpinBox { - id: focusSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.focus / 60 - onValueChanged: { - Config.options.time.pomodoro.focus = value * 60 - if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime - Pomodoro.timeLeft = Pomodoro.focusTime - } - } - } - - ConfigSpinBox { - id: breakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.breakTime / 60 - onValueChanged: { - Config.options.time.pomodoro.breakTime = value * 60 - } - } - - StyledText { - Layout.topMargin: 6 - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Cycle") - } - StyledText { - Layout.topMargin: 6 - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Long break") - } - - ConfigSpinBox { - id: cycleSpinBox - spacing: 0 - from: 1 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.cycle - onValueChanged: { - Config.options.time.pomodoro.cycle = value - } - } - - ConfigSpinBox { - id: longBreakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.longBreak / 60 - onValueChanged: { - Config.options.time.pomodoro.longBreak = value * 60 - } - } - } - } - } - - // Stopwatch Tab - Item { - id: stopwatchTab - Layout.fillWidth: true - Layout.fillHeight: true - - ColumnLayout { - anchors { - fill: parent - leftMargin: 20 - rightMargin: 20 - } - spacing: 20 - - ColumnLayout { - spacing: 8 - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: false - - RowLayout { // Elapsed - id: elapsedIndicator - Layout.alignment: Qt.AlignHCenter - spacing: 0 - StyledText { - Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness - font.pixelSize: 40 - color: Appearance.m3colors.m3onSurface - text: { - let totalSeconds = Math.floor(Pomodoro.stopwatchTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `${minutes}:${seconds}` - } - } - StyledText { - Layout.fillWidth: true - font.pixelSize: 40 - color: Appearance.colors.colSubtext - text: { - return `:${(Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}` - } - } - } - - // The Start/Stop and Reset buttons - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 4 - - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") - color: Appearance.colors.colSecondary - } - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.toggleStopwatch() - colBackground: Appearance.colors.colSecondaryContainer - colBackgroundHover: Appearance.colors.colSecondaryContainer - } - - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") - color: Appearance.colors.colSecondary - } - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.stopwatchResetOrLaps() - colBackground: Appearance.m3colors.m3onError - colBackgroundHover: Appearance.m3colors.m3onError - } - } - } - - // Laps - StyledListView { - id: lapsList - Layout.fillWidth: true - Layout.fillHeight: true - spacing: lapsListItemSpacing - clip: true - popin: true - - model: ScriptModel { - values: Pomodoro.stopwatchLaps - } - - delegate: Rectangle { - id: lapItem - required property int index - required property var modelData - property var horizontalPadding: 10 - property var verticalPadding: 6 - width: lapsList.width - implicitHeight: lapRow.implicitHeight + verticalPadding * 2 - implicitWidth: lapRow.implicitWidth + horizontalPadding * 2 - color: Appearance.colors.colLayer2 - radius: Appearance.rounding.small - - RowLayout { - id: lapRow - anchors { - fill: parent - leftMargin: lapItem.horizontalPadding - rightMargin: lapItem.horizontalPadding - topMargin: lapItem.verticalPadding - bottomMargin: lapItem.verticalPadding - } - - StyledText { - font.pixelSize: Appearance.font.pixelSize.small - color: Appearance.colors.colSubtext - text: `${Pomodoro.stopwatchLaps.length - lapItem.index}.` - } - - StyledText { - font.pixelSize: Appearance.font.pixelSize.small - text: { - let lapTime = lapItem.modelData - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `${minutes}:${seconds}.${_10ms}` - } - } - - Item { Layout.fillWidth: true } - - StyledText { - font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.colors.colPrimary - text: { - if (lapItem.index != Pomodoro.stopwatchLaps.length - 1) { // except first lap - let lapTime = lapItem.modelData - Pomodoro.stopwatchLaps[lapItem.index + 1] - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` - } else { - return `` // Nothing for first lap - } - } - } - } - } - } - } - } + // Tabs + PomodoroTimer {} + Stopwatch {} } } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml new file mode 100644 index 000000000..90ea1ef78 --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -0,0 +1,170 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Item { + id: stopwatchTab + Layout.fillWidth: true + Layout.fillHeight: true + + ColumnLayout { + anchors { + fill: parent + leftMargin: 20 + rightMargin: 20 + } + spacing: 20 + + ColumnLayout { + spacing: 8 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + + RowLayout { // Elapsed + id: elapsedIndicator + Layout.alignment: Qt.AlignHCenter + spacing: 0 + StyledText { + Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface + text: { + let totalSeconds = Math.floor(Pomodoro.stopwatchTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}` + } + } + StyledText { + Layout.fillWidth: true + font.pixelSize: 40 + color: Appearance.colors.colSubtext + text: { + return `:${(Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}` + } + } + } + + // The Start/Stop and Reset buttons + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 4 + + RippleButton { + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + + onClicked: Pomodoro.toggleStopwatch() + + colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover + colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") + } + } + + RippleButton { + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + + onClicked: Pomodoro.stopwatchResetOrLaps() + enabled: Pomodoro.stopwatchTime !== 0 + + colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer + colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover + colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer + } + } + } + } + + // Laps + StyledListView { + id: lapsList + Layout.fillWidth: true + Layout.fillHeight: true + spacing: lapsListItemSpacing + clip: true + popin: true + + model: ScriptModel { + values: Pomodoro.stopwatchLaps + } + + delegate: Rectangle { + id: lapItem + required property int index + required property var modelData + property var horizontalPadding: 10 + property var verticalPadding: 6 + width: lapsList.width + implicitHeight: lapRow.implicitHeight + verticalPadding * 2 + implicitWidth: lapRow.implicitWidth + horizontalPadding * 2 + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.small + + RowLayout { + id: lapRow + anchors { + fill: parent + leftMargin: lapItem.horizontalPadding + rightMargin: lapItem.horizontalPadding + topMargin: lapItem.verticalPadding + bottomMargin: lapItem.verticalPadding + } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colSubtext + text: `${Pomodoro.stopwatchLaps.length - lapItem.index}.` + } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: { + let lapTime = lapItem.modelData + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}.${_10ms}` + } + } + + Item { Layout.fillWidth: true } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colPrimary + text: { + let lastTime = (lapItem.index != Pomodoro.stopwatchLaps.length - 1) ? Pomodoro.stopwatchLaps[lapItem.index + 1] : 0 + let lapTime = lapItem.modelData - lastTime + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` + } + } + } + } + } + } +} \ No newline at end of file From 903a9750339d3b7afcc022c9894b490cf2158f97 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:31:03 +0700 Subject: [PATCH 09/23] rename getPomodoroSecondsLeft -> pomodoroSecondsLeft --- .../modules/sidebarRight/pomodoro/PomodoroTimer.qml | 12 ++++++------ .config/quickshell/ii/services/Pomodoro.qml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index 57bb4a9dc..60a0cefbd 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -22,7 +22,7 @@ Item { gapAngle: Math.PI / 14 value: { let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime - return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime + return Pomodoro.pomodoroSecondsLeft / pomodoroTotalTime } size: 125 primaryColor: Appearance.m3colors.m3onSecondaryContainer @@ -36,8 +36,8 @@ Item { 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') + let minutes = Math.floor(Pomodoro.pomodoroSecondsLeft / 60).toString().padStart(2, '0') + let seconds = Math.floor(Pomodoro.pomodoroSecondsLeft % 60).toString().padStart(2, '0') return `${minutes}:${seconds}` } font.pixelSize: Appearance.font.pixelSize.hugeass + 4 @@ -61,7 +61,7 @@ Item { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.getPomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.pomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary } implicitHeight: 35 @@ -77,7 +77,7 @@ Item { implicitWidth: 90 onClicked: Pomodoro.pomodoroReset() - enabled: (Pomodoro.getPomodoroSecondsLeft < Pomodoro.focusTime) + enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.focusTime) font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer @@ -121,7 +121,7 @@ Item { onValueChanged: { Config.options.time.pomodoro.focus = value * 60 if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime + Pomodoro.pomodoroSecondsLeft = Pomodoro.focusTime Pomodoro.timeLeft = Pomodoro.focusTime } } diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 073280d65..5a08709b3 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -24,7 +24,7 @@ Singleton { property bool isBreak: false property bool isPomodoroReset: !isPomodoroRunning property int timeLeft: focusTime - property int getPomodoroSecondsLeft: focusTime + property int pomodoroSecondsLeft: focusTime property int pomodoroStartTime: Persistent.states.timer.pomodoro.start property int pomodoroCycle: 1 @@ -79,7 +79,7 @@ Singleton { } // A nice abstraction for resume logic by updating the TimeStarted - getPomodoroSecondsLeft = (pomodoroStartTime + timeLeft) - getCurrentTimeInSeconds() + pomodoroSecondsLeft = (pomodoroStartTime + timeLeft) - getCurrentTimeInSeconds() } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds From 1f4568d22f2e388c06b2b45756f6e1a60a7a45e3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 18:20:14 +0700 Subject: [PATCH 10/23] pomodoro: rename more for consistency --- .../ii/modules/common/Persistent.qml | 5 + .../sidebarRight/pomodoro/PomodoroTimer.qml | 161 ++++++++++-------- .../sidebarRight/pomodoro/PomodoroWidget.qml | 2 +- .../sidebarRight/pomodoro/Stopwatch.qml | 29 ++-- .config/quickshell/ii/services/Pomodoro.qml | 44 ++--- 5 files changed, 135 insertions(+), 106 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index a650dc0bb..f0cbcafab 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -50,6 +50,11 @@ Singleton { property bool running: false property int start: 0 } + property JsonObject stopwatch: JsonObject { + property bool running: false + property int start: 0 + property list laps: [] + } } } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index 60a0cefbd..1534adb1f 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -76,7 +76,7 @@ Item { implicitHeight: 35 implicitWidth: 90 - onClicked: Pomodoro.pomodoroReset() + onClicked: Pomodoro.resetPomodoro() enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.focusTime) font.pixelSize: Appearance.font.pixelSize.larger @@ -95,82 +95,101 @@ Item { } // The SpinBoxes for adjusting duration - GridLayout { - Layout.alignment: Qt.AlignHCenter - columns: 2 - uniformCellWidths: true - columnSpacing: 20 - rowSpacing: 4 + // GridLayout { + // Layout.alignment: Qt.AlignHCenter + // columns: 2 + // uniformCellWidths: true + // columnSpacing: 20 + // rowSpacing: 4 - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Focus") - } + // StyledText { + // Layout.alignment: Qt.AlignHCenter + // text: Translation.tr("Focus") + // } - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Break") - } + // StyledText { + // Layout.alignment: Qt.AlignHCenter + // text: Translation.tr("Break") + // } - ConfigSpinBox { - id: focusSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.focus / 60 - onValueChanged: { - Config.options.time.pomodoro.focus = value * 60 - if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - Pomodoro.pomodoroSecondsLeft = Pomodoro.focusTime - Pomodoro.timeLeft = Pomodoro.focusTime - } - } - } + // ConfigSpinBox { + // id: focusSpinBox + // spacing: 0 + // Layout.leftMargin: 0 + // Layout.rightMargin: 0 + // from: 0 + // to: 120 + // Connections { + // target: Config + // function onReadyChanged() { + // focusSpinBox.valueChanged() + // } + // } + // value: { + // console.log("New focus time: " + (Config.options.time.pomodoro.focus / 60)) + // return Config.options.time.pomodoro.focus / 60 + // } + // onValueChanged: { + // console.log("New focus time is " + value + " minutes, Config is ready:", Config.ready) + // if (!Config.ready) return; + // console.log("Setting focus time to " + value + " minutes") + // Config.options.time.pomodoro.focus = value * 60 + // if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state + // Pomodoro.pomodoroSecondsLeft = Pomodoro.focusTime + // Pomodoro.timeLeft = Pomodoro.focusTime + // } + // } + // } - ConfigSpinBox { - id: breakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.breakTime / 60 - onValueChanged: { - Config.options.time.pomodoro.breakTime = value * 60 - } - } + // ConfigSpinBox { + // id: breakSpinBox + // value: Config.options.time.pomodoro.breakTime / 60 + // spacing: 0 + // from: 0 + // to: 120 + // Layout.leftMargin: 0 + // Layout.rightMargin: 0 + // onValueChanged: { + // Config.options.time.pomodoro.breakTime = value * 60 + // } + // } - StyledText { - Layout.topMargin: 6 - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Cycle") - } - StyledText { - Layout.topMargin: 6 - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Long break") - } + // StyledText { + // Layout.topMargin: 6 + // Layout.alignment: Qt.AlignHCenter + // text: Translation.tr("Cycle") + // } + // StyledText { + // Layout.topMargin: 6 + // Layout.alignment: Qt.AlignHCenter + // text: Translation.tr("Long break") + // } - ConfigSpinBox { - id: cycleSpinBox - spacing: 0 - from: 1 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.cycle - onValueChanged: { - Config.options.time.pomodoro.cycle = value - } - } + // ConfigSpinBox { + // id: cycleSpinBox + // value: Config.options.time.pomodoro.cycle + // spacing: 0 + // from: 1 + // to: 20 + // Layout.leftMargin: 0 + // Layout.rightMargin: 0 + // onValueChanged: { + // Config.options.time.pomodoro.cycle = value + // } + // } - ConfigSpinBox { - id: longBreakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.longBreak / 60 - onValueChanged: { - Config.options.time.pomodoro.longBreak = value * 60 - } - } - } + // ConfigSpinBox { + // id: longBreakSpinBox + // spacing: 0 + // Layout.leftMargin: 0 + // Layout.rightMargin: 0 + // value: Config.options.time.pomodoro.longBreak / 60 + // from: 0 + // to: 120 + // onValueChanged: { + // Config.options.time.pomodoro.longBreak = value * 60 + // } + // } + // } } } \ No newline at end of file diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 594533f72..242b1ceb8 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -34,7 +34,7 @@ Item { } else if (event.key === Qt.Key_R) { // Reset with R key if (currentTab === 0) { - Pomodoro.pomodoroReset() + Pomodoro.resetPomodoro() } else { Pomodoro.stopwatchReset() } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index 90ea1ef78..8f5d65295 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -31,7 +31,7 @@ Item { Layout.alignment: Qt.AlignHCenter spacing: 0 StyledText { - Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness + // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness font.pixelSize: 40 color: Appearance.m3colors.m3onSurface text: { @@ -100,12 +100,12 @@ Item { id: lapsList Layout.fillWidth: true Layout.fillHeight: true - spacing: lapsListItemSpacing + spacing: 4 clip: true popin: true model: ScriptModel { - values: Pomodoro.stopwatchLaps + values: Pomodoro.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i]) } delegate: Rectangle { @@ -139,11 +139,11 @@ Item { StyledText { font.pixelSize: Appearance.font.pixelSize.small text: { - let lapTime = lapItem.modelData - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + const lapTime = lapItem.modelData + const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + const totalSeconds = Math.floor(lapTime) / 100 + const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') return `${minutes}:${seconds}.${_10ms}` } } @@ -154,12 +154,13 @@ Item { font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colPrimary text: { - let lastTime = (lapItem.index != Pomodoro.stopwatchLaps.length - 1) ? Pomodoro.stopwatchLaps[lapItem.index + 1] : 0 - let lapTime = lapItem.modelData - lastTime - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + const originalIndex = Pomodoro.stopwatchLaps.length - lapItem.index - 1 + const lastTime = originalIndex > 0 ? Pomodoro.stopwatchLaps[originalIndex - 1] : 0 + const lapTime = lapItem.modelData - lastTime + const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + const totalSeconds = Math.floor(lapTime) / 100 + const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` } } diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 5a08709b3..87fe046be 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -25,13 +25,17 @@ Singleton { property bool isPomodoroReset: !isPomodoroRunning property int timeLeft: focusTime property int pomodoroSecondsLeft: focusTime - property int pomodoroStartTime: Persistent.states.timer.pomodoro.start + property int pomodoroStart: Persistent.states.timer.pomodoro.start property int pomodoroCycle: 1 - property bool isStopwatchRunning: false + property bool isStopwatchRunning: Persistent.states.timer.stopwatch.running property int stopwatchTime: 0 - property int stopwatchStart: 0 - property var stopwatchLaps: [] + property int stopwatchStart: Persistent.states.timer.stopwatch.start + property var stopwatchLaps: Persistent.states.timer.stopwatch.laps + + Component.onCompleted: { + if (!isStopwatchRunning) stopwatchReset() + } // Start and Stop button function togglePomodoro() { @@ -40,12 +44,12 @@ Singleton { if (isPomodoroRunning) { // Pressed Start button Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() } else { // Pressed Stop button - timeLeft -= (getCurrentTimeInSeconds() - pomodoroStartTime) + timeLeft -= (getCurrentTimeInSeconds() - pomodoroStart) } } // Reset button - function pomodoroReset() { + function resetPomodoro() { Persistent.states.timer.pomodoro.running = false isBreak = false isPomodoroReset = true @@ -56,7 +60,7 @@ Singleton { } function refreshPomodoro() { - if (getCurrentTimeInSeconds() >= pomodoroStartTime + timeLeft) { + if (getCurrentTimeInSeconds() >= pomodoroStart + timeLeft) { isBreak = !isBreak Persistent.states.timer.pomodoro.start += timeLeft timeLeft = isBreak ? breakTime : focusTime @@ -74,12 +78,12 @@ Singleton { Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) if (alertSound) { // Play sound only if alertSound is explicitly specified - Quickshell.execDetached(["bash", "-c", `ffplay -nodisp -autoexit ${alertSound}`]) + Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]) } } // A nice abstraction for resume logic by updating the TimeStarted - pomodoroSecondsLeft = (pomodoroStartTime + timeLeft) - getCurrentTimeInSeconds() + pomodoroSecondsLeft = (pomodoroStart + timeLeft) - getCurrentTimeInSeconds() } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds @@ -96,31 +100,31 @@ Singleton { // Stopwatch functions function toggleStopwatch() { - isStopwatchRunning = !isStopwatchRunning + Persistent.states.timer.stopwatch.running = !isStopwatchRunning if (isStopwatchRunning) { // Resume from paused time by adjusting start time - stopwatchStart = getCurrentTimeIn10ms() - stopwatchTime + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime } } function stopwatchResetOrLaps() { - if (isStopwatchRunning) { // Clicked on Lap + if (isStopwatchRunning) { recordLaps() - } else { // Clicked on Reset + } else { stopwatchReset() } } function stopwatchReset() { - isStopwatchRunning = false - stopwatchTime = 0 - stopwatchStart = 0 - stopwatchLaps = [] + Persistent.states.timer.stopwatch.running = false + stopwatchTime = 0 + stopwatchStart = getCurrentTimeIn10ms() + Persistent.states.timer.stopwatch.laps = [] } function recordLaps() { - stopwatchLaps.unshift(stopwatchTime) // Last lap goes first on list - // Reassign to trigger onListChanged, idk copied from Todo.qml - root.stopwatchLaps = stopwatchLaps.slice(0) + Persistent.states.timer.stopwatch.laps.push(stopwatchTime) + // Reassign to trigger change + // Persistent.states.timer.stopwatch.laps = Persistent.states.timer.stopwatch.laps.slice(0) } } From 5bf80dae4ed5844f54d8ecfc175e18c7e9c323c8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:45:26 +0700 Subject: [PATCH 11/23] pomodoro: move timers to service, specific button logic to widget --- .../ii/modules/common/Persistent.qml | 1 + .../sidebarRight/pomodoro/PomodoroWidget.qml | 17 ---- .../sidebarRight/pomodoro/Stopwatch.qml | 11 ++- .config/quickshell/ii/services/Pomodoro.qml | 99 +++++++++++-------- 4 files changed, 67 insertions(+), 61 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index f0cbcafab..876b2ef6a 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -49,6 +49,7 @@ Singleton { property JsonObject pomodoro: JsonObject { property bool running: false property int start: 0 + property bool isBreak: false } property JsonObject stopwatch: JsonObject { property bool running: false diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 242b1ceb8..64b16a9df 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -45,23 +45,6 @@ Item { } } - Timer { - id: pomodoroTimer - interval: 200 - running: Pomodoro.isPomodoroRunning - repeat: true - onTriggered: Pomodoro.refreshPomodoro() - } - - Timer { - id: stopwatchTimer - interval: 10 - running: Pomodoro.isStopwatchRunning - repeat: true - onTriggered: Pomodoro.refreshStopwatch() - } - - ColumnLayout { anchors.fill: parent spacing: 0 diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index 8f5d65295..f1c428ddf 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -61,7 +61,9 @@ Item { Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.toggleStopwatch() + onClicked: { + Pomodoro.toggleStopwatch() + } colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover @@ -79,7 +81,12 @@ Item { implicitWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.stopwatchResetOrLaps() + onClicked: { + if (Pomodoro.isStopwatchRunning) + Pomodoro.stopwatchRecordLap() + else + Pomodoro.stopwatchReset() + } enabled: Pomodoro.stopwatchTime !== 0 colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 87fe046be..dd60a9f4a 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -21,7 +21,7 @@ Singleton { property string alertSound: Config.options.time.pomodoro.alertSound property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running - property bool isBreak: false + property bool isBreak: Persistent.states.timer.pomodoro.isBreak property bool isPomodoroReset: !isPomodoroRunning property int timeLeft: focusTime property int pomodoroSecondsLeft: focusTime @@ -33,35 +33,24 @@ Singleton { property int stopwatchStart: Persistent.states.timer.stopwatch.start property var stopwatchLaps: Persistent.states.timer.stopwatch.laps + // General Component.onCompleted: { if (!isStopwatchRunning) stopwatchReset() } - // Start and Stop button - function togglePomodoro() { - isPomodoroReset = false - Persistent.states.timer.pomodoro.running = !isPomodoroRunning - if (isPomodoroRunning) { // Pressed Start button - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - } else { // Pressed Stop button - timeLeft -= (getCurrentTimeInSeconds() - pomodoroStart) - } + function getCurrentTimeInSeconds() { // Pomodoro uses Seconds + return Math.floor(Date.now() / 1000) } - // Reset button - function resetPomodoro() { - Persistent.states.timer.pomodoro.running = false - isBreak = false - isPomodoroReset = true - timeLeft = focusTime - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - pomodoroCycle = 1 - refreshPomodoro() + function getCurrentTimeIn10ms() { // Stopwatch uses 10ms + return Math.floor(Date.now() / 10) } + // Pomodoro function refreshPomodoro() { + // Work <-> break ? if (getCurrentTimeInSeconds() >= pomodoroStart + timeLeft) { - isBreak = !isBreak + Persistent.states.timer.pomodoro.isBreak = !isBreak Persistent.states.timer.pomodoro.start += timeLeft timeLeft = isBreak ? breakTime : focusTime @@ -86,45 +75,71 @@ Singleton { pomodoroSecondsLeft = (pomodoroStart + timeLeft) - getCurrentTimeInSeconds() } - function getCurrentTimeInSeconds() { // Pomodoro uses Seconds - return Math.floor(Date.now() / 1000) + Timer { + id: pomodoroTimer + interval: 200 + running: root.isPomodoroRunning + repeat: true + onTriggered: Pomodoro.refreshPomodoro() } - function getCurrentTimeIn10ms() { // Stopwatch uses 10ms - return Math.floor(Date.now() / 10) + function togglePomodoro() { + isPomodoroReset = false + Persistent.states.timer.pomodoro.running = !isPomodoroRunning + if (isPomodoroRunning) { // Pressed Start button + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + } else { // Pressed Stop button + timeLeft -= (getCurrentTimeInSeconds() - pomodoroStart) + } } - function refreshStopwatch() { // stopwatch stores time in 10ms + function resetPomodoro() { + Persistent.states.timer.pomodoro.running = false + Persistent.states.timer.pomodoro.isBreak = false + isPomodoroReset = true + timeLeft = focusTime + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroCycle = 1 + refreshPomodoro() + } + + // Stopwatch + function refreshStopwatch() { // Stopwatch stores time in 10ms stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart } - // Stopwatch functions - function toggleStopwatch() { - Persistent.states.timer.stopwatch.running = !isStopwatchRunning - if (isStopwatchRunning) { - // Resume from paused time by adjusting start time - Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime - } + Timer { + id: stopwatchTimer + interval: 10 + running: root.isStopwatchRunning + repeat: true + onTriggered: root.refreshStopwatch() } - function stopwatchResetOrLaps() { - if (isStopwatchRunning) { - recordLaps() - } else { - stopwatchReset() - } + function toggleStopwatch() { + if (root.isStopwatchRunning) + root.stopwatchPause() + else + root.stopwatchResume() + } + + function stopwatchPause() { + Persistent.states.timer.stopwatch.running = false + } + + function stopwatchResume() { + Persistent.states.timer.stopwatch.running = true + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime } function stopwatchReset() { Persistent.states.timer.stopwatch.running = false stopwatchTime = 0 - stopwatchStart = getCurrentTimeIn10ms() + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() Persistent.states.timer.stopwatch.laps = [] } - function recordLaps() { + function stopwatchRecordLap() { Persistent.states.timer.stopwatch.laps.push(stopwatchTime) - // Reassign to trigger change - // Persistent.states.timer.stopwatch.laps = Persistent.states.timer.stopwatch.laps.slice(0) } } From 4ca15a1fc361c9f1b10ed6b22431f16ab0f66a4d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 21:36:28 +0700 Subject: [PATCH 12/23] pomodoro: make pause/resume less weird --- .../quickshell/ii/modules/common/Config.qml | 4 +- .../ii/modules/common/Persistent.qml | 1 + .../sidebarRight/pomodoro/PomodoroTimer.qml | 237 ++++++------------ .config/quickshell/ii/services/Pomodoro.qml | 13 +- 4 files changed, 80 insertions(+), 175 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 9580c3bc0..32cc214dd 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -256,9 +256,9 @@ Singleton { property JsonObject pomodoro: JsonObject { property string alertSound: "" property int breakTime: 300 - property int cycle: 4 + property int cyclesBeforeLongBreak: 4 property int focus: 1500 - property int longBreak: 1200 + property int longBreak: 900 } } diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index 876b2ef6a..a2c8f4391 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -50,6 +50,7 @@ Singleton { property bool running: false property int start: 0 property bool isBreak: false + property int cycle: 0 } property JsonObject stopwatch: JsonObject { property bool running: false diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index 1534adb1f..fd971b512 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -9,187 +9,92 @@ import QtQuick.Layouts import Quickshell Item { + id: root + + implicitHeight: contentColumn.implicitHeight + implicitWidth: contentColumn.implicitWidth + ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter - spacing: 20 + id: contentColumn + anchors.fill: parent + spacing: 0 - RowLayout { - spacing: 40 - // The Pomodoro timer circle - CircularProgress { - Layout.alignment: Qt.AlignHCenter - lineWidth: 7 - gapAngle: Math.PI / 14 - value: { - let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime - return Pomodoro.pomodoroSecondsLeft / pomodoroTotalTime - } - size: 125 - primaryColor: Appearance.m3colors.m3onSecondaryContainer - secondaryColor: Appearance.colors.colSecondaryContainer - enableAnimation: true - - ColumnLayout { - anchors.centerIn: parent - spacing: 0 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: { - let minutes = Math.floor(Pomodoro.pomodoroSecondsLeft / 60).toString().padStart(2, '0') - let seconds = Math.floor(Pomodoro.pomodoroSecondsLeft % 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.isBreak ? Translation.tr("Break") : Translation.tr("Focus") - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3onSurface - } - } + // The Pomodoro timer circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 8 + gapAngle: Math.PI / 14 + value: { + let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime; + return Pomodoro.pomodoroSecondsLeft / pomodoroTotalTime; } + size: 200 + primaryColor: Appearance.m3colors.m3onSecondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer + enableAnimation: true - // The Start/Stop and Reset buttons ColumnLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 10 + anchors.centerIn: parent + spacing: 0 - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.pomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") - color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + let minutes = Math.floor(Pomodoro.pomodoroSecondsLeft / 60).toString().padStart(2, '0'); + let seconds = Math.floor(Pomodoro.pomodoroSecondsLeft % 60).toString().padStart(2, '0'); + return `${minutes}:${seconds}`; } - implicitHeight: 35 - implicitWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.togglePomodoro() - colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface } - - RippleButton { - implicitHeight: 35 - implicitWidth: 90 - - onClicked: Pomodoro.resetPomodoro() - enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.focusTime) - - font.pixelSize: Appearance.font.pixelSize.larger - colBackground: Appearance.colors.colErrorContainer - colBackgroundHover: Appearance.colors.colErrorContainerHover - colRipple: Appearance.colors.colErrorContainerActive - - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Translation.tr("Reset") - color: Appearance.colors.colOnErrorContainer - } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colSubtext } } } - // The SpinBoxes for adjusting duration - // GridLayout { - // Layout.alignment: Qt.AlignHCenter - // columns: 2 - // uniformCellWidths: true - // columnSpacing: 20 - // rowSpacing: 4 + // The Start/Stop and Reset buttons + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 10 - // StyledText { - // Layout.alignment: Qt.AlignHCenter - // text: Translation.tr("Focus") - // } + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.pomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + } + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.togglePomodoro() + colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + } - // StyledText { - // Layout.alignment: Qt.AlignHCenter - // text: Translation.tr("Break") - // } + RippleButton { + implicitHeight: 35 + implicitWidth: 90 - // ConfigSpinBox { - // id: focusSpinBox - // spacing: 0 - // Layout.leftMargin: 0 - // Layout.rightMargin: 0 - // from: 0 - // to: 120 - // Connections { - // target: Config - // function onReadyChanged() { - // focusSpinBox.valueChanged() - // } - // } - // value: { - // console.log("New focus time: " + (Config.options.time.pomodoro.focus / 60)) - // return Config.options.time.pomodoro.focus / 60 - // } - // onValueChanged: { - // console.log("New focus time is " + value + " minutes, Config is ready:", Config.ready) - // if (!Config.ready) return; - // console.log("Setting focus time to " + value + " minutes") - // Config.options.time.pomodoro.focus = value * 60 - // if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - // Pomodoro.pomodoroSecondsLeft = Pomodoro.focusTime - // Pomodoro.timeLeft = Pomodoro.focusTime - // } - // } - // } + onClicked: Pomodoro.resetPomodoro() + enabled: (Pomodoro.pomodoroSecondsLeft < (Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime)) - // ConfigSpinBox { - // id: breakSpinBox - // value: Config.options.time.pomodoro.breakTime / 60 - // spacing: 0 - // from: 0 - // to: 120 - // Layout.leftMargin: 0 - // Layout.rightMargin: 0 - // onValueChanged: { - // Config.options.time.pomodoro.breakTime = value * 60 - // } - // } + font.pixelSize: Appearance.font.pixelSize.larger + colBackground: Appearance.colors.colErrorContainer + colBackgroundHover: Appearance.colors.colErrorContainerHover + colRipple: Appearance.colors.colErrorContainerActive - // StyledText { - // Layout.topMargin: 6 - // Layout.alignment: Qt.AlignHCenter - // text: Translation.tr("Cycle") - // } - // StyledText { - // Layout.topMargin: 6 - // Layout.alignment: Qt.AlignHCenter - // text: Translation.tr("Long break") - // } - - // ConfigSpinBox { - // id: cycleSpinBox - // value: Config.options.time.pomodoro.cycle - // spacing: 0 - // from: 1 - // to: 20 - // Layout.leftMargin: 0 - // Layout.rightMargin: 0 - // onValueChanged: { - // Config.options.time.pomodoro.cycle = value - // } - // } - - // ConfigSpinBox { - // id: longBreakSpinBox - // spacing: 0 - // Layout.leftMargin: 0 - // Layout.rightMargin: 0 - // value: Config.options.time.pomodoro.longBreak / 60 - // from: 0 - // to: 120 - // onValueChanged: { - // Config.options.time.pomodoro.longBreak = value * 60 - // } - // } - // } + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Translation.tr("Reset") + color: Appearance.colors.colOnErrorContainer + } + } + } } -} \ No newline at end of file +} diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index dd60a9f4a..a537fd957 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -17,7 +17,7 @@ Singleton { property int focusTime: Config.options.time.pomodoro.focus property int breakTime: Config.options.time.pomodoro.breakTime property int longBreakTime: Config.options.time.pomodoro.longBreak - property int longBreakCycle: Config.options.time.pomodoro.cycle + property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak property string alertSound: Config.options.time.pomodoro.alertSound property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running @@ -56,7 +56,7 @@ Singleton { let notificationTitle, notificationMessage - if (isBreak && pomodoroCycle % longBreakCycle === 0) { // isPomodoroLongBreak + if (isBreak && pomodoroCycle % cyclesBeforeLongBreak === 0) { // isPomodoroLongBreak notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)) } else if (isBreak) { notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)) @@ -86,10 +86,8 @@ Singleton { function togglePomodoro() { isPomodoroReset = false Persistent.states.timer.pomodoro.running = !isPomodoroRunning - if (isPomodoroRunning) { // Pressed Start button - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - } else { // Pressed Stop button - timeLeft -= (getCurrentTimeInSeconds() - pomodoroStart) + if (Persistent.states.timer.pomodoro.running) { // Start/Resume + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - (isBreak ? breakTime : focusTime) } } @@ -99,7 +97,8 @@ Singleton { isPomodoroReset = true timeLeft = focusTime Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - pomodoroCycle = 1 + pomodoroSecondsLeft = 0 + Persistent.states.timer.pomodoro.cycle = 1 refreshPomodoro() } From 9e1b55a749126c45dbdf07e1228b41ffd0a2f54b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:06:05 +0700 Subject: [PATCH 13/23] pomodoro: add cycle indicator, make long break work, fix reset button --- .../ii/modules/common/Persistent.qml | 28 ++++++++++--- .../sidebarRight/pomodoro/PomodoroTimer.qml | 29 ++++++++++---- .config/quickshell/ii/services/Pomodoro.qml | 40 +++++++++---------- 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index a2c8f4391..e639f54f1 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -11,18 +11,35 @@ Singleton { property string fileName: "states.json" property string filePath: `${root.fileDir}/${root.fileName}` + Timer { + id: fileReloadTimer + interval: 100 + repeat: false + onTriggered: { + persistentStatesFileView.reload() + } + } + + Timer { + id: fileWriteTimer + interval: 100 + repeat: false + onTriggered: { + persistentStatesFileView.writeAdapter() + } + } + FileView { + id: persistentStatesFileView path: root.filePath watchChanges: true - onFileChanged: reload() - onAdapterUpdated: { - writeAdapter() - } + onFileChanged: fileReloadTimer.restart() + onAdapterUpdated: fileWriteTimer.restart() onLoadFailed: error => { console.log("Failed to load persistent states file:", error); if (error == FileViewError.FileNotFound) { - writeAdapter(); + fileWriteTimer.restart(); } } @@ -50,6 +67,7 @@ Singleton { property bool running: false property int start: 0 property bool isBreak: false + property bool isLongBreak: false property int cycle: 0 } property JsonObject stopwatch: JsonObject { diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index fd971b512..b086a68ab 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -23,14 +23,10 @@ Item { CircularProgress { Layout.alignment: Qt.AlignHCenter lineWidth: 8 - gapAngle: Math.PI / 14 value: { - let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime; - return Pomodoro.pomodoroSecondsLeft / pomodoroTotalTime; + return Pomodoro.pomodoroSecondsLeft / Pomodoro.pomodoroLapDuration; } size: 200 - primaryColor: Appearance.m3colors.m3onSecondaryContainer - secondaryColor: Appearance.colors.colSecondaryContainer enableAnimation: true ColumnLayout { @@ -49,11 +45,30 @@ Item { } StyledText { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + text: Pomodoro.isLongBreak ? Translation.tr("Long break") : Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colSubtext } } + + Rectangle { + radius: Appearance.rounding.full + color: Appearance.colors.colLayer2 + + anchors { + right: parent.right + bottom: parent.bottom + } + implicitWidth: 36 + implicitHeight: implicitWidth + + StyledText { + id: cycleText + anchors.centerIn: parent + color: Appearance.colors.colOnLayer2 + text: Pomodoro.pomodoroCycle + 1 + } + } } // The Start/Stop and Reset buttons @@ -81,7 +96,7 @@ Item { implicitWidth: 90 onClicked: Pomodoro.resetPomodoro() - enabled: (Pomodoro.pomodoroSecondsLeft < (Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime)) + enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.pomodoroLapDuration) || Pomodoro.pomodoroCycle > 0 || Pomodoro.isBreak font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index a537fd957..8bb198076 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -22,11 +22,11 @@ Singleton { property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running property bool isBreak: Persistent.states.timer.pomodoro.isBreak - property bool isPomodoroReset: !isPomodoroRunning - property int timeLeft: focusTime + property bool isLongBreak: Persistent.states.timer.pomodoro.isLongBreak + property bool isPomodoroLongBreak: Persistent.states.timer.pomodoro.isLongBreak + property int pomodoroLapDuration: isBreak ? (isLongBreak ? longBreakTime : breakTime) : focusTime property int pomodoroSecondsLeft: focusTime - property int pomodoroStart: Persistent.states.timer.pomodoro.start - property int pomodoroCycle: 1 + property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle property bool isStopwatchRunning: Persistent.states.timer.stopwatch.running property int stopwatchTime: 0 @@ -49,30 +49,34 @@ Singleton { // Pomodoro function refreshPomodoro() { // Work <-> break ? - if (getCurrentTimeInSeconds() >= pomodoroStart + timeLeft) { - Persistent.states.timer.pomodoro.isBreak = !isBreak - Persistent.states.timer.pomodoro.start += timeLeft - timeLeft = isBreak ? breakTime : focusTime + if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { + // Reset counts + const currentTimeInSeconds = getCurrentTimeInSeconds() + Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak + Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak) + Persistent.states.timer.pomodoro.start = currentTimeInSeconds + // Send notification let notificationTitle, notificationMessage - - if (isBreak && pomodoroCycle % cyclesBeforeLongBreak === 0) { // isPomodoroLongBreak + if (Persistent.states.timer.pomodoro.isBreak && pomodoroCycle % cyclesBeforeLongBreak === 0) { // isPomodoroLongBreak notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)) - } else if (isBreak) { + } else if (Persistent.states.timer.pomodoro.isBreak) { notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)) } else { notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)) - pomodoroCycle += 1 } Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) if (alertSound) { // Play sound only if alertSound is explicitly specified Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]) } + + if (!isBreak) { + Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak; + } } - // A nice abstraction for resume logic by updating the TimeStarted - pomodoroSecondsLeft = (pomodoroStart + timeLeft) - getCurrentTimeInSeconds() + pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start) } Timer { @@ -84,21 +88,17 @@ Singleton { } function togglePomodoro() { - isPomodoroReset = false Persistent.states.timer.pomodoro.running = !isPomodoroRunning if (Persistent.states.timer.pomodoro.running) { // Start/Resume - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - (isBreak ? breakTime : focusTime) + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration } } function resetPomodoro() { Persistent.states.timer.pomodoro.running = false Persistent.states.timer.pomodoro.isBreak = false - isPomodoroReset = true - timeLeft = focusTime Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - pomodoroSecondsLeft = 0 - Persistent.states.timer.pomodoro.cycle = 1 + Persistent.states.timer.pomodoro.cycle = 0 refreshPomodoro() } From 90fafa219a54f5ee2e81d2fbb8d7827cb93235c6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:09:07 +0700 Subject: [PATCH 14/23] remove unnecessary qualified access --- .config/quickshell/ii/services/Pomodoro.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 8bb198076..8376499d9 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -84,7 +84,7 @@ Singleton { interval: 200 running: root.isPomodoroRunning repeat: true - onTriggered: Pomodoro.refreshPomodoro() + onTriggered: refreshPomodoro() } function togglePomodoro() { @@ -112,14 +112,14 @@ Singleton { interval: 10 running: root.isStopwatchRunning repeat: true - onTriggered: root.refreshStopwatch() + onTriggered: refreshStopwatch() } function toggleStopwatch() { if (root.isStopwatchRunning) - root.stopwatchPause() + stopwatchPause() else - root.stopwatchResume() + stopwatchResume() } function stopwatchPause() { From df9a5e398e9ad838a71509e0ca299d9a7e3202cc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:11:06 +0700 Subject: [PATCH 15/23] pomodoro: remove unused notificationTitle var, format --- .config/quickshell/ii/services/Pomodoro.qml | 73 +++++++++++---------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 8376499d9..297a1c204 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -35,15 +35,16 @@ Singleton { // General Component.onCompleted: { - if (!isStopwatchRunning) stopwatchReset() + if (!isStopwatchRunning) + stopwatchReset(); } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds - return Math.floor(Date.now() / 1000) + return Math.floor(Date.now() / 1000); } function getCurrentTimeIn10ms() { // Stopwatch uses 10ms - return Math.floor(Date.now() / 10) + return Math.floor(Date.now() / 10); } // Pomodoro @@ -51,32 +52,31 @@ Singleton { // Work <-> break ? if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { // Reset counts - const currentTimeInSeconds = getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak - Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak) - Persistent.states.timer.pomodoro.start = currentTimeInSeconds + const currentTimeInSeconds = getCurrentTimeInSeconds(); + Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak; + Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); + Persistent.states.timer.pomodoro.start = currentTimeInSeconds; // Send notification - let notificationTitle, notificationMessage - if (Persistent.states.timer.pomodoro.isBreak && pomodoroCycle % cyclesBeforeLongBreak === 0) { // isPomodoroLongBreak - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)) + let notificationMessage; + if (Persistent.states.timer.pomodoro.isLongBreak) { + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)); } else if (Persistent.states.timer.pomodoro.isBreak) { - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)) + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)); } else { - notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)) + notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)); } - Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) - if (alertSound) { // Play sound only if alertSound is explicitly specified - Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]) - } + Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]); + if (alertSound) + Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]); if (!isBreak) { Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak; } } - pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start) + pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start); } Timer { @@ -88,23 +88,24 @@ Singleton { } function togglePomodoro() { - Persistent.states.timer.pomodoro.running = !isPomodoroRunning - if (Persistent.states.timer.pomodoro.running) { // Start/Resume - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration + Persistent.states.timer.pomodoro.running = !isPomodoroRunning; + if (Persistent.states.timer.pomodoro.running) { + // Start/Resume + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration; } } function resetPomodoro() { - Persistent.states.timer.pomodoro.running = false - Persistent.states.timer.pomodoro.isBreak = false - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.cycle = 0 - refreshPomodoro() + Persistent.states.timer.pomodoro.running = false; + Persistent.states.timer.pomodoro.isBreak = false; + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); + Persistent.states.timer.pomodoro.cycle = 0; + refreshPomodoro(); } // Stopwatch function refreshStopwatch() { // Stopwatch stores time in 10ms - stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart + stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart; } Timer { @@ -117,28 +118,28 @@ Singleton { function toggleStopwatch() { if (root.isStopwatchRunning) - stopwatchPause() + stopwatchPause(); else - stopwatchResume() + stopwatchResume(); } function stopwatchPause() { - Persistent.states.timer.stopwatch.running = false + Persistent.states.timer.stopwatch.running = false; } function stopwatchResume() { - Persistent.states.timer.stopwatch.running = true - Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime + Persistent.states.timer.stopwatch.running = true; + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime; } function stopwatchReset() { - Persistent.states.timer.stopwatch.running = false - stopwatchTime = 0 - Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - Persistent.states.timer.stopwatch.laps = [] + Persistent.states.timer.stopwatch.running = false; + stopwatchTime = 0; + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms(); + Persistent.states.timer.stopwatch.laps = []; } function stopwatchRecordLap() { - Persistent.states.timer.stopwatch.laps.push(stopwatchTime) + Persistent.states.timer.stopwatch.laps.push(stopwatchTime); } } From b102e5c1a5c840dc4dc860b9ea297dcbf4f7e469 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:15:07 +0700 Subject: [PATCH 16/23] pomodoro: remove unnecessary event propagation prevention --- .../sidebarRight/pomodoro/PomodoroWidget.qml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 64b16a9df..b5ee7a597 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -16,32 +16,26 @@ Item { // These are keybinds for stopwatch and pomodoro Keys.onPressed: (event) => { - if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { + if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs if (event.key === Qt.Key_PageDown) { currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) } else if (event.key === Qt.Key_PageUp) { currentTab = Math.max(currentTab - 1, 0) } - event.accepted = true - } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { - // Toggle start/stop with Space or S key + } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S if (currentTab === 0) { Pomodoro.togglePomodoro() } else { Pomodoro.toggleStopwatch() } - event.accepted = true - } else if (event.key === Qt.Key_R) { - // Reset with R key + } else if (event.key === Qt.Key_R) { // Reset with R if (currentTab === 0) { Pomodoro.resetPomodoro() } else { Pomodoro.stopwatchReset() } - event.accepted = true - } else if (event.key === Qt.Key_L) { - // record Stopwatch lap with L key, regardless of current Tab - Pomodoro.recordLaps() + } else if (event.key === Qt.Key_L) { // Record lap with L + Pomodoro.stopwatchRecordLap() } } From fbe17dc3e3119d5ff15dd8418e1f9fb7c9461d15 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:19:45 +0700 Subject: [PATCH 17/23] pomodoro: accept event on keybind cuz recommended by qt docs --- .../ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index b5ee7a597..3aaa868a4 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -22,20 +22,24 @@ Item { } else if (event.key === Qt.Key_PageUp) { currentTab = Math.max(currentTab - 1, 0) } + event.accepted = true } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S if (currentTab === 0) { Pomodoro.togglePomodoro() } else { Pomodoro.toggleStopwatch() } + event.accepted = true } else if (event.key === Qt.Key_R) { // Reset with R if (currentTab === 0) { Pomodoro.resetPomodoro() } else { Pomodoro.stopwatchReset() } + event.accepted = true } else if (event.key === Qt.Key_L) { // Record lap with L Pomodoro.stopwatchRecordLap() + event.accepted = true } } From bcd1167d3989bbbdbf4af1eeec2c4e8b1cb70f17 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:48:40 +0700 Subject: [PATCH 18/23] pomodoro: rename service to TimerService, better stopwatch layout --- .../sidebarRight/pomodoro/PomodoroTimer.qml | 24 +-- .../sidebarRight/pomodoro/PomodoroWidget.qml | 10 +- .../sidebarRight/pomodoro/Stopwatch.qml | 192 ++++++++++-------- .../{Pomodoro.qml => TimerService.qml} | 0 4 files changed, 128 insertions(+), 98 deletions(-) rename .config/quickshell/ii/services/{Pomodoro.qml => TimerService.qml} (100%) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index b086a68ab..e50f92d83 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -24,7 +24,7 @@ Item { Layout.alignment: Qt.AlignHCenter lineWidth: 8 value: { - return Pomodoro.pomodoroSecondsLeft / Pomodoro.pomodoroLapDuration; + return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration; } size: 200 enableAnimation: true @@ -36,8 +36,8 @@ Item { StyledText { Layout.alignment: Qt.AlignHCenter text: { - let minutes = Math.floor(Pomodoro.pomodoroSecondsLeft / 60).toString().padStart(2, '0'); - let seconds = Math.floor(Pomodoro.pomodoroSecondsLeft % 60).toString().padStart(2, '0'); + let minutes = Math.floor(TimerService.pomodoroSecondsLeft / 60).toString().padStart(2, '0'); + let seconds = Math.floor(TimerService.pomodoroSecondsLeft % 60).toString().padStart(2, '0'); return `${minutes}:${seconds}`; } font.pixelSize: 40 @@ -45,7 +45,7 @@ Item { } StyledText { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isLongBreak ? Translation.tr("Long break") : Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + text: TimerService.isLongBreak ? Translation.tr("Long break") : TimerService.isBreak ? Translation.tr("Break") : Translation.tr("Focus") font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colSubtext } @@ -66,7 +66,7 @@ Item { id: cycleText anchors.centerIn: parent color: Appearance.colors.colOnLayer2 - text: Pomodoro.pomodoroCycle + 1 + text: TimerService.pomodoroCycle + 1 } } } @@ -80,23 +80,23 @@ Item { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.pomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") - color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: TimerService.isPomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + color: TimerService.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary } implicitHeight: 35 implicitWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.togglePomodoro() - colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + onClicked: TimerService.togglePomodoro() + colBackground: TimerService.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary } RippleButton { implicitHeight: 35 implicitWidth: 90 - onClicked: Pomodoro.resetPomodoro() - enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.pomodoroLapDuration) || Pomodoro.pomodoroCycle > 0 || Pomodoro.isBreak + onClicked: TimerService.resetPomodoro() + enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.isBreak font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 3aaa868a4..729d45809 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -25,20 +25,20 @@ Item { event.accepted = true } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S if (currentTab === 0) { - Pomodoro.togglePomodoro() + TimerService.togglePomodoro() } else { - Pomodoro.toggleStopwatch() + TimerService.toggleStopwatch() } event.accepted = true } else if (event.key === Qt.Key_R) { // Reset with R if (currentTab === 0) { - Pomodoro.resetPomodoro() + TimerService.resetPomodoro() } else { - Pomodoro.stopwatchReset() + TimerService.stopwatchReset() } event.accepted = true } else if (event.key === Qt.Key_L) { // Record lap with L - Pomodoro.stopwatchRecordLap() + TimerService.stopwatchRecordLap() event.accepted = true } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index f1c428ddf..cbf588194 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -13,91 +13,61 @@ Item { Layout.fillWidth: true Layout.fillHeight: true - ColumnLayout { + Item { anchors { fill: parent - leftMargin: 20 - rightMargin: 20 + topMargin: 8 + leftMargin: 16 + rightMargin: 16 } - spacing: 20 - ColumnLayout { - spacing: 8 - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: false + RowLayout { // Elapsed + id: elapsedIndicator + + anchors { + top: undefined + verticalCenter: parent.verticalCenter + left: controlButtons.left + leftMargin: 6 + } - RowLayout { // Elapsed - id: elapsedIndicator - Layout.alignment: Qt.AlignHCenter - spacing: 0 - StyledText { - // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness - font.pixelSize: 40 - color: Appearance.m3colors.m3onSurface - text: { - let totalSeconds = Math.floor(Pomodoro.stopwatchTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `${minutes}:${seconds}` - } - } - StyledText { - Layout.fillWidth: true - font.pixelSize: 40 - color: Appearance.colors.colSubtext - text: { - return `:${(Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}` - } + states: State { + name: "hasLaps" + when: TimerService.stopwatchLaps.length > 0 + AnchorChanges { + target: elapsedIndicator + anchors.top: parent.top + anchors.verticalCenter: undefined + anchors.left: controlButtons.left } } - // The Start/Stop and Reset buttons - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 4 - - RippleButton { - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - - onClicked: { - Pomodoro.toggleStopwatch() - } - - colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover - colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive - - contentItem: StyledText { - horizontalAlignment: Text.AlignHCenter - color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary - text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") - } + transitions: Transition { + AnchorAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } + } - RippleButton { - implicitHeight: 35 - implicitWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - - onClicked: { - if (Pomodoro.isStopwatchRunning) - Pomodoro.stopwatchRecordLap() - else - Pomodoro.stopwatchReset() - } - enabled: Pomodoro.stopwatchTime !== 0 - - colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer - colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover - colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive - - contentItem: StyledText { - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") - color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer - } + spacing: 0 + StyledText { + // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface + text: { + let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}` + } + } + StyledText { + Layout.fillWidth: true + font.pixelSize: 40 + color: Appearance.colors.colSubtext + text: { + return `:${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}` } } } @@ -105,14 +75,20 @@ Item { // Laps StyledListView { id: lapsList - Layout.fillWidth: true - Layout.fillHeight: true + anchors { + top: elapsedIndicator.bottom + bottom: controlButtons.top + left: parent.left + right: parent.right + topMargin: 16 + bottomMargin: 16 + } spacing: 4 clip: true popin: true model: ScriptModel { - values: Pomodoro.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i]) + values: TimerService.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i]) } delegate: Rectangle { @@ -140,7 +116,7 @@ Item { StyledText { font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colSubtext - text: `${Pomodoro.stopwatchLaps.length - lapItem.index}.` + text: `${TimerService.stopwatchLaps.length - lapItem.index}.` } StyledText { @@ -161,8 +137,8 @@ Item { font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colPrimary text: { - const originalIndex = Pomodoro.stopwatchLaps.length - lapItem.index - 1 - const lastTime = originalIndex > 0 ? Pomodoro.stopwatchLaps[originalIndex - 1] : 0 + const originalIndex = TimerService.stopwatchLaps.length - lapItem.index - 1 + const lastTime = originalIndex > 0 ? TimerService.stopwatchLaps[originalIndex - 1] : 0 const lapTime = lapItem.modelData - lastTime const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') const totalSeconds = Math.floor(lapTime) / 100 @@ -174,5 +150,59 @@ Item { } } } + + RowLayout { + id: controlButtons + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: 6 + } + spacing: 4 + + RippleButton { + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + + onClicked: { + TimerService.toggleStopwatch() + } + + colBackground: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover + colRipple: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + color: TimerService.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: TimerService.isStopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") + } + } + + RippleButton { + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + + onClicked: { + if (TimerService.isStopwatchRunning) + TimerService.stopwatchRecordLap() + else + TimerService.stopwatchReset() + } + enabled: TimerService.stopwatchTime !== 0 + + colBackground: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer + colBackgroundHover: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover + colRipple: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + text: TimerService.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: TimerService.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer + } + } + } } } \ No newline at end of file diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/TimerService.qml similarity index 100% rename from .config/quickshell/ii/services/Pomodoro.qml rename to .config/quickshell/ii/services/TimerService.qml From 0e17b7a981d2e6450434db5d0aaebd43c379f58a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:55:40 +0700 Subject: [PATCH 19/23] TimerService: add back emojis, make reset also reset isLongBreak --- .config/quickshell/ii/services/TimerService.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index 297a1c204..651af1b05 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -60,11 +60,11 @@ Singleton { // Send notification let notificationMessage; if (Persistent.states.timer.pomodoro.isLongBreak) { - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)); + notificationMessage = Translation.tr(`🌿 Long break: %1 minutes`).arg(Math.floor(longBreakTime / 60)); } else if (Persistent.states.timer.pomodoro.isBreak) { - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)); + notificationMessage = Translation.tr(`☕ Break: %1 minutes`).arg(Math.floor(breakTime / 60)); } else { - notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)); + notificationMessage = Translation.tr(`🔴 Focus: %1 minutes`).arg(Math.floor(focusTime / 60)); } Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]); @@ -98,6 +98,7 @@ Singleton { function resetPomodoro() { Persistent.states.timer.pomodoro.running = false; Persistent.states.timer.pomodoro.isBreak = false; + Persistent.states.timer.pomodoro.isLongBreak = false; Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); Persistent.states.timer.pomodoro.cycle = 0; refreshPomodoro(); From ca6463cae8f828a032a4136c489f8347196f256f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:58:55 +0700 Subject: [PATCH 20/23] TimerService: not have stupid extra declaration --- .config/quickshell/ii/services/TimerService.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index 651af1b05..f4f9bde95 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -52,10 +52,9 @@ Singleton { // Work <-> break ? if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { // Reset counts - const currentTimeInSeconds = getCurrentTimeInSeconds(); Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak; Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); - Persistent.states.timer.pomodoro.start = currentTimeInSeconds; + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); // Send notification let notificationMessage; From c2d5d2b61ae4d4905ecc7f61e7dbf90e46557071 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 15:40:42 +0700 Subject: [PATCH 21/23] pomodoro: remove unnecessary isLongBreak persistent state --- .config/quickshell/ii/modules/common/Persistent.qml | 1 - .config/quickshell/ii/services/TimerService.qml | 10 +++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index e639f54f1..29d41e18d 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -67,7 +67,6 @@ Singleton { property bool running: false property int start: 0 property bool isBreak: false - property bool isLongBreak: false property int cycle: 0 } property JsonObject stopwatch: JsonObject { diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index f4f9bde95..baf13c796 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -22,10 +22,9 @@ Singleton { property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running property bool isBreak: Persistent.states.timer.pomodoro.isBreak - property bool isLongBreak: Persistent.states.timer.pomodoro.isLongBreak - property bool isPomodoroLongBreak: Persistent.states.timer.pomodoro.isLongBreak + property bool isLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); property int pomodoroLapDuration: isBreak ? (isLongBreak ? longBreakTime : breakTime) : focusTime - property int pomodoroSecondsLeft: focusTime + property int pomodoroSecondsLeft: isLongBreak ? longBreakTime : (isBreak ? breakTime : focusTime) property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle property bool isStopwatchRunning: Persistent.states.timer.stopwatch.running @@ -53,7 +52,6 @@ Singleton { if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { // Reset counts Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak; - Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); // Send notification @@ -97,7 +95,6 @@ Singleton { function resetPomodoro() { Persistent.states.timer.pomodoro.running = false; Persistent.states.timer.pomodoro.isBreak = false; - Persistent.states.timer.pomodoro.isLongBreak = false; Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); Persistent.states.timer.pomodoro.cycle = 0; refreshPomodoro(); @@ -133,10 +130,9 @@ Singleton { } function stopwatchReset() { - Persistent.states.timer.stopwatch.running = false; stopwatchTime = 0; - Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms(); Persistent.states.timer.stopwatch.laps = []; + Persistent.states.timer.stopwatch.running = false; } function stopwatchRecordLap() { From 709415a6b4e54edc07d55d87511013e1cf8c90f8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:02:00 +0700 Subject: [PATCH 22/23] rename stuff --- .../sidebarRight/pomodoro/PomodoroTimer.qml | 12 ++++----- .../sidebarRight/pomodoro/Stopwatch.qml | 22 ++++++++-------- .../quickshell/ii/services/TimerService.qml | 26 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index e50f92d83..439c06ee4 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -45,7 +45,7 @@ Item { } StyledText { Layout.alignment: Qt.AlignHCenter - text: TimerService.isLongBreak ? Translation.tr("Long break") : TimerService.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + text: TimerService.pomodoroLongBreak ? Translation.tr("Long break") : TimerService.pomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus") font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colSubtext } @@ -80,15 +80,15 @@ Item { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: TimerService.isPomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") - color: TimerService.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: TimerService.pomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + color: TimerService.pomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary } implicitHeight: 35 implicitWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger onClicked: TimerService.togglePomodoro() - colBackground: TimerService.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: TimerService.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackground: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary } RippleButton { @@ -96,7 +96,7 @@ Item { implicitWidth: 90 onClicked: TimerService.resetPomodoro() - enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.isBreak + enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.pomodoroBreak font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index cbf588194..2f1483932 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -169,14 +169,14 @@ Item { TimerService.toggleStopwatch() } - colBackground: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover - colRipple: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive + colBackground: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover + colRipple: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive contentItem: StyledText { horizontalAlignment: Text.AlignHCenter - color: TimerService.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary - text: TimerService.isStopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") + color: TimerService.stopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: TimerService.stopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") } } @@ -186,21 +186,21 @@ Item { font.pixelSize: Appearance.font.pixelSize.larger onClicked: { - if (TimerService.isStopwatchRunning) + if (TimerService.stopwatchRunning) TimerService.stopwatchRecordLap() else TimerService.stopwatchReset() } enabled: TimerService.stopwatchTime !== 0 - colBackground: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer - colBackgroundHover: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover - colRipple: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive + colBackground: TimerService.stopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer + colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover + colRipple: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive contentItem: StyledText { horizontalAlignment: Text.AlignHCenter - text: TimerService.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") - color: TimerService.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer + text: TimerService.stopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: TimerService.stopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer } } } diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index baf13c796..5824c01ab 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -20,21 +20,21 @@ Singleton { property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak property string alertSound: Config.options.time.pomodoro.alertSound - property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running - property bool isBreak: Persistent.states.timer.pomodoro.isBreak - property bool isLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); - property int pomodoroLapDuration: isBreak ? (isLongBreak ? longBreakTime : breakTime) : focusTime - property int pomodoroSecondsLeft: isLongBreak ? longBreakTime : (isBreak ? breakTime : focusTime) + property bool pomodoroRunning: Persistent.states.timer.pomodoro.running + property bool pomodoroBreak: Persistent.states.timer.pomodoro.isBreak + property bool pomodoroLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); + property int pomodoroLapDuration: pomodoroLongBreak ? longBreakTime : pomodoroBreak ? breakTime : focusTime // This is a binding that's to be kept + property int pomodoroSecondsLeft: pomodoroLapDuration // Reasonable init value, to be changed property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle - property bool isStopwatchRunning: Persistent.states.timer.stopwatch.running + property bool stopwatchRunning: Persistent.states.timer.stopwatch.running property int stopwatchTime: 0 property int stopwatchStart: Persistent.states.timer.stopwatch.start property var stopwatchLaps: Persistent.states.timer.stopwatch.laps // General Component.onCompleted: { - if (!isStopwatchRunning) + if (!stopwatchRunning) stopwatchReset(); } @@ -56,7 +56,7 @@ Singleton { // Send notification let notificationMessage; - if (Persistent.states.timer.pomodoro.isLongBreak) { + if (Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak)) { notificationMessage = Translation.tr(`🌿 Long break: %1 minutes`).arg(Math.floor(longBreakTime / 60)); } else if (Persistent.states.timer.pomodoro.isBreak) { notificationMessage = Translation.tr(`☕ Break: %1 minutes`).arg(Math.floor(breakTime / 60)); @@ -68,7 +68,7 @@ Singleton { if (alertSound) Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]); - if (!isBreak) { + if (!pomodoroBreak) { Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak; } } @@ -79,13 +79,13 @@ Singleton { Timer { id: pomodoroTimer interval: 200 - running: root.isPomodoroRunning + running: root.pomodoroRunning repeat: true onTriggered: refreshPomodoro() } function togglePomodoro() { - Persistent.states.timer.pomodoro.running = !isPomodoroRunning; + Persistent.states.timer.pomodoro.running = !pomodoroRunning; if (Persistent.states.timer.pomodoro.running) { // Start/Resume Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration; @@ -108,13 +108,13 @@ Singleton { Timer { id: stopwatchTimer interval: 10 - running: root.isStopwatchRunning + running: root.stopwatchRunning repeat: true onTriggered: refreshStopwatch() } function toggleStopwatch() { - if (root.isStopwatchRunning) + if (root.stopwatchRunning) stopwatchPause(); else stopwatchResume(); From 86f1e63ed2e56013b77a5d254326a69e454f692d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:03:37 +0700 Subject: [PATCH 23/23] stopwatch: clear laps on Start --- .../quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml | 2 +- .config/quickshell/ii/services/TimerService.qml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index 2f1483932..ffc706568 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -191,7 +191,7 @@ Item { else TimerService.stopwatchReset() } - enabled: TimerService.stopwatchTime !== 0 + enabled: TimerService.stopwatchTime > 0 || Persistent.states.timer.stopwatch.laps.length > 0 colBackground: TimerService.stopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index 5824c01ab..e6c8906bc 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -125,6 +125,7 @@ Singleton { } function stopwatchResume() { + if (stopwatchTime === 0) Persistent.states.timer.stopwatch.laps = []; Persistent.states.timer.stopwatch.running = true; Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime; }