mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 22:43:02 +02:00
Split 16+ files. 27 files still need splitting. See session notes.
270 lines
9.9 KiB
Python
270 lines
9.9 KiB
Python
"""Workout form methods mixin for the screen locker."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from python_pkg.screen_locker._constants import (
|
|
MAX_DISTANCE_KM,
|
|
MAX_PACE_MIN_PER_KM,
|
|
MAX_REPS,
|
|
MAX_SETS,
|
|
MAX_TIME_MINUTES,
|
|
MAX_WEIGHT_KG,
|
|
MIN_EXERCISE_NAME_LEN,
|
|
STRENGTH_FIELDS,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
import tkinter as tk
|
|
|
|
|
|
class WorkoutFormsMixin:
|
|
"""Mixin providing workout form creation and validation."""
|
|
|
|
# ------------------------------------------------------------------
|
|
# Workout type selection
|
|
# ------------------------------------------------------------------
|
|
|
|
def ask_workout_type(self) -> None:
|
|
"""Display workout type selection dialog."""
|
|
self.clear_container()
|
|
self._label("What type of workout?", pady=30)
|
|
frame = self._button_row()
|
|
self._button(
|
|
frame,
|
|
"STRENGTH",
|
|
bg="#cc6600",
|
|
command=self.ask_strength_details,
|
|
width=12,
|
|
).pack(side="left", padx=20)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Running workout
|
|
# ------------------------------------------------------------------
|
|
|
|
def _create_running_entries(self) -> list[tk.Entry]:
|
|
"""Create running workout entry fields."""
|
|
self.distance_entry = self._entry_row("Distance (km):")
|
|
self.time_entry = self._entry_row("Time (minutes):")
|
|
self.pace_entry = self._entry_row("Pace (min/km):")
|
|
return [self.distance_entry, self.time_entry, self.pace_entry]
|
|
|
|
def ask_running_details(self) -> None:
|
|
"""Display running workout input form."""
|
|
self.clear_container()
|
|
self.workout_data["type"] = "running"
|
|
self._label("Running Details", pady=20)
|
|
entries = self._create_running_entries()
|
|
self._setup_form_controls(
|
|
entries,
|
|
self.verify_running_data,
|
|
self.ask_workout_type,
|
|
)
|
|
|
|
def _check_running_ranges(
|
|
self,
|
|
distance: float,
|
|
time_mins: float,
|
|
pace: float,
|
|
) -> str | None:
|
|
"""Check if running values are in valid ranges."""
|
|
if distance <= 0 or distance > MAX_DISTANCE_KM:
|
|
return f"Distance seems unrealistic (0-{MAX_DISTANCE_KM} km)"
|
|
if time_mins <= 0 or time_mins > MAX_TIME_MINUTES:
|
|
return f"Time seems unrealistic (0-{MAX_TIME_MINUTES} minutes)"
|
|
if pace <= 0 or pace > MAX_PACE_MIN_PER_KM:
|
|
return f"Pace seems unrealistic (0-{MAX_PACE_MIN_PER_KM} min/km)"
|
|
expected_pace = time_mins / distance
|
|
tolerance = expected_pace * 0.15 # 15% tolerance
|
|
if abs(pace - expected_pace) > tolerance:
|
|
return (
|
|
f"Pace doesn't match! "
|
|
f"Expected ~{expected_pace:.2f} min/km, got {pace:.2f}"
|
|
)
|
|
return None
|
|
|
|
def _validate_running_input(self) -> tuple[float, float, float] | None:
|
|
"""Parse and validate running input fields."""
|
|
try:
|
|
distance = float(self.distance_entry.get())
|
|
time_mins = float(self.time_entry.get())
|
|
pace = float(self.pace_entry.get())
|
|
except ValueError:
|
|
self.show_error("Please enter valid numbers")
|
|
return None
|
|
error = self._check_running_ranges(distance, time_mins, pace)
|
|
if error:
|
|
self.show_error(error)
|
|
return None
|
|
return distance, time_mins, pace
|
|
|
|
def verify_running_data(self) -> None:
|
|
"""Validate running workout data and unlock if valid."""
|
|
result = self._validate_running_input()
|
|
if result is None:
|
|
return
|
|
distance, time_mins, pace = result
|
|
self.workout_data["distance_km"] = str(distance)
|
|
self.workout_data["time_minutes"] = str(time_mins)
|
|
self.workout_data["pace_min_per_km"] = str(pace)
|
|
self._attempt_unlock()
|
|
|
|
# ------------------------------------------------------------------
|
|
# Strength workout
|
|
# ------------------------------------------------------------------
|
|
|
|
def _create_strength_entries(self) -> list[tk.Entry]:
|
|
"""Create strength training entry fields."""
|
|
entries = [
|
|
self._entry_row(lbl, width=w, font_size=18) for lbl, w in STRENGTH_FIELDS
|
|
]
|
|
(
|
|
self.exercises_entry,
|
|
self.sets_entry,
|
|
self.reps_entry,
|
|
self.weights_entry,
|
|
self.total_weight_entry,
|
|
) = entries
|
|
return entries
|
|
|
|
def ask_strength_details(self) -> None:
|
|
"""Display strength training input form."""
|
|
self.clear_container()
|
|
self.workout_data["type"] = "strength"
|
|
self._label("Strength Training Details", pady=20)
|
|
entries = self._create_strength_entries()
|
|
self._setup_form_controls(
|
|
entries,
|
|
self.verify_strength_data,
|
|
self.ask_workout_type,
|
|
)
|
|
|
|
def _parse_reps(self, reps_raw: list[str]) -> list[list[int]]:
|
|
"""Parse reps input - single number or variable reps like '12+11+12'."""
|
|
reps: list[list[int]] = []
|
|
for r in reps_raw:
|
|
if "+" in r:
|
|
reps.append([int(x.strip()) for x in r.split("+")])
|
|
else:
|
|
reps.append([int(r)])
|
|
return reps
|
|
|
|
def _validate_strength_inputs(
|
|
self,
|
|
exercises: list[str],
|
|
sets: list[int],
|
|
reps: list[list[int]],
|
|
weights: list[float],
|
|
) -> str | None:
|
|
"""Validate strength workout inputs. Returns error message or None."""
|
|
if not (len(exercises) == len(sets) == len(reps) == len(weights)):
|
|
return "Number of exercises, sets, reps, and weights must match"
|
|
if any(len(ex) < MIN_EXERCISE_NAME_LEN for ex in exercises):
|
|
return "Exercise names too short - be specific"
|
|
if any(s < 1 or s > MAX_SETS for s in sets):
|
|
return f"Sets should be between 1-{MAX_SETS}"
|
|
if any(w < 0 or w > MAX_WEIGHT_KG for w in weights):
|
|
return f"Weights should be between 0-{MAX_WEIGHT_KG} kg"
|
|
return self._validate_reps(exercises, sets, reps)
|
|
|
|
def _validate_reps(
|
|
self,
|
|
exercises: list[str],
|
|
sets: list[int],
|
|
reps: list[list[int]],
|
|
) -> str | None:
|
|
"""Validate reps data. Returns error message or None if valid."""
|
|
for i, rep_list in enumerate(reps):
|
|
if any(r < 1 or r > MAX_REPS for r in rep_list):
|
|
return f"Reps should be between 1-{MAX_REPS}"
|
|
if len(rep_list) > 1 and len(rep_list) != sets[i]:
|
|
return (
|
|
f"For {exercises[i]!r}: variable reps count "
|
|
f"({len(rep_list)}) doesn't match sets ({sets[i]})"
|
|
)
|
|
return None
|
|
|
|
def _calculate_expected_total(
|
|
self,
|
|
sets: list[int],
|
|
reps: list[list[int]],
|
|
weights: list[float],
|
|
) -> float:
|
|
"""Calculate expected total weight lifted."""
|
|
expected_total = 0.0
|
|
for i, rep_list in enumerate(reps):
|
|
if len(rep_list) == 1:
|
|
expected_total += sets[i] * rep_list[0] * weights[i]
|
|
else:
|
|
expected_total += sum(rep_list) * weights[i]
|
|
return expected_total
|
|
|
|
def _parse_strength_entries(
|
|
self,
|
|
) -> tuple[list[str], list[int], list[list[int]], list[float], float]:
|
|
"""Parse raw strength training input from entry widgets."""
|
|
exercises = [e.strip() for e in self.exercises_entry.get().split(",")]
|
|
sets = [int(s.strip()) for s in self.sets_entry.get().split(",")]
|
|
reps_raw = [r.strip() for r in self.reps_entry.get().split(",")]
|
|
reps = self._parse_reps(reps_raw)
|
|
weights = [float(w.strip()) for w in self.weights_entry.get().split(",")]
|
|
total_weight = float(self.total_weight_entry.get())
|
|
return exercises, sets, reps, weights, total_weight
|
|
|
|
def _check_total_weight(
|
|
self,
|
|
sets: list[int],
|
|
reps: list[list[int]],
|
|
weights: list[float],
|
|
total_weight: float,
|
|
) -> str | None:
|
|
"""Verify total weight matches individual exercise calculations."""
|
|
expected = self._calculate_expected_total(sets, reps, weights)
|
|
tolerance = expected * 0.15 # 15% tolerance
|
|
if abs(total_weight - expected) > tolerance:
|
|
return (
|
|
f"Total weight doesn't match! "
|
|
f"Expected ~{expected:.1f} kg, got {total_weight:.1f}"
|
|
)
|
|
return None
|
|
|
|
def _store_strength_data(
|
|
self,
|
|
exercises: list[str],
|
|
sets: list[int],
|
|
reps: list[list[int]],
|
|
weights: list[float],
|
|
total_weight: float,
|
|
) -> None:
|
|
"""Store validated strength workout data."""
|
|
self.workout_data["exercises"] = exercises
|
|
self.workout_data["sets"] = [str(s) for s in sets]
|
|
self.workout_data["reps"] = [
|
|
"+".join(str(r) for r in rep_list) for rep_list in reps
|
|
]
|
|
self.workout_data["weights_kg"] = [str(w) for w in weights]
|
|
self.workout_data["total_weight_kg"] = str(total_weight)
|
|
|
|
def verify_strength_data(self) -> None:
|
|
"""Validate strength workout data and unlock if valid."""
|
|
try:
|
|
self._verify_strength_data_inner()
|
|
except ValueError:
|
|
self.show_error("Please enter valid data in correct format")
|
|
|
|
def _verify_strength_data_inner(self) -> None:
|
|
"""Parse, validate, and store strength data."""
|
|
data = self._parse_strength_entries()
|
|
exercises, sets, reps, weights, total_weight = data
|
|
error = self._validate_strength_inputs(exercises, sets, reps, weights)
|
|
if error:
|
|
self.show_error(error)
|
|
return
|
|
total_err = self._check_total_weight(sets, reps, weights, total_weight)
|
|
if total_err:
|
|
self.show_error(total_err)
|
|
return
|
|
self._store_strength_data(exercises, sets, reps, weights, total_weight)
|
|
self._attempt_unlock()
|