mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 19:53:55 +01:00
Every locker screen, the status window and the credit toast now show glyphs (screen_locker/_glyphs.py) and numbers, coloured by state. Sentences that carried a reason (verifier message, sync diagnosis, sick refusal, form rejection) go to the log at warning instead. New pre-commit hook text-free runs scripts/verify_text_free.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
111 lines
4.0 KiB
Python
111 lines
4.0 KiB
Python
"""The "I'm sick" button: one tap marks today sick and unlocks.
|
|
|
|
A sick day is a date in the shared ``sickdays`` pool (``_sick.py``). There is
|
|
no budget, countdown, justification, commitment or debt any more: the tap
|
|
writes today into the local pool, pushes it in the background (best
|
|
effort, so the phone sees it before the next sickdays-sync tick), raises
|
|
tonight's shutdown to the sick floor right
|
|
away (``_sick_floor``) and closes the lock. Every other gate reads the same
|
|
pool and stands down too.
|
|
|
|
The button is offered on every screen that can end in a lock or a prompt:
|
|
the retry screen, the no-phone penalty screen and both relaxed-day screens.
|
|
A refusal (a free day, an unreadable pool) is shown on screen and the lock
|
|
stays up.
|
|
|
|
Next to the button sits a small "choose lockers…" link: the same day, but
|
|
with some lockers kept (``_ui_flows_sick_panel``). The button itself still
|
|
keeps nothing.
|
|
|
|
Split out of :mod:`screen_locker._ui_flows` to keep every file under the
|
|
250-line cap. Composed back into ``UIFlowsMixin`` there.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from screen_locker import _glyphs as g
|
|
from screen_locker._constants import SHUTDOWN_BASE_FILE
|
|
from screen_locker._credit_slot import locked_shutdown_state
|
|
from screen_locker._sick import mark_today_sick, push_in_background
|
|
|
|
if TYPE_CHECKING:
|
|
import tkinter as tk
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SickDayFlowMixin:
|
|
"""The one-tap sick-day button and what follows it."""
|
|
|
|
def _sick_button(self, frame: tk.Widget) -> None:
|
|
"""Pack the sick-day button (was "I'm sick") into ``frame``."""
|
|
self._button(
|
|
frame,
|
|
g.SICK,
|
|
bg=self._colors.palette.warning,
|
|
command=self.mark_sick_and_unlock,
|
|
width=4,
|
|
).pack(side="left", padx=self._colors.space("sm"))
|
|
# The keep-aware alternative (``_ui_flows_sick_panel``), next to it.
|
|
self._choose_lockers_link(frame)
|
|
|
|
def mark_sick_and_unlock(self) -> None:
|
|
"""Mark today sick, lift tonight's shutdown to 20:00 and unlock."""
|
|
result = mark_today_sick()
|
|
if not result.ok:
|
|
self._show_sick_refused(result.message)
|
|
return
|
|
self._raise_sick_floor()
|
|
# Best effort and off the UI thread: the unlock never waits for it.
|
|
push_in_background()
|
|
self._unlock_sick_day(
|
|
f"Marked today sick from the lock screen. {result.message}"
|
|
)
|
|
|
|
def _raise_sick_floor(self) -> None:
|
|
"""Lift tonight's shutdown to the sick floor (a no-op unless sick)."""
|
|
# Now, not at the next 5-minute tick: while this window is up that
|
|
# tick stands down at the instance lock.
|
|
with locked_shutdown_state(SHUTDOWN_BASE_FILE):
|
|
self._apply_sick_floor()
|
|
|
|
def _unlock_sick_day(self, detail: str) -> None:
|
|
"""Record the sick-day unlock and close the lock.
|
|
|
|
Shared by the one-tap button and the keep panel; neither pushes here
|
|
(the panel starts its own sync).
|
|
"""
|
|
self._record_decision(locked=False, reason="sick_day_marked", detail=detail)
|
|
self.close()
|
|
|
|
def _show_sick_refused(self, message: str) -> None:
|
|
"""Show that today could not be marked sick; the lock stays up.
|
|
|
|
Was "Could not mark today as sick" over sickdays' reason; the reason
|
|
(a free day, an unreadable pool) now goes to the log.
|
|
"""
|
|
_logger.warning("Could not mark today sick: %s", message)
|
|
self.clear_container()
|
|
self._label(
|
|
f"{g.SICK} {g.FAIL}",
|
|
role="display",
|
|
color=self._colors.palette.danger,
|
|
pad="md",
|
|
)
|
|
frame = self._button_row()
|
|
back = (
|
|
self._start_relaxed_day_flow
|
|
if self._relaxed_day_mode
|
|
else self._start_phone_check
|
|
)
|
|
self._button(
|
|
frame,
|
|
g.BACK,
|
|
bg=self._colors.palette.accent,
|
|
command=back,
|
|
width=4,
|
|
).pack(side="left", padx=self._colors.space("sm"))
|