screen_locker: enhance workout logging and UI

- Log full workout data (exercises, sets, reps, weights) instead of just type
- Add workout_log.json to gitignore
- Support variable reps per set using + separator (e.g., 12+12+11+11+12)
- Widen exercise name input field from 30 to 50 characters
This commit is contained in:
Krzysztof kuhy Rudnicki 2025-12-02 23:22:13 +01:00
parent cabf29f111
commit 3cda5c7582

View File

@ -331,7 +331,10 @@ class ScreenLocker:
)
return
# Data looks good
# Data looks good - store full data
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.unlock_screen()
except ValueError:
@ -361,7 +364,7 @@ class ScreenLocker:
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.exercises_entry = tk.Entry(ex_frame, font=("Arial", 18), width=30)
self.exercises_entry = tk.Entry(ex_frame, font=("Arial", 18), width=50)
self.exercises_entry.pack(side="left", padx=10)
# Sets per exercise
@ -377,17 +380,17 @@ class ScreenLocker:
self.sets_entry = tk.Entry(sets_frame, font=("Arial", 18), width=20)
self.sets_entry.pack(side="left", padx=10)
# Reps per set
# Reps per set (can be variable, e.g., "12+12+11+11+12" for one exercise)
reps_frame = tk.Frame(self.container, bg="#1a1a1a")
reps_frame.pack(pady=10)
tk.Label(
reps_frame,
text="Reps per set (comma-separated):",
text="Reps (comma-sep, use + for variable: 12+11+12):",
font=("Arial", 18),
fg="white",
bg="#1a1a1a",
).pack(side="left", padx=10)
self.reps_entry = tk.Entry(reps_frame, font=("Arial", 18), width=20)
self.reps_entry = tk.Entry(reps_frame, font=("Arial", 18), width=30)
self.reps_entry.pack(side="left", padx=10)
# Weights
@ -459,44 +462,76 @@ class ScreenLocker:
self.submit_command = self.verify_strength_data
self.update_submit_timer()
def _parse_reps(self, reps_raw: list[str]) -> list[list[int]]:
"""Parse reps input - can be 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 valid."""
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]}': variable reps count ({len(rep_list)}) "
f"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 verify_strength_data(self) -> None:
"""Validate strength workout data and unlock if valid."""
try:
exercises = [e.strip() for e in self.exercises_entry.get().split(",")]
sets = [int(s.strip()) for s in self.sets_entry.get().split(",")]
reps = [int(r.strip()) for r in self.reps_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())
# Check all lists have same length
if not (len(exercises) == len(sets) == len(reps) == len(weights)):
self.show_error(
"Number of exercises, sets, reps, and weights must match"
)
error = self._validate_strength_inputs(exercises, sets, reps, weights)
if error:
self.show_error(error)
return
# Check for empty or lazy entries
if any(len(ex) < MIN_EXERCISE_NAME_LEN for ex in exercises):
self.show_error("Exercise names too short - be specific")
return
# Sanity checks
if any(s < 1 or s > MAX_SETS for s in sets):
self.show_error(f"Sets should be between 1-{MAX_SETS}")
return
if any(r < 1 or r > MAX_REPS for r in reps):
self.show_error(f"Reps should be between 1-{MAX_REPS}")
return
if any(w < 0 or w > MAX_WEIGHT_KG for w in weights):
self.show_error(f"Weights should be between 0-{MAX_WEIGHT_KG} kg")
return
# Calculate expected total weight
expected_total = sum(
sets[i] * reps[i] * weights[i] for i in range(len(exercises))
)
expected_total = self._calculate_expected_total(sets, reps, weights)
weight_diff = abs(total_weight - expected_total)
tolerance = expected_total * 0.15 # 15% tolerance
@ -507,7 +542,14 @@ class ScreenLocker:
)
return
# Data looks good
# Data looks good - store full 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)
self.unlock_screen()
except ValueError: