pomodoro: add cycle indicator, make long break work, fix reset button

This commit is contained in:
end-4
2025-08-09 22:06:05 +07:00
parent 4ca15a1fc3
commit 9e1b55a749
3 changed files with 65 additions and 32 deletions
@@ -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 {
@@ -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
+20 -20
View File
@@ -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()
}