mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-10 19:27:02 +01:00
Route _alarm.py's dismiss-screen colors through gatelock's LockConfig
Replaces ~15 hardcoded hex/named-color literals with the shared unified-design-system palette (bg/fg/muted/danger/warning/success), matching diet-guard/screen-locker. Extracts widget construction into _alarm_view.py to keep _alarm.py under the repo's 500-line cap.
This commit is contained in:
parent
2c10788d44
commit
870a71665d
@ -21,6 +21,7 @@ import tkinter as tk
|
||||
from gatelock import GateRoot, LockConfig, LockWindow
|
||||
|
||||
from wake_alarm._alarm_display import _restore_display, _wake_display
|
||||
from wake_alarm._alarm_view import _build_alarm_view
|
||||
from wake_alarm._audio import (
|
||||
_beep_loud,
|
||||
_beep_medium,
|
||||
@ -68,20 +69,6 @@ def _is_alarm_day() -> bool:
|
||||
return datetime.now(tz=timezone.utc).weekday() in ALARM_DAYS
|
||||
|
||||
|
||||
@dataclass
|
||||
class _AlarmView:
|
||||
"""The Tk widgets that make up the alarm's dismiss-challenge screen."""
|
||||
|
||||
container: tk.Frame
|
||||
title_label: tk.Label
|
||||
round_label: tk.Label
|
||||
info_label: tk.Label
|
||||
code_label: tk.Label
|
||||
entry: tk.Entry
|
||||
status_label: tk.Label
|
||||
timer_label: tk.Label
|
||||
|
||||
|
||||
@dataclass
|
||||
class _AlarmProgress:
|
||||
"""Mutable dismiss-challenge progress state."""
|
||||
@ -121,11 +108,14 @@ class WakeAlarm:
|
||||
self.root.title("Wake Alarm" + (" [DEMO]" if demo_mode else ""))
|
||||
# mode="soft": no watchdog exists yet for a clean, silent lock
|
||||
# failure, so hard-lock (overrideredirect + grab) is deferred.
|
||||
self._lock = LockWindow(self.root, LockConfig(mode="soft"), hooks=self)
|
||||
config = LockConfig(mode="soft")
|
||||
self._lock = LockWindow(self.root, config, hooks=self)
|
||||
self._lock.setup()
|
||||
|
||||
self._progress = _AlarmProgress(current_challenge=_make_challenge())
|
||||
self._view = self._build_ui()
|
||||
self._view = _build_alarm_view(
|
||||
self.root, self._progress.current_challenge, config, self._on_submit
|
||||
)
|
||||
self._update_timer()
|
||||
if self._progress.current_challenge.kind == "flash":
|
||||
self._start_flash_countdown()
|
||||
@ -159,92 +149,6 @@ class WakeAlarm:
|
||||
_restore_display()
|
||||
turn_off_plug()
|
||||
|
||||
def _build_ui(self) -> _AlarmView:
|
||||
"""Build the dismiss-challenge UI and return its widgets as a view."""
|
||||
challenge = self._progress.current_challenge
|
||||
|
||||
container = tk.Frame(self.root, bg="#1a1a1a")
|
||||
container.place(relx=0.5, rely=0.5, anchor="center")
|
||||
|
||||
title_label = tk.Label(
|
||||
container,
|
||||
text="WAKE UP!",
|
||||
font=("Arial", 48, "bold"),
|
||||
fg="#ff4444",
|
||||
bg="#1a1a1a",
|
||||
)
|
||||
title_label.pack(pady=20)
|
||||
|
||||
round_label = tk.Label(
|
||||
container,
|
||||
text=f"Round 1 / {DISMISS_ROUNDS_REQUIRED}",
|
||||
font=("Arial", 24, "bold"),
|
||||
fg="#ffaa00",
|
||||
bg="#1a1a1a",
|
||||
)
|
||||
round_label.pack(pady=5)
|
||||
|
||||
info_label = tk.Label(
|
||||
container,
|
||||
text=challenge.hint,
|
||||
font=("Arial", 18),
|
||||
fg="white",
|
||||
bg="#1a1a1a",
|
||||
)
|
||||
info_label.pack(pady=10)
|
||||
|
||||
# Non-flash types use a smaller font because their display text is wider.
|
||||
code_font_size = (
|
||||
48 if challenge.kind in ("math", "sort", "sequence", "reverse") else 72
|
||||
)
|
||||
code_label = tk.Label(
|
||||
container,
|
||||
text=challenge.display,
|
||||
font=("Courier", code_font_size, "bold"),
|
||||
fg="#00ff00",
|
||||
bg="#1a1a1a",
|
||||
)
|
||||
code_label.pack(pady=30)
|
||||
|
||||
entry = tk.Entry(
|
||||
container,
|
||||
font=("Courier", 36),
|
||||
justify="center",
|
||||
width=12,
|
||||
)
|
||||
entry.pack(pady=10)
|
||||
entry.focus_set()
|
||||
entry.bind("<Return>", self._on_submit)
|
||||
|
||||
status_label = tk.Label(
|
||||
container,
|
||||
text="",
|
||||
font=("Arial", 18),
|
||||
fg="#ff4444",
|
||||
bg="#1a1a1a",
|
||||
)
|
||||
status_label.pack(pady=10)
|
||||
|
||||
timer_label = tk.Label(
|
||||
container,
|
||||
text="",
|
||||
font=("Arial", 14),
|
||||
fg="#aaaaaa",
|
||||
bg="#1a1a1a",
|
||||
)
|
||||
timer_label.pack(pady=5)
|
||||
|
||||
return _AlarmView(
|
||||
container=container,
|
||||
title_label=title_label,
|
||||
round_label=round_label,
|
||||
info_label=info_label,
|
||||
code_label=code_label,
|
||||
entry=entry,
|
||||
status_label=status_label,
|
||||
timer_label=timer_label,
|
||||
)
|
||||
|
||||
def _on_submit(self, _event: object = None) -> None:
|
||||
"""Handle challenge submission.
|
||||
|
||||
@ -260,7 +164,7 @@ class WakeAlarm:
|
||||
if self._progress.current_challenge.kind == "flash":
|
||||
self._view.code_label.configure(
|
||||
text=self._progress.current_challenge.display,
|
||||
fg="#00ff00",
|
||||
fg=self._view.config.success,
|
||||
)
|
||||
self._start_flash_countdown()
|
||||
return
|
||||
@ -271,7 +175,7 @@ class WakeAlarm:
|
||||
self._progress.current_challenge = _make_challenge()
|
||||
self._view.code_label.configure(
|
||||
text=self._progress.current_challenge.display,
|
||||
fg="#00ff00",
|
||||
fg=self._view.config.success,
|
||||
)
|
||||
self._view.info_label.configure(text=self._progress.current_challenge.hint)
|
||||
self._view.entry.delete(0, tk.END)
|
||||
@ -302,7 +206,7 @@ class WakeAlarm:
|
||||
self.root.after(1000, self._flash_tick)
|
||||
else:
|
||||
hidden = "?" * len(self._progress.current_challenge.display)
|
||||
self._view.code_label.configure(text=hidden, fg="#555555")
|
||||
self._view.code_label.configure(text=hidden, fg=self._view.config.muted)
|
||||
self._view.status_label.configure(text="Now type the code from memory!")
|
||||
|
||||
def _dismiss_alarm(self, *, earned_skip: bool) -> None:
|
||||
@ -321,14 +225,15 @@ class WakeAlarm:
|
||||
if earned_skip
|
||||
else "Alarm dismissed. No workout skip."
|
||||
)
|
||||
color = "#00ff00" if earned_skip else "#ffaa00"
|
||||
cfg = self._view.config
|
||||
color = cfg.success if earned_skip else cfg.warning
|
||||
|
||||
tk.Label(
|
||||
self._view.container,
|
||||
text=msg,
|
||||
font=("Arial", 36, "bold"),
|
||||
fg=color,
|
||||
bg="#1a1a1a",
|
||||
bg=cfg.bg,
|
||||
).pack(pady=30)
|
||||
|
||||
self.root.after(3000, self._lock.close)
|
||||
@ -344,7 +249,7 @@ class WakeAlarm:
|
||||
self._progress.current_challenge = _make_challenge()
|
||||
self._view.code_label.configure(
|
||||
text=self._progress.current_challenge.display,
|
||||
fg="#00ff00",
|
||||
fg=self._view.config.success,
|
||||
)
|
||||
self._view.info_label.configure(text=self._progress.current_challenge.hint)
|
||||
self._view.entry.delete(0, tk.END)
|
||||
@ -406,7 +311,8 @@ class WakeAlarm:
|
||||
"""Alternate background colour every 750 ms (below seizure-risk 3 Hz)."""
|
||||
if not self._active:
|
||||
return
|
||||
self.root.configure(bg="#ff0000" if self._progress.flash_on else "#1a1a1a")
|
||||
cfg = self._view.config
|
||||
self.root.configure(bg=cfg.danger if self._progress.flash_on else cfg.bg)
|
||||
self._progress.flash_on = not self._progress.flash_on
|
||||
self.root.after(750, self._flash_step)
|
||||
|
||||
|
||||
122
wake_alarm/_alarm_view.py
Normal file
122
wake_alarm/_alarm_view.py
Normal file
@ -0,0 +1,122 @@
|
||||
"""Widget construction for the wake alarm's dismiss-challenge screen."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import tkinter as tk
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from wake_alarm._constants import DISMISS_ROUNDS_REQUIRED
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from gatelock import LockConfig
|
||||
|
||||
from wake_alarm._challenges import _Challenge
|
||||
|
||||
|
||||
@dataclass
|
||||
class _AlarmView:
|
||||
"""The Tk widgets that make up the alarm's dismiss-challenge screen."""
|
||||
|
||||
container: tk.Frame
|
||||
title_label: tk.Label
|
||||
round_label: tk.Label
|
||||
info_label: tk.Label
|
||||
code_label: tk.Label
|
||||
entry: tk.Entry
|
||||
status_label: tk.Label
|
||||
timer_label: tk.Label
|
||||
config: LockConfig
|
||||
|
||||
|
||||
def _build_alarm_view(
|
||||
root: tk.Tk,
|
||||
challenge: _Challenge,
|
||||
config: LockConfig,
|
||||
on_submit: Callable[[object], None],
|
||||
) -> _AlarmView:
|
||||
"""Build the dismiss-challenge UI and return its widgets as a view."""
|
||||
container = tk.Frame(root, bg=config.bg)
|
||||
container.place(relx=0.5, rely=0.5, anchor="center")
|
||||
|
||||
title_label = tk.Label(
|
||||
container,
|
||||
text="WAKE UP!",
|
||||
font=("Arial", 48, "bold"),
|
||||
fg=config.danger,
|
||||
bg=config.bg,
|
||||
)
|
||||
title_label.pack(pady=20)
|
||||
|
||||
round_label = tk.Label(
|
||||
container,
|
||||
text=f"Round 1 / {DISMISS_ROUNDS_REQUIRED}",
|
||||
font=("Arial", 24, "bold"),
|
||||
fg=config.warning,
|
||||
bg=config.bg,
|
||||
)
|
||||
round_label.pack(pady=5)
|
||||
|
||||
info_label = tk.Label(
|
||||
container,
|
||||
text=challenge.hint,
|
||||
font=("Arial", 18),
|
||||
fg=config.fg,
|
||||
bg=config.bg,
|
||||
)
|
||||
info_label.pack(pady=10)
|
||||
|
||||
# Non-flash types use a smaller font because their display text is wider.
|
||||
code_font_size = (
|
||||
48 if challenge.kind in ("math", "sort", "sequence", "reverse") else 72
|
||||
)
|
||||
code_label = tk.Label(
|
||||
container,
|
||||
text=challenge.display,
|
||||
font=("Courier", code_font_size, "bold"),
|
||||
fg=config.success,
|
||||
bg=config.bg,
|
||||
)
|
||||
code_label.pack(pady=30)
|
||||
|
||||
entry = tk.Entry(
|
||||
container,
|
||||
font=("Courier", 36),
|
||||
justify="center",
|
||||
width=12,
|
||||
)
|
||||
entry.pack(pady=10)
|
||||
entry.focus_set()
|
||||
entry.bind("<Return>", on_submit)
|
||||
|
||||
status_label = tk.Label(
|
||||
container,
|
||||
text="",
|
||||
font=("Arial", 18),
|
||||
fg=config.danger,
|
||||
bg=config.bg,
|
||||
)
|
||||
status_label.pack(pady=10)
|
||||
|
||||
timer_label = tk.Label(
|
||||
container,
|
||||
text="",
|
||||
font=("Arial", 14),
|
||||
fg=config.muted,
|
||||
bg=config.bg,
|
||||
)
|
||||
timer_label.pack(pady=5)
|
||||
|
||||
return _AlarmView(
|
||||
container=container,
|
||||
title_label=title_label,
|
||||
round_label=round_label,
|
||||
info_label=info_label,
|
||||
code_label=code_label,
|
||||
entry=entry,
|
||||
status_label=status_label,
|
||||
timer_label=timer_label,
|
||||
config=config,
|
||||
)
|
||||
@ -60,6 +60,7 @@ def _block_real_tk() -> Generator[MagicMock]:
|
||||
mock = _make_mock_tk()
|
||||
with (
|
||||
patch("wake_alarm._alarm.tk", mock),
|
||||
patch("wake_alarm._alarm_view.tk", mock),
|
||||
patch(
|
||||
"wake_alarm._alarm.GateRoot",
|
||||
return_value=mock.Tk.return_value,
|
||||
@ -74,6 +75,7 @@ def mock_tk_module() -> Generator[MagicMock]:
|
||||
mock = _make_mock_tk()
|
||||
with (
|
||||
patch("wake_alarm._alarm.tk", mock),
|
||||
patch("wake_alarm._alarm_view.tk", mock),
|
||||
patch(
|
||||
"wake_alarm._alarm.GateRoot",
|
||||
return_value=mock.Tk.return_value,
|
||||
|
||||
@ -42,6 +42,7 @@ def _block_real_tk() -> Generator[MagicMock]:
|
||||
mock = _make_mock_tk()
|
||||
with (
|
||||
patch("wake_alarm._alarm.tk", mock),
|
||||
patch("wake_alarm._alarm_view.tk", mock),
|
||||
patch(
|
||||
"wake_alarm._alarm.GateRoot",
|
||||
return_value=mock.Tk.return_value,
|
||||
@ -75,6 +76,7 @@ def mock_tk_module() -> Generator[MagicMock]:
|
||||
mock = _make_mock_tk()
|
||||
with (
|
||||
patch("wake_alarm._alarm.tk", mock),
|
||||
patch("wake_alarm._alarm_view.tk", mock),
|
||||
patch(
|
||||
"wake_alarm._alarm.GateRoot",
|
||||
return_value=mock.Tk.return_value,
|
||||
@ -397,7 +399,7 @@ class TestScreenFlash:
|
||||
alarm._progress.flash_on = False
|
||||
alarm._flash_step()
|
||||
|
||||
mock_root.configure.assert_called_once_with(bg="#1a1a1a")
|
||||
mock_root.configure.assert_called_once_with(bg=alarm._view.config.bg)
|
||||
assert alarm._progress.flash_on is True
|
||||
mock_root.after.assert_called_with(750, alarm._flash_step)
|
||||
alarm._stop_beep.set()
|
||||
@ -415,7 +417,7 @@ class TestScreenFlash:
|
||||
alarm._progress.flash_on = True
|
||||
alarm._flash_step()
|
||||
|
||||
mock_root.configure.assert_called_once_with(bg="#ff0000")
|
||||
mock_root.configure.assert_called_once_with(bg=alarm._view.config.danger)
|
||||
assert alarm._progress.flash_on is False
|
||||
mock_root.after.assert_called_with(750, alarm._flash_step)
|
||||
alarm._stop_beep.set()
|
||||
|
||||
@ -41,6 +41,7 @@ def _block_real_tk() -> Generator[MagicMock]:
|
||||
mock = _make_mock_tk()
|
||||
with (
|
||||
patch("wake_alarm._alarm.tk", mock),
|
||||
patch("wake_alarm._alarm_view.tk", mock),
|
||||
patch(
|
||||
"wake_alarm._alarm.GateRoot",
|
||||
return_value=mock.Tk.return_value,
|
||||
@ -74,6 +75,7 @@ def mock_tk_module() -> Generator[MagicMock]:
|
||||
mock = _make_mock_tk()
|
||||
with (
|
||||
patch("wake_alarm._alarm.tk", mock),
|
||||
patch("wake_alarm._alarm_view.tk", mock),
|
||||
patch(
|
||||
"wake_alarm._alarm.GateRoot",
|
||||
return_value=mock.Tk.return_value,
|
||||
|
||||
@ -40,6 +40,7 @@ def _block_real_tk() -> Generator[MagicMock]:
|
||||
mock = _make_mock_tk()
|
||||
with (
|
||||
patch("wake_alarm._alarm.tk", mock),
|
||||
patch("wake_alarm._alarm_view.tk", mock),
|
||||
patch(
|
||||
"wake_alarm._alarm.GateRoot",
|
||||
return_value=mock.Tk.return_value,
|
||||
@ -73,6 +74,7 @@ def mock_tk_module() -> Generator[MagicMock]:
|
||||
mock = _make_mock_tk()
|
||||
with (
|
||||
patch("wake_alarm._alarm.tk", mock),
|
||||
patch("wake_alarm._alarm_view.tk", mock),
|
||||
patch(
|
||||
"wake_alarm._alarm.GateRoot",
|
||||
return_value=mock.Tk.return_value,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user