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] 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() }