mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 21:27:13 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (flutter, flutter-analyze, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint,flutter-analyze) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (0) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (1) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (2) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (3) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (4) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (5) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (6) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (7) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (8) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (9) (push) Waiting to run
Tests / flutter-coverage (push) Blocked by required conditions
Tests / web-coverage (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
194 lines
7.1 KiB
Python
194 lines
7.1 KiB
Python
"""The individual rendered sections of the status window.
|
|
|
|
Split out of :mod:`screen_locker.status_view` to keep every file under the
|
|
250-line cap. Composed into ``StatusWindow`` there, alongside the other view
|
|
mixins, so the class's public surface is unchanged.
|
|
|
|
Every method here is display-only: it reads the snapshot it is handed and
|
|
paints widgets. Nothing in this module writes to disk or reaches the network.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from screen_locker._earned import hhmm
|
|
from screen_locker._sync_status import format_sync_line, gather_sync_status
|
|
|
|
if TYPE_CHECKING:
|
|
import tkinter as tk
|
|
|
|
from screen_locker._compliance_state import LockExplanation
|
|
from screen_locker._status_data import (
|
|
DayStatus,
|
|
ManualWorkoutBudgetStatus,
|
|
ShutdownProjection,
|
|
SickBudgetStatus,
|
|
WeeklySummary,
|
|
)
|
|
|
|
|
|
class StatusSectionsMixin:
|
|
"""Renders each section of the status window from a snapshot."""
|
|
|
|
def _section_today(self, parent: tk.Widget, day: DayStatus) -> None:
|
|
"""Render today's outcome."""
|
|
del parent
|
|
mark = "✓" if day.counted else ("😷" if day.is_sick_day else "—")
|
|
entry_str = ", ".join(day.entry_types) or (
|
|
"sick day" if day.is_sick_day else "no entry yet"
|
|
)
|
|
self._text(
|
|
f"{mark} Today ({day.label}): {entry_str}",
|
|
role="body",
|
|
color=self._colors.palette.success
|
|
if day.counted
|
|
else self._colors.palette.warning,
|
|
)
|
|
if day.source:
|
|
# Secondary provenance note on an already-shown entry -- a
|
|
# deliberately caption-sized exception to the 16px floor.
|
|
self._text(
|
|
day.source, role="caption", color=self._colors.palette.muted, pad="xs"
|
|
)
|
|
|
|
def _section_week(self, parent: tk.Widget, week: WeeklySummary) -> None:
|
|
"""Render this ISO week's per-day breakdown and totals."""
|
|
del parent
|
|
self._label(
|
|
f"This Week: {week.counted_count}/{week.minimum}", role="body", pad="sm"
|
|
)
|
|
for day in week.days:
|
|
mark = "✓" if day.counted else ("😷" if day.is_sick_day else "·")
|
|
entry_str = ", ".join(day.entry_types) or (
|
|
"sick day" if day.is_sick_day else "no entry"
|
|
)
|
|
self._text(
|
|
f"{mark} {day.label}: {entry_str}",
|
|
role="body",
|
|
color=self._colors.palette.muted,
|
|
pad="xs",
|
|
)
|
|
if week.remaining > 0:
|
|
self._text(
|
|
f"Need {week.remaining} more this week.",
|
|
role="body",
|
|
color=self._colors.palette.warning,
|
|
)
|
|
elif week.extra > 0:
|
|
self._text(
|
|
f"{week.extra} above the weekly minimum!",
|
|
role="body",
|
|
color=self._colors.palette.success,
|
|
)
|
|
|
|
def _section_lock_explanation(
|
|
self, parent: tk.Widget, expl: LockExplanation
|
|
) -> None:
|
|
"""Render why the lock did/didn't fire today, plus its evaluation trace."""
|
|
del parent
|
|
self._label("Why the lock did/didn't fire", role="body", pad="sm")
|
|
self._text(
|
|
expl.reason,
|
|
role="body",
|
|
color=self._colors.palette.danger
|
|
if expl.fired
|
|
else self._colors.palette.success,
|
|
)
|
|
if expl.auto_upgrade.would_attempt:
|
|
self._text(
|
|
f"Pending auto-upgrade: {expl.auto_upgrade.reason}",
|
|
role="label",
|
|
color=self._colors.palette.warning,
|
|
)
|
|
|
|
def _section_sick_budget(self, parent: tk.Widget, sick: SickBudgetStatus) -> None:
|
|
"""Render rolling sick-day budget usage."""
|
|
del parent
|
|
self._label("Sick Budget", role="body", pad="sm")
|
|
self._text(
|
|
f"{sick.used_7d}/{sick.budget_7d} week · {sick.used_30d}/{sick.budget_30d} "
|
|
f"month · {sick.used_90d}/{sick.budget_90d} quarter · debt {sick.debt}",
|
|
role="body",
|
|
color=self._colors.palette.danger
|
|
if sick.exhausted
|
|
else self._colors.palette.muted,
|
|
)
|
|
|
|
def _section_manual_workout_budget(
|
|
self, parent: tk.Widget, manual: ManualWorkoutBudgetStatus
|
|
) -> None:
|
|
"""Render rolling manual-workout budget usage."""
|
|
del parent
|
|
self._label("Manual Workout Budget", role="body", pad="sm")
|
|
self._text(
|
|
f"{manual.used_7d}/{manual.budget_7d} week · "
|
|
f"{manual.used_30d}/{manual.budget_30d} month",
|
|
role="body",
|
|
color=self._colors.palette.danger
|
|
if manual.exhausted
|
|
else self._colors.palette.muted,
|
|
)
|
|
|
|
def _section_sync_backend(self, parent: tk.Widget) -> None:
|
|
"""Render which backend this machine syncs through.
|
|
|
|
Read from local state only (see :mod:`screen_locker._sync_status`), so
|
|
opening or refreshing the window never makes a network call. Coloured
|
|
by ``healthy`` rather than by backend: "configured but never pushed"
|
|
looks fine everywhere else and produces no data, which is exactly the
|
|
state worth surfacing here.
|
|
"""
|
|
del parent
|
|
status = gather_sync_status()
|
|
self._label("Sync backend", role="body", pad="sm")
|
|
self._text(
|
|
format_sync_line(status),
|
|
role="body",
|
|
color=self._colors.palette.muted
|
|
if status.healthy
|
|
else self._colors.palette.danger,
|
|
)
|
|
|
|
def _section_shutdown(
|
|
self, parent: tk.Widget, shutdown: ShutdownProjection
|
|
) -> None:
|
|
"""Render tonight's live config, rest-of-week, and next-week preview."""
|
|
del parent
|
|
self._label("Shutdown Time", role="body", pad="sm")
|
|
if shutdown.tonight is not None:
|
|
mon_wed, thu_sun, _morning = shutdown.tonight
|
|
self._text(
|
|
f"Live config — Mon-Wed {hhmm(mon_wed)}, Thu-Sun {hhmm(thu_sun)}",
|
|
role="body",
|
|
)
|
|
else:
|
|
self._text(
|
|
"Live shutdown config unavailable.",
|
|
role="body",
|
|
color=self._colors.palette.warning,
|
|
)
|
|
# Rest-of-week/next-week/explanation are speculative annotations, not
|
|
# the section's primary content -- a deliberate caption-sized
|
|
# exception to the 16px floor, all sharing one muted tone instead of
|
|
# three unrelated ad hoc grays.
|
|
rest_line = ", ".join(
|
|
f"{d.label} {hhmm(d.minutes)}" for d in shutdown.rest_of_week
|
|
)
|
|
self._text(
|
|
f"Rest of week: {rest_line}",
|
|
role="caption",
|
|
color=self._colors.palette.muted,
|
|
)
|
|
next_line = ", ".join(
|
|
f"{d.label} {hhmm(d.minutes)}" for d in shutdown.next_week_preview
|
|
)
|
|
self._text(
|
|
f"Next week (speculative): {next_line}",
|
|
role="caption",
|
|
color=self._colors.palette.muted,
|
|
)
|
|
self._text(
|
|
shutdown.explanation, role="caption", color=self._colors.palette.muted
|
|
)
|