2026-06-28 08:11:43 +02:00
|
|
|
/// Core domain model for a single exercise and its current progression state.
|
2026-05-31 16:23:46 +02:00
|
|
|
library;
|
|
|
|
|
|
2026-06-28 08:11:43 +02:00
|
|
|
/// Default weight cap: above this, reps increase instead of weight.
|
2026-05-31 16:23:46 +02:00
|
|
|
const double kDefaultMaxWeight = 27.5;
|
2026-06-28 08:11:43 +02:00
|
|
|
|
|
|
|
|
/// Weight increment used for progression steps (kg).
|
2026-05-31 16:23:46 +02:00
|
|
|
const double kWeightIncrement = 2.5;
|
|
|
|
|
|
2026-06-28 08:11:43 +02:00
|
|
|
/// Immutable definition of a single exercise and its current target state.
|
2026-05-31 16:23:46 +02:00
|
|
|
class Exercise {
|
2026-06-28 08:11:43 +02:00
|
|
|
/// Creates an exercise with the given parameters.
|
2026-05-31 16:23:46 +02:00
|
|
|
const Exercise({
|
|
|
|
|
required this.name,
|
|
|
|
|
required this.sets,
|
|
|
|
|
required this.reps,
|
|
|
|
|
required this.weight,
|
|
|
|
|
this.maxWeight = kDefaultMaxWeight,
|
2026-06-28 08:11:43 +02:00
|
|
|
this.hasWarmup = true,
|
2026-05-31 16:23:46 +02:00
|
|
|
});
|
|
|
|
|
|
2026-06-28 08:11:43 +02:00
|
|
|
/// Deserializes an exercise from a JSON map.
|
|
|
|
|
factory Exercise.fromJson(Map<String, dynamic> json) => Exercise(
|
|
|
|
|
name: json['name'] as String,
|
|
|
|
|
sets: json['sets'] as int,
|
|
|
|
|
reps: json['reps'] as int,
|
|
|
|
|
weight: (json['weight'] as num).toDouble(),
|
|
|
|
|
maxWeight: (json['maxWeight'] as num?)?.toDouble() ?? kDefaultMaxWeight,
|
|
|
|
|
hasWarmup: json['hasWarmup'] as bool? ?? true,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/// Display name of the exercise.
|
2026-05-31 16:23:46 +02:00
|
|
|
final String name;
|
2026-06-28 08:11:43 +02:00
|
|
|
|
|
|
|
|
/// Number of working sets per session.
|
2026-05-31 16:23:46 +02:00
|
|
|
final int sets;
|
2026-06-28 08:11:43 +02:00
|
|
|
|
|
|
|
|
/// Target reps per set.
|
2026-05-31 16:23:46 +02:00
|
|
|
final int reps;
|
2026-06-28 08:11:43 +02:00
|
|
|
|
|
|
|
|
/// Current working weight in kg.
|
2026-05-31 16:23:46 +02:00
|
|
|
final double weight;
|
|
|
|
|
|
|
|
|
|
/// Weight cap beyond which reps increase instead of weight.
|
|
|
|
|
final double maxWeight;
|
|
|
|
|
|
2026-06-28 08:11:43 +02:00
|
|
|
/// Whether a warmup set should be shown for this exercise.
|
|
|
|
|
final bool hasWarmup;
|
|
|
|
|
|
2026-05-31 16:23:46 +02:00
|
|
|
/// Warmup weight: 4/5 of target weight, rounded DOWN to nearest 2.5 kg.
|
|
|
|
|
double get warmupWeight {
|
|
|
|
|
final raw = weight * 4.0 / 5.0;
|
|
|
|
|
return (raw / kWeightIncrement).floor() * kWeightIncrement;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 08:11:43 +02:00
|
|
|
/// Returns a copy of this exercise with the given fields replaced.
|
2026-05-31 16:23:46 +02:00
|
|
|
Exercise copyWith({
|
|
|
|
|
String? name,
|
|
|
|
|
int? sets,
|
|
|
|
|
int? reps,
|
|
|
|
|
double? weight,
|
|
|
|
|
double? maxWeight,
|
2026-06-28 08:11:43 +02:00
|
|
|
bool? hasWarmup,
|
2026-05-31 16:23:46 +02:00
|
|
|
}) {
|
|
|
|
|
return Exercise(
|
|
|
|
|
name: name ?? this.name,
|
|
|
|
|
sets: sets ?? this.sets,
|
|
|
|
|
reps: reps ?? this.reps,
|
|
|
|
|
weight: weight ?? this.weight,
|
|
|
|
|
maxWeight: maxWeight ?? this.maxWeight,
|
2026-06-28 08:11:43 +02:00
|
|
|
hasWarmup: hasWarmup ?? this.hasWarmup,
|
2026-05-31 16:23:46 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 08:11:43 +02:00
|
|
|
/// Serializes this exercise to a JSON map.
|
2026-05-31 16:23:46 +02:00
|
|
|
Map<String, dynamic> toJson() => {
|
2026-06-28 08:11:43 +02:00
|
|
|
'name': name,
|
|
|
|
|
'sets': sets,
|
|
|
|
|
'reps': reps,
|
|
|
|
|
'weight': weight,
|
|
|
|
|
'maxWeight': maxWeight,
|
|
|
|
|
'hasWarmup': hasWarmup,
|
|
|
|
|
};
|
2026-05-31 16:23:46 +02:00
|
|
|
}
|