feat(train): strength plans and sessions (ADR 0035)

Strength plans of exercises from a catalogue or the person's own, in
blocks of one exercise or several done in turn (supersets, circuits),
each with its sets, reps or seconds, weights and rests; each a sealed
`strength_plan` row. Plans are marked with what the place chosen lacks,
and a session offers swaps moving the same way that the place allows.

A session runs on every device, set by set with the rest counted down,
each weight made up from the place's plates, bars and fixed weights with
the plate changes between sets, kept in strength_session.json after each
change so one cut off is carried on, and saved as a workout of the new
activity Strength with its sets, listed on its page; strength training
in Health Connect and FIT, no GPX.

Migration 20261102113104496-strength-plans is purely additive: it
creates the table strength_plan (id uuid v7, spaceId, keyId, the sealed
payload and its nonce), its index on spaceId, and a foreign key to
offline_sync_spaces with ON DELETE CASCADE, so deleting a person's sync
spaces deletes their plans; then the modules' version rows. The
gen_random_uuid_v7 function is created or replaced as in every
migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-02 13:18:12 +01:00
commit 508994be3a
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -184,6 +184,12 @@ abstract class AppLocalizations {
/// **'Cycling'**
String get activityCycling;
/// No description provided for @activityStrength.
///
/// In en, this message translates to:
/// **'Strength training'**
String get activityStrength;
/// No description provided for @activityIndoorRowing.
///
/// In en, this message translates to:
@ -421,7 +427,7 @@ abstract class AppLocalizations {
/// A workout's name where the person gave none: the time of day it started (morning 5–12, afternoon 12–17, evening 17–22, night) and its activity, as Strava names them.
///
/// In en, this message translates to:
/// **'{when, select, morning_running{Morning run} morning_walking{Morning walk} morning_hiking{Morning hike} morning_cycling{Morning ride} morning_indoorRowing{Morning row} afternoon_running{Afternoon run} afternoon_walking{Afternoon walk} afternoon_hiking{Afternoon hike} afternoon_cycling{Afternoon ride} afternoon_indoorRowing{Afternoon row} evening_running{Evening run} evening_walking{Evening walk} evening_hiking{Evening hike} evening_cycling{Evening ride} evening_indoorRowing{Evening row} night_running{Night run} night_walking{Night walk} night_hiking{Night hike} night_cycling{Night ride} night_indoorRowing{Night row} other{Workout}}'**
/// **'{when, select, morning_strength{Morning strength} afternoon_strength{Afternoon strength} evening_strength{Evening strength} night_strength{Night strength} morning_running{Morning run} morning_walking{Morning walk} morning_hiking{Morning hike} morning_cycling{Morning ride} morning_indoorRowing{Morning row} afternoon_running{Afternoon run} afternoon_walking{Afternoon walk} afternoon_hiking{Afternoon hike} afternoon_cycling{Afternoon ride} afternoon_indoorRowing{Afternoon row} evening_running{Evening run} evening_walking{Evening walk} evening_hiking{Evening hike} evening_cycling{Evening ride} evening_indoorRowing{Evening row} night_running{Night run} night_walking{Night walk} night_hiking{Night hike} night_cycling{Night ride} night_indoorRowing{Night row} other{Workout}}'**
String workoutName(String when);
/// No description provided for @workoutLocked.
@ -3825,6 +3831,432 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Set {number}'**
String plateSet(int number);
/// A catalogue exercise's name, by its id (ADR 0035, D1).
///
/// In en, this message translates to:
/// **'{id, select, backSquat{Back squat} frontSquat{Front squat} gobletSquat{Goblet squat} kettlebellGobletSquat{Kettlebell goblet squat} bodyweightSquat{Bodyweight squat} lunge{Lunge} bulgarianSplitSquat{Bulgarian split squat} bodyweightLunge{Bodyweight lunge} deadlift{Deadlift} romanianDeadlift{Romanian deadlift} trapBarDeadlift{Trap bar deadlift} dumbbellRomanianDeadlift{Dumbbell Romanian deadlift} kettlebellSwing{Kettlebell swing} hipThrust{Hip thrust} gluteBridge{Glute bridge} benchPress{Bench press} inclineBenchPress{Incline bench press} dumbbellBenchPress{Dumbbell bench press} pushUp{Push-up} dip{Dip} overheadPress{Overhead press} dumbbellShoulderPress{Dumbbell shoulder press} pikePushUp{Pike push-up} barbellRow{Barbell row} dumbbellRow{One-arm dumbbell row} cableRow{Seated cable row} invertedRow{Inverted row} bandPullApart{Band pull-apart} facePull{Face pull} pullUp{Pull-up} chinUp{Chin-up} latPulldown{Lat pulldown} bandPulldown{Band pulldown} barbellCurl{Barbell curl} ezBarCurl{EZ bar curl} dumbbellCurl{Dumbbell curl} hammerCurl{Hammer curl} tricepsPushdown{Triceps pushdown} skullCrusher{Skull crusher} overheadTricepsExtension{Overhead triceps extension} benchDip{Bench dip} lateralRaise{Lateral raise} cableLateralRaise{Cable lateral raise} calfRaise{Calf raise} plank{Plank} sidePlank{Side plank} hangingLegRaise{Hanging leg raise} crunch{Crunch} farmersCarry{Farmer\'s carry} other{Exercise}}'**
String exerciseName(String id);
/// What an exercise needs, as in "Missing here: a rack, dip bars".
///
/// In en, this message translates to:
/// **'{need, select, barbell{a barbell} ezBar{an EZ bar} trapBar{a trap bar} dumbbells{dumbbells} kettlebell{a kettlebell} bench{a bench} rack{a rack} pullUpBar{a pull-up bar} dipBars{dip bars} cableMachine{a cable machine} bands{bands} other{something}}'**
String exerciseNeed(String need);
/// No description provided for @exercisePattern.
///
/// In en, this message translates to:
/// **'{pattern, select, squat{Squats} lunge{Lunges} hinge{Hinges} horizontalPush{Pushes} verticalPush{Pushes overhead} horizontalPull{Rows} verticalPull{Pull-ups and pulldowns} curl{Curls} extension{Triceps} raise{Raises} calf{Calves} core{Core} carry{Carries} other{Other}}'**
String exercisePattern(String pattern);
/// No description provided for @exerciseLoad.
///
/// In en, this message translates to:
/// **'{load, select, barbell{Barbell with plates} ezBar{EZ bar with plates} trapBar{Trap bar with plates} dumbbellPair{A dumbbell in each hand} dumbbell{One dumbbell} kettlebell{Kettlebell} cable{Cable stack} band{Band} body{Body weight} other{Other}}'**
String exerciseLoad(String load);
/// No description provided for @activityStrengthCard.
///
/// In en, this message translates to:
/// **'Strength'**
String get activityStrengthCard;
/// No description provided for @strengthPlansHeading.
///
/// In en, this message translates to:
/// **'Strength plans'**
String get strengthPlansHeading;
/// No description provided for @strengthPlansNew.
///
/// In en, this message translates to:
/// **'New strength plan'**
String get strengthPlansNew;
/// No description provided for @strengthPlansEmpty.
///
/// In en, this message translates to:
/// **'Strength plans list exercises with their sets, repetitions, weights and rests, alone or as supersets and circuits.'**
String get strengthPlansEmpty;
/// No description provided for @strengthPlanLocked.
///
/// In en, this message translates to:
/// **'Can\'t be opened on this device'**
String get strengthPlanLocked;
/// No description provided for @strengthPlanDeleted.
///
/// In en, this message translates to:
/// **'Strength plan deleted'**
String get strengthPlanDeleted;
/// No description provided for @strengthStart.
///
/// In en, this message translates to:
/// **'Start'**
String get strengthStart;
/// No description provided for @strengthChoosePlan.
///
/// In en, this message translates to:
/// **'Which plan?'**
String get strengthChoosePlan;
/// No description provided for @strengthNoPlans.
///
/// In en, this message translates to:
/// **'Make a strength plan first: the list below has New strength plan.'**
String get strengthNoPlans;
/// No description provided for @strengthPlanTitle.
///
/// In en, this message translates to:
/// **'Strength plan'**
String get strengthPlanTitle;
/// No description provided for @strengthPlanName.
///
/// In en, this message translates to:
/// **'Name'**
String get strengthPlanName;
/// No description provided for @strengthPlanSave.
///
/// In en, this message translates to:
/// **'Save'**
String get strengthPlanSave;
/// No description provided for @strengthPlanDelete.
///
/// In en, this message translates to:
/// **'Delete'**
String get strengthPlanDelete;
/// No description provided for @strengthAddExercise.
///
/// In en, this message translates to:
/// **'Add exercise'**
String get strengthAddExercise;
/// No description provided for @strengthAddToBlock.
///
/// In en, this message translates to:
/// **'Add an exercise to do in turn'**
String get strengthAddToBlock;
/// No description provided for @strengthOwnExercise.
///
/// In en, this message translates to:
/// **'Your own exercise'**
String get strengthOwnExercise;
/// No description provided for @strengthOwnExercises.
///
/// In en, this message translates to:
/// **'Your own'**
String get strengthOwnExercises;
/// No description provided for @strengthExerciseName.
///
/// In en, this message translates to:
/// **'What it is'**
String get strengthExerciseName;
/// No description provided for @strengthExerciseNeeds.
///
/// In en, this message translates to:
/// **'What it needs'**
String get strengthExerciseNeeds;
/// No description provided for @strengthExerciseLoad.
///
/// In en, this message translates to:
/// **'What it is done with'**
String get strengthExerciseLoad;
/// No description provided for @strengthExercisePattern.
///
/// In en, this message translates to:
/// **'How it moves'**
String get strengthExercisePattern;
/// No description provided for @strengthExerciseTimed.
///
/// In en, this message translates to:
/// **'Held for a time'**
String get strengthExerciseTimed;
/// No description provided for @strengthExerciseDone.
///
/// In en, this message translates to:
/// **'Done'**
String get strengthExerciseDone;
/// No description provided for @strengthAddSet.
///
/// In en, this message translates to:
/// **'Add a set'**
String get strengthAddSet;
/// No description provided for @strengthReps.
///
/// In en, this message translates to:
/// **'Reps'**
String get strengthReps;
/// No description provided for @strengthSeconds.
///
/// In en, this message translates to:
/// **'Seconds'**
String get strengthSeconds;
/// No description provided for @strengthKg.
///
/// In en, this message translates to:
/// **'kg'**
String get strengthKg;
/// No description provided for @strengthRemoveExercise.
///
/// In en, this message translates to:
/// **'Remove exercise'**
String get strengthRemoveExercise;
/// No description provided for @strengthRemoveSet.
///
/// In en, this message translates to:
/// **'Remove set'**
String get strengthRemoveSet;
/// No description provided for @strengthMoveUp.
///
/// In en, this message translates to:
/// **'Move up'**
String get strengthMoveUp;
/// No description provided for @strengthMoveDown.
///
/// In en, this message translates to:
/// **'Move down'**
String get strengthMoveDown;
/// No description provided for @strengthRest.
///
/// In en, this message translates to:
/// **'Rest'**
String get strengthRest;
/// No description provided for @strengthRestBetween.
///
/// In en, this message translates to:
/// **'Rest between exercises'**
String get strengthRestBetween;
/// No description provided for @strengthDiscardTitle.
///
/// In en, this message translates to:
/// **'Discard changes?'**
String get strengthDiscardTitle;
/// No description provided for @strengthDiscard.
///
/// In en, this message translates to:
/// **'Discard'**
String get strengthDiscard;
/// No description provided for @strengthDiscardMessage.
///
/// In en, this message translates to:
/// **'What you changed in this plan is not kept.'**
String get strengthDiscardMessage;
/// No description provided for @strengthAbandon.
///
/// In en, this message translates to:
/// **'End without saving'**
String get strengthAbandon;
/// No description provided for @strengthAbandonMessage.
///
/// In en, this message translates to:
/// **'The sets done so far are deleted from this device. This can\'t be undone.'**
String get strengthAbandonMessage;
/// No description provided for @strengthFinish.
///
/// In en, this message translates to:
/// **'Finish and save'**
String get strengthFinish;
/// No description provided for @strengthAllDone.
///
/// In en, this message translates to:
/// **'Every set is through.'**
String get strengthAllDone;
/// No description provided for @strengthSetDone.
///
/// In en, this message translates to:
/// **'Done'**
String get strengthSetDone;
/// No description provided for @strengthSkip.
///
/// In en, this message translates to:
/// **'Skip set'**
String get strengthSkip;
/// No description provided for @strengthSwap.
///
/// In en, this message translates to:
/// **'Swap exercise'**
String get strengthSwap;
/// No description provided for @strengthSwapTitle.
///
/// In en, this message translates to:
/// **'Do instead'**
String get strengthSwapTitle;
/// No description provided for @strengthNoSwaps.
///
/// In en, this message translates to:
/// **'Nothing else here moves this way.'**
String get strengthNoSwaps;
/// No description provided for @strengthRestSkip.
///
/// In en, this message translates to:
/// **'Skip rest'**
String get strengthRestSkip;
/// No description provided for @strengthResting.
///
/// In en, this message translates to:
/// **'Rest'**
String get strengthResting;
/// No description provided for @strengthCarryOn.
///
/// In en, this message translates to:
/// **'A session is under way'**
String get strengthCarryOn;
/// No description provided for @strengthCarryOnAction.
///
/// In en, this message translates to:
/// **'Carry on'**
String get strengthCarryOnAction;
/// No description provided for @strengthWeightNotMadeUp.
///
/// In en, this message translates to:
/// **'Nothing here makes this weight up.'**
String get strengthWeightNotMadeUp;
/// No description provided for @strengthFixed.
///
/// In en, this message translates to:
/// **'{weight} kg, nearest at or below'**
String strengthFixed(Object weight);
/// No description provided for @strengthNoPlace.
///
/// In en, this message translates to:
/// **'No place chosen: weights as planned.'**
String get strengthNoPlace;
/// No description provided for @strengthTrainsAt.
///
/// In en, this message translates to:
/// **'At {place}'**
String strengthTrainsAt(Object place);
/// No description provided for @strengthWorkoutVolume.
///
/// In en, this message translates to:
/// **'{weight} kg moved'**
String strengthWorkoutVolume(Object weight);
/// No description provided for @strengthWorkoutSets.
///
/// In en, this message translates to:
/// **'{count, plural, =1{1 set} other{{count} sets}}'**
String strengthWorkoutSets(num count);
/// No description provided for @strengthMissing.
///
/// In en, this message translates to:
/// **'Missing here: {needs}'**
String strengthMissing(String needs);
/// No description provided for @strengthPlanSummary.
///
/// In en, this message translates to:
/// **'{exercises, plural, =1{1 exercise} other{{exercises} exercises}} · {sets, plural, =1{1 set} other{{sets} sets}}'**
String strengthPlanSummary(int exercises, int sets);
/// No description provided for @strengthBlockKind.
///
/// In en, this message translates to:
/// **'{kind, select, superset{Superset} circuit{Circuit} other{Exercise}}'**
String strengthBlockKind(String kind);
/// No description provided for @strengthSetOf.
///
/// In en, this message translates to:
/// **'Set {set} of {sets}'**
String strengthSetOf(int set, int sets);
/// No description provided for @strengthRoundOf.
///
/// In en, this message translates to:
/// **'Round {round} of {rounds}'**
String strengthRoundOf(int round, int rounds);
/// No description provided for @strengthTarget.
///
/// In en, this message translates to:
/// **'{reps} × {weight} kg'**
String strengthTarget(int reps, String weight);
/// No description provided for @strengthTargetReps.
///
/// In en, this message translates to:
/// **'{reps, plural, =1{1 rep} other{{reps} reps}}'**
String strengthTargetReps(int reps);
/// No description provided for @strengthTargetSeconds.
///
/// In en, this message translates to:
/// **'{seconds} s'**
String strengthTargetSeconds(int seconds);
/// No description provided for @strengthNextUp.
///
/// In en, this message translates to:
/// **'Next: {exercise}'**
String strengthNextUp(String exercise);
/// No description provided for @strengthBarAndPlates.
///
/// In en, this message translates to:
/// **'{bar} kg bar, each side: {plates}'**
String strengthBarAndPlates(String bar, String plates);
/// No description provided for @strengthHandlesAndPlates.
///
/// In en, this message translates to:
/// **'{bar} kg handles, each side: {plates}'**
String strengthHandlesAndPlates(String bar, String plates);
/// No description provided for @strengthWeightShort.
///
/// In en, this message translates to:
/// **'{weight} kg below what was planned'**
String strengthWeightShort(String weight);
}
class _AppLocalizationsDelegate