Add routines module and full goal management

Routines: workout, meditation and must-do routines with timed, rep and
tick steps, sets and rest. A library screen, a builder with templates
and drag-to-reorder steps, and a full-screen player that auto-advances
timed phases, counts backgrounding and logs broken chains as partial.

Goals: a goal editor (deadline, colour, category, default class, archive),
routines linked to goals, and a goal field on tasks and routines. Tasks
added from inside a goal are now actually attached to it via parentId.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
alvocool
2026-11-02 08:45:41 +03:00
parent c88887b639
commit 1a5867a98f
49 changed files with 5384 additions and 220 deletions

View File

@@ -0,0 +1,75 @@
import '../../internal/application/StepMode.dart';
class RoutineStep {
String? id;
String title;
int order;
StepMode mode;
/// Work seconds per set for timed steps; the estimate per set otherwise.
int timerSeconds;
int sets;
/// Target per set for rep steps; 0 otherwise.
int reps;
/// Rest between sets, and after the last set before the next step.
int restSeconds;
/// One line shown under the clock — a form cue, a breathing pattern.
String cue;
bool completed;
RoutineStep({
this.id,
this.title = "",
this.order = 0,
this.mode = StepMode.Timed,
this.timerSeconds = 0,
this.sets = 1,
this.reps = 0,
this.restSeconds = 0,
this.cue = "",
this.completed = false,
});
factory RoutineStep.fromJson(Map<String, dynamic> json) {
return RoutineStep(
id: json['id'],
title: json['title'] ?? "",
order: json['order'] ?? 0,
mode: getStepMode(json['mode']),
timerSeconds: json['timerSeconds'] ?? 0,
sets: json['sets'] ?? 1,
reps: json['reps'] ?? 0,
restSeconds: json['restSeconds'] ?? 0,
cue: json['cue'] ?? "",
completed: json['completed'] ?? false,
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['title'] = title;
data['order'] = order;
data['mode'] = mode.name;
data['timerSeconds'] = timerSeconds;
data['sets'] = sets;
data['reps'] = reps;
data['restSeconds'] = restSeconds;
data['cue'] = cue;
data['completed'] = completed;
return data;
}
int get plannedSeconds {
final int safeSets = sets < 1 ? 1 : sets;
return timerSeconds * safeSets + restSeconds * safeSets;
}
}