mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 14:07:41 +01:00
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
This commit is contained in:
parent
3476dd3afd
commit
0714c2264d
@ -19,8 +19,8 @@ churn, not size alone — refactoring pays where code is read and changed often
|
||||
|
||||
<!-- BEGIN GENERATED VIOLATIONS -->
|
||||
|
||||
- **61 files** currently exceed 250 lines.
|
||||
- **9,084 lines** over the cap in total (the work left to do); longest file is **897** lines.
|
||||
- **41 files** currently exceed 250 lines.
|
||||
- **6,753 lines** over the cap in total (the work left to do); longest file is **897** lines.
|
||||
|
||||
ROI = lines x commits in the last year. Work top-down; a long file nobody edits
|
||||
has near-zero payoff and should not be first.
|
||||
@ -33,7 +33,6 @@ has near-zero payoff and should not be first.
|
||||
| 323 | 22 | code | `screen_locker/tests/conftest.py` |
|
||||
| 480 | 12 | code | `stronglift_replacement/workout_app/test/screens/settings_screen_test.dart` |
|
||||
| 633 | 9 | code | `stronglift_replacement/workout_app/lib/screens/workout_screen.dart` |
|
||||
| 343 | 16 | code | `screen_locker/tests/test_init_and_log.py` |
|
||||
| 740 | 7 | code | `stronglift_replacement/workout_app/test/services/workout_sync_service_test.dart` |
|
||||
| 370 | 14 | code | `screen_locker/_ui_flows.py` |
|
||||
| 380 | 13 | code | `screen_locker/_phone_verification.py` |
|
||||
@ -42,8 +41,9 @@ has near-zero payoff and should not be first.
|
||||
| 357 | 9 | code | `screen_locker/_manual_workout_dialog.py` |
|
||||
| 384 | 8 | code | `screen_locker/_shutdown.py` |
|
||||
| 383 | 8 | code | `stronglift_replacement/workout_app/lib/screens/home_screen.dart` |
|
||||
| 396 | 7 | code | `screen_locker/status_view.py` |
|
||||
|
||||
_(46 further files over 250 lines not listed — re-run `python3 scripts/refresh_refactor_todo.py` for the current set.)_
|
||||
_(26 further files over 250 lines not listed — re-run `python3 scripts/refresh_refactor_todo.py` for the current set.)_
|
||||
|
||||
<!-- END GENERATED VIOLATIONS -->
|
||||
|
||||
|
||||
@ -6,18 +6,19 @@ from concurrent.futures import ThreadPoolExecutor # pylint: disable=no-name-in-
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from screen_locker import _manual_workout, _sick_tracker
|
||||
from screen_locker._constants import (
|
||||
NO_PHONE_EXTRA_LOCKOUT_SECONDS,
|
||||
PHONE_PENALTY_DELAY_DEMO,
|
||||
PHONE_PENALTY_DELAY_PRODUCTION,
|
||||
)
|
||||
from screen_locker._ui_flows_lockout import LockoutFlowMixin
|
||||
from screen_locker._ui_flows_sick import SickDayFlowMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
class UIFlowsMixin:
|
||||
"""Mixin providing UI flow logic for the screen locker."""
|
||||
class UIFlowsMixin(SickDayFlowMixin, LockoutFlowMixin):
|
||||
"""Mixin providing UI flow logic for the screen locker.
|
||||
|
||||
The sick-day and lockout/penalty flows live in sibling modules and are
|
||||
composed in here, so ``ScreenLocker``'s own mixin list stays as it was.
|
||||
"""
|
||||
|
||||
def paint_phone_check(self) -> None:
|
||||
"""Paint the "checking phone" screen, without starting the check.
|
||||
@ -198,173 +199,3 @@ class UIFlowsMixin:
|
||||
self._runnerup_on_failure()
|
||||
else:
|
||||
self.root.after(500, self._poll_runnerup_fallback)
|
||||
|
||||
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()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Lockout flow
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def lockout(self) -> None:
|
||||
"""Display lockout screen with countdown timer."""
|
||||
self.clear_container()
|
||||
self.lockout_label = self._label(
|
||||
f"Go work out!\nLocked for {self.lockout_time} seconds",
|
||||
role="display",
|
||||
scale=1.5,
|
||||
color=self._colors.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."""
|
||||
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 into sick mode.
|
||||
delay = (
|
||||
base_delay
|
||||
if self.demo_mode
|
||||
else base_delay + NO_PHONE_EXTRA_LOCKOUT_SECONDS
|
||||
)
|
||||
self._label(
|
||||
"Cannot Verify Workout",
|
||||
role="display",
|
||||
color=self._colors.warning,
|
||||
pad="md",
|
||||
)
|
||||
self._text(message, color=self._colors.warning)
|
||||
self._text(
|
||||
"Connect phone via ADB to skip this wait,\n"
|
||||
"or wait for the penalty timer.\n\n"
|
||||
"Note: Phone must be rooted and StrongLifts installed.",
|
||||
role="body",
|
||||
)
|
||||
self.phone_penalty_remaining = delay
|
||||
self.phone_penalty_label = self._label(
|
||||
str(delay),
|
||||
role="display",
|
||||
scale=2.5,
|
||||
pad="md",
|
||||
)
|
||||
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()
|
||||
|
||||
113
screen_locker/_ui_flows_lockout.py
Normal file
113
screen_locker/_ui_flows_lockout.py
Normal file
@ -0,0 +1,113 @@
|
||||
"""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
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
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
|
||||
|
||||
|
||||
class LockoutFlowMixin:
|
||||
"""Lockout countdown and the no-phone penalty timer."""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Lockout flow
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def lockout(self) -> None:
|
||||
"""Display lockout screen with countdown timer."""
|
||||
self.clear_container()
|
||||
self.lockout_label = self._label(
|
||||
f"Go work out!\nLocked for {self.lockout_time} seconds",
|
||||
role="display",
|
||||
scale=1.5,
|
||||
color=self._colors.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."""
|
||||
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 into sick mode.
|
||||
delay = (
|
||||
base_delay
|
||||
if self.demo_mode
|
||||
else base_delay + NO_PHONE_EXTRA_LOCKOUT_SECONDS
|
||||
)
|
||||
self._label(
|
||||
"Cannot Verify Workout",
|
||||
role="display",
|
||||
color=self._colors.warning,
|
||||
pad="md",
|
||||
)
|
||||
self._text(message, color=self._colors.warning)
|
||||
self._text(
|
||||
"Connect phone via ADB to skip this wait,\n"
|
||||
"or wait for the penalty timer.\n\n"
|
||||
"Note: Phone must be rooted and StrongLifts installed.",
|
||||
role="body",
|
||||
)
|
||||
self.phone_penalty_remaining = delay
|
||||
self.phone_penalty_label = self._label(
|
||||
str(delay),
|
||||
role="display",
|
||||
scale=2.5,
|
||||
pad="md",
|
||||
)
|
||||
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()
|
||||
94
screen_locker/_ui_flows_sick.py
Normal file
94
screen_locker/_ui_flows_sick.py
Normal file
@ -0,0 +1,94 @@
|
||||
"""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()
|
||||
Loading…
Reference in New Issue
Block a user