fix timers
lint / ruff (push) Failing after 15s
tests / desktop (push) Successful in 21s
tests / web (push) Successful in 46s
docker / image (push) Failing after 1m31s
tests / android-core (push) Successful in 1m5s
tests / backend (push) Successful in 2m33s
android / apk (push) Successful in 4m34s

This commit is contained in:
timka123 committed 2026-10-27 18:52:54 +03:00
1 parent ff77d8f89f
commit 157a113c39
30 files changed
+3331 -419

No files matched your search

+12 -2
View File
@@ -87,8 +87,18 @@ class Api:
def resume(self) -> dict:
return self._request("POST", "/api/timer/resume", {})
def complete(self, auto_next: bool = True) -> dict:
return self._request("POST", "/api/timer/complete", {"auto_next": auto_next})
def complete(self, auto_next: bool = True, session_id: int | None = None) -> dict:
"""`session_id` names the interval we think we are finishing.
The server ignores the call if something else is running by then, which
is what keeps two clients hitting zero at the same moment from eating
the break the first one's `auto_next` just started.
"""
return self._request(
"POST",
"/api/timer/complete",
{"auto_next": auto_next, "session_id": session_id},
)
def abandon(self, reason: str | None = None) -> dict:
return self._request("POST", "/api/timer/abandon", {"reason": reason})
+30 -7
View File
@@ -78,7 +78,17 @@ class Controller(QObject):
self._last_session_id: int | None = None
self._last_kind = ""
self._last_status = ""
self._completing = False
self._completed_id: int | None = None
"""The interval we have already asked the server to finish.
An id rather than a bare "in flight" flag. A poll issued just before the
countdown ran out answers with that interval still running at 00:00, and
a flag cleared by that answer sends `complete` a second time — onto the
break `auto_next` has since started, which is why a break could end the
instant it began. Ids are never reused, so the second attempt is never
made. Cleared only when the call itself fails, so a refused or dropped
complete is still retried.
"""
self._paused_by_lock = False
self._mini_visible = False
self._mini_dismissed = False
@@ -174,6 +184,13 @@ class Controller(QObject):
current = self._state.get("current")
return str(current["status"]) if current else "idle"
@property
def _session_id(self) -> int | None:
"""The running interval's id, or None when nothing is running."""
current = self._state.get("current") or {}
session_id = current.get("id")
return int(session_id) if session_id is not None else None
@pyqtProperty(int, notify=stateChanged)
def remainingSeconds(self) -> int:
if self._state.get("current"):
@@ -393,7 +410,7 @@ class Controller(QObject):
@pyqtSlot()
def complete(self) -> None:
self._send("complete", {"auto_next": True})
self._send("complete", {"auto_next": True, "session_id": self._session_id})
@pyqtSlot()
@pyqtSlot(str)
@@ -567,7 +584,6 @@ class Controller(QObject):
self._last_session_id = session_id
self._last_kind = kind
self._last_status = status
self._completing = False
self.stateChanged.emit()
self._update_launcher()
@@ -610,12 +626,18 @@ class Controller(QObject):
def _on_command_failed(self, name: str, detail: str) -> None:
log.warning("%s failed: %s", name, detail)
if name == "complete":
# It never landed, so the interval at 00:00 is still owed a complete.
self._completed_id = None
self.toast.emit(detail)
def _on_command_done(self, name: str, result: dict[str, Any]) -> None:
if name == "complete":
finished = result.get("finished") or {}
self._announce_finished(str(finished.get("kind", "work")), result)
finished = result.get("finished")
# None means the server declined: something else was running by the
# time the call arrived, so nothing finished and nothing is announced.
if finished:
self._announce_finished(str(finished.get("kind", "work")), result)
elif name == "send_report":
self.toast.emit("Daily report sent" if result.get("sent") else "Report not sent — check Telegram settings")
elif name == "refresh_sources":
@@ -706,10 +728,11 @@ class Controller(QObject):
if self._remaining % 5 == 0:
self._update_launcher()
return
if not self._completing:
session_id = self._session_id
if session_id is not None and self._completed_id != session_id:
# The countdown reached zero: the server books the interval, and its
# reply decides what happens next (auto-break, or nothing).
self._completing = True
self._completed_id = session_id
self.complete()
def _check_idle(self) -> None:
+1 -1
View File
@@ -3,7 +3,7 @@ Type=Application
Name=Kadence
GenericName=Pomodoro timer
Comment=Pomodoro timer and focus guard — a tray agent with a floating countdown
Exec=kadence-desktop --mini
Exec=kadence-desktop
TryExec=kadence-desktop
Icon=kadence
Terminal=false