screen-locker/screen_locker/_ui_flows_lockout.py
Krzysztof kuhy Rudnicki e5bfdd9a9a Make the Tk locker screens text-free
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>
2026-11-11 01:20:23 +01:00

123 lines
4.0 KiB
Python

"""Lockout countdown and phone-penalty flows for the 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
import logging
from typing import TYPE_CHECKING
from screen_locker import _glyphs as g
from screen_locker._constants import (
NO_PHONE_EXTRA_LOCKOUT_SECONDS,
PHONE_PENALTY_DELAY_DEMO,
PHONE_PENALTY_DELAY_PRODUCTION,
)
if TYPE_CHECKING:
from collections.abc import Callable
_logger = logging.getLogger(__name__)
class LockoutFlowMixin:
"""Lockout countdown and the no-phone penalty timer."""
# ------------------------------------------------------------------
# Lockout flow
# ------------------------------------------------------------------
def lockout(self) -> None:
"""Display lockout screen with countdown timer.
Was "Go work out! Locked for N seconds": the barbell in the danger
colour over the seconds counting down.
"""
self.clear_container()
self.lockout_label = self._label(
g.LIFT,
role="display",
scale=1.5,
color=self._colors.palette.danger,
pad="lg",
)
self.countdown_label = self._label(
str(self.lockout_time),
role="display",
scale=3.75,
pad="lg",
)
self.remaining_time = self.lockout_time
self.update_lockout_countdown()
def update_lockout_countdown(self) -> None:
"""Update the lockout countdown timer display."""
if self.remaining_time > 0:
self.countdown_label.config(text=str(self.remaining_time))
self.remaining_time -= 1
self.root.after(1000, self.update_lockout_countdown)
else:
self._start_phone_check()
# ------------------------------------------------------------------
# Phone penalty
# ------------------------------------------------------------------
def _show_phone_penalty(
self, message: str, *, on_done: Callable[[], None] | None = None
) -> None:
"""Show penalty countdown when phone verification is unavailable.
Was "Cannot Verify Workout", the reason, and "Connect phone via ADB
to skip this wait, or wait for the penalty timer". Now the phone glyph
crossed out over the seconds left; the reason goes to the log.
"""
_logger.warning("Phone check unavailable, penalty wait: %s", message)
self.clear_container()
self._phone_penalty_done_fn: Callable[[], None] = (
on_done
if on_done is not None
else lambda: self._show_retry_and_sick(message)
)
base_delay = (
PHONE_PENALTY_DELAY_DEMO
if self.demo_mode
else PHONE_PENALTY_DELAY_PRODUCTION
)
# Disconnecting the phone shouldn't be a fast path to the manual log.
delay = (
base_delay
if self.demo_mode
else base_delay + NO_PHONE_EXTRA_LOCKOUT_SECONDS
)
self._label(
f"{g.PHONE}{g.FAIL}",
role="display",
color=self._colors.palette.warning,
pad="md",
)
self.phone_penalty_remaining = delay
self.phone_penalty_label = self._label(
str(delay),
role="display",
scale=2.5,
pad="md",
)
# A sick day needs no wait: it is one tap on every screen.
self._sick_button(self._button_row())
self._update_phone_penalty()
def _update_phone_penalty(self) -> None:
"""Update phone penalty countdown."""
if self.phone_penalty_remaining > 0:
self.phone_penalty_label.config(
text=str(self.phone_penalty_remaining),
)
self.phone_penalty_remaining -= 1
self.root.after(1000, self._update_phone_penalty)
else:
self._phone_penalty_done_fn()