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 = [] - } } }