import '../../internal/application/CommitmentClass.dart'; import '../../internal/application/RoutineKind.dart'; import '../../internal/application/StepMode.dart'; import '../../../utils/ObjectConvertors.dart'; import 'RoutineStep.dart'; /// An ordered sequence where the chain, not the step, is the unit of /// completion. Breaking mid-way logs partial. class RoutineChain { String? id; String title; /// The goal this routine serves — "Workout" holds "Full body strength". /// Null for a routine that stands on its own. String? goalId; RoutineKind kind; /// Weight class, exactly as for a commitment — a non-negotiable morning /// routine carries non-negotiable debt when it breaks. CommitmentClass commitmentClass; /// 1..7, Monday first. Empty means unscheduled — run on demand only. List days; /// Minutes after midnight the routine is due to start; -1 when unset. int startMinute; String note; List steps; /// Index of the step reached when the chain last broke; -1 when clean. int lastBreakIndex; RoutineChain({ this.id, this.title = "", this.goalId, this.kind = RoutineKind.Workout, this.commitmentClass = CommitmentClass.Standard, List? days, this.startMinute = -1, this.note = "", List? steps, this.lastBreakIndex = -1, }) : days = days ?? [], steps = steps ?? []; factory RoutineChain.fromJson(Map json) { return RoutineChain( id: json['id'], title: json['title'] ?? "", goalId: json['goalId'], kind: getRoutineKind(json['kind']), commitmentClass: getCommitmentClass(json['commitmentClass']), days: json['days'] == null ? [] : (json['days'] as List).map((item) => item as int).toList(), startMinute: json['startMinute'] ?? -1, note: json['note'] ?? "", steps: getRoutineStepList(json['steps']), lastBreakIndex: json['lastBreakIndex'] ?? -1, ); } Map toJson() { final Map data = {}; data['id'] = id; data['title'] = title; data['goalId'] = goalId; data['kind'] = kind.name; data['commitmentClass'] = commitmentClass.name; data['days'] = days; data['startMinute'] = startMinute; data['note'] = note; data['steps'] = steps.map((item) => item.toJson()).toList(); data['lastBreakIndex'] = lastBreakIndex; return data; } int get completedSteps { return steps.where((step) => step.completed).length; } /// 0..1 — a partially run chain is recorded as partial, not as done. double get partialCompletion { if (steps.isEmpty) { return 0; } return completedSteps / steps.length; } bool get complete { return steps.isNotEmpty && completedSteps == steps.length; } bool get scheduled { return days.isNotEmpty; } bool scheduledOn(DateTime day) { return days.contains(day.weekday); } /// Every set of every step, for the "12 sets" figure. int get totalSets { int total = 0; for (RoutineStep step in steps) { total = total + step.sets; } return total; } /// Planned seconds, rest included. Rep and tick steps count their estimate. int get totalSeconds { int total = 0; for (RoutineStep step in steps) { total = total + step.plannedSeconds; } return total; } int get timedSteps { return steps.where((step) => step.mode == StepMode.Timed).length; } /// A deep copy for the builder, so cancelling an edit leaves the library /// untouched. RoutineChain copy() { return RoutineChain.fromJson(toJson()); } }