screen-locker/stronglift_replacement/workout_app/lib/models/manual_deload.dart
Krzysztof kuhy Rudnicki 7e1b4e865e workout_app: manual deload on demand (workout tile + Settings)
"Deload now" in the exercise settings sheet runs exactly the step a fail
streak would take (targetAfterFailure), resets both streaks, and records a
progression_events row (schema v7, backed up and restored with the rest).
No manual step up: the target still only climbs through finished workouts.

Mid-workout it re-targets the exercise for the current session: untapped
sets move to the new reps, tapped sets keep theirs, and the whole exercise
is recorded at the new weight at Finish. From Settings it cancels pending
weight/reps debounce writes so they cannot undo the deload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KejiqD7y4NXRiPBKQeWHSy
2026-10-09 14:55:10 +02:00

81 lines
2.2 KiB
Dart

/// A deload the user asked for, as opposed to one a fail streak earned.
///
/// Only ever a step DOWN: there is deliberately no manual step up, so the
/// target can only climb through finished workouts. The step is
/// `targetAfterFailure`, the same rule a fail streak runs, so a manual deload
/// lands exactly where the automatic one would have.
library;
import 'package:workout_app/models/exercise_state.dart';
/// Where a manual deload was started from; stored in `progression_events`.
enum DeloadSource {
/// The settings sheet opened from an exercise tile mid-workout.
workout('workout'),
/// The settings sheet opened from the Settings screen.
settings('settings');
const DeloadSource(this.storageKey);
/// Value stored in SQLite. Never rename: stored rows hold it.
final String storageKey;
}
/// `progression_events.kind` of a manual deload.
const String kManualDeloadKind = 'manual_deload';
/// The outcome of `StorageService.manualDeload`.
class ManualDeloadResult {
/// Creates a result; [state] is null when nothing was changed.
const ManualDeloadResult({required this.state, required this.reason});
/// The exercise's state after the deload, or null when it did not apply.
final ExerciseState? state;
/// What happened, as a sentence a human can act on.
final String reason;
/// Whether the target actually went down.
bool get applied => state != null;
}
/// One row of `progression_events`: a target change and what caused it.
class ProgressionEvent {
/// Creates a [ProgressionEvent].
const ProgressionEvent({
required this.at,
required this.exercise,
required this.kind,
required this.source,
required this.fromWeight,
required this.fromReps,
required this.toWeight,
required this.toReps,
});
/// When it happened (local time).
final DateTime at;
/// Exercise name.
final String exercise;
/// What kind of change; [kManualDeloadKind] is the only one so far.
final String kind;
/// [DeloadSource.storageKey] of where it was started.
final String source;
/// Weight before, in kg.
final double fromWeight;
/// Reps before.
final int fromReps;
/// Weight after, in kg.
final double toWeight;
/// Reps after.
final int toReps;
}