screen-locker/screen_locker/_ui_flows_sick.py
Krzysztof kuhy Rudnicki 0714c2264d Split _ui_flows.py into sick-day and lockout flow mixins
_ui_flows.py was 370 lines. The sick-day flow and the lockout/phone-penalty
flow are both self-contained, so each moves to a sibling module:

  _ui_flows_sick.py     SickDayFlowMixin   (justification, countdown, debt)
  _ui_flows_lockout.py  LockoutFlowMixin   (lockout timer, no-phone penalty)

UIFlowsMixin composes both rather than exposing them as separate mixins, so
neither ScreenLocker nor scripts/verify_screen_fits.py changes its bases --
and ScreenLocker's ancestor count (already at pylint's max-parents limit)
stays where it was.

Pure move: 975 tests still pass at 100% branch coverage, pylint holds at
8.70/10, and `screen_lock --status` renders the real week unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EveB813BQ8abAP68VSqZ5Y
2026-08-15 19:56:16 +02:00

95 lines
3.5 KiB
Python

"""Sick-day flow for the screen locker's locked screen.
Split out of :mod:`screen_locker._ui_flows` to keep every file under the
250-line cap. Composed back into ``UIFlowsMixin`` there, so the set of mixins
``ScreenLocker`` inherits is unchanged.
"""
from __future__ import annotations
from screen_locker import _sick_tracker
class SickDayFlowMixin:
"""Sick-day justification, countdown and debt bookkeeping."""
def ask_if_sick(self) -> None:
"""Display the structured sick-day justification dialog."""
self._show_sick_justification()
def _get_sick_day_status(self) -> tuple[str, str]:
"""Determine sick day status text and color."""
if self._sick_mode_used_today():
return "Shutdown time already adjusted today", self._colors.warning
if self._adjust_shutdown_time_earlier():
return (
"Shutdown time moved 1.5 hours earlier ✓\n(Will revert tomorrow)"
), self._colors.success
return (
"Could not adjust shutdown time (check permissions)",
self._colors.danger,
)
def _proceed_to_sick_countdown(self) -> None:
"""Start the (escalated) sick day countdown after justification."""
history = getattr(
self,
"_sick_history_cache",
None,
)
if history is None:
history = _sick_tracker.load_history()
self._sick_history_cache = history
countdown = _sick_tracker.compute_lockout_seconds(history)
self.clear_container()
status_text, status_color = self._get_sick_day_status()
self._show_sick_day_ui(status_text, status_color, countdown)
self.sick_remaining_time = countdown
self._update_sick_countdown()
def _show_sick_day_ui(
self,
status_text: str,
status_color: str,
countdown: int,
) -> None:
"""Display sick day UI labels and countdown."""
self._label("Sick Day Mode", color=self._colors.warning, pad="md")
self._text(status_text, color=status_color)
minutes = countdown // 60
self._text(
f"Please wait ~{minutes} min before unlocking...",
role="title",
pad="md",
)
self.sick_countdown_label = self._label(
str(countdown),
role="display",
scale=2.5,
pad="lg",
)
def _update_sick_countdown(self) -> None:
"""Update the sick day countdown timer."""
if self.sick_remaining_time > 0:
self.sick_countdown_label.config(text=str(self.sick_remaining_time))
self.sick_remaining_time -= 1
self.root.after(1000, self._update_sick_countdown)
else:
self._finalize_sick_day()
def _finalize_sick_day(self) -> None:
"""Persist sick-day history and unlock the screen."""
history = getattr(self, "_sick_history_cache", None)
if history is None:
history = _sick_tracker.load_history()
if _sick_tracker.had_commitment_for_today(history):
_sick_tracker.mark_commitment_broken(history)
self.workout_data["broke_commitment"] = "true"
new_debt = _sick_tracker.add_sick_day(history)
_sick_tracker.save_history(history)
self.workout_data["type"] = "sick_day"
self.workout_data["note"] = "Sick day - shutdown moved earlier"
self.workout_data["debt"] = str(new_debt)
self.unlock_screen()