feat(train): a recording runs an interval plan, with cues and Lap (ADR 0030, D42)

The plan chosen on the Record sheet goes to the recording service spelled
out: each step's end, its target (zones as the heart rates they span for
the profile's highest rate) and its spoken line in the app's language.
PlanRunner.kt moves through the steps on the moving time and distance,
counts a timed step's last seconds down and cues off target from a 30 s
window; PlanCues.kt gives a tone, a vibration and the voice, ducking
music. The file's plan and step lines bring it back, and the recording
screen shows the step, what is left of it, its target, the next step and
Lap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-28 22:59:48 +01:00
commit d9f98449a8
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
5 changed files with 1886 additions and 26 deletions

View file

@ -27,6 +27,13 @@
"recordAutoPause": "Von selbst pausieren",
"recordAutoPauseHint": "Wenn du stehst, und weiter, sobald du dich wieder bewegst",
"recordStart": "Start",
"recordPlan": "Plan",
"recordNoPlan": "Keiner",
"recordingLap": "Runde",
"recordingPlanDone": "Plan geschafft",
"recordingStepOf": "Schritt {index} von {count}",
"recordingNextStep": "Danach: {step}",
"recordingStepTapLap": "Bis du auf Runde tippst",
"recordLocationRefused": "Train braucht deinen Standort, um das aufzuzeichnen. Du kannst ihn in den Einstellungen des Systems erlauben.",
"recordingWaitingForGps": "Warte auf GPS",
"recordingWaitingForGpsAccuracy": "Warte auf GPS · ±{metres} m",
@ -273,6 +280,22 @@
"planDeleteMessage": "Er wird auf all deinen Geräten gelöscht; Trainings, die mit ihm aufgezeichnet wurden, behalten ihre Schritte.",
"planDeleted": "Plan gelöscht",
"planSteps": "{count, plural, =1{1 Schritt} other{{count} Schritte}}",
"planSayRound": "{round} von {rounds}",
"planSayMinutes": "{count, plural, =1{1 Minute} other{{count} Minuten}}",
"planSaySeconds": "{count, plural, =1{1 Sekunde} other{{count} Sekunden}}",
"planSayMetres": "{count, plural, =1{1 Meter} other{{count} Meter}}",
"planSayKilometres": "{km} Kilometer",
"planSayUntilLap": "bis du auf Runde tippst",
"planSayPace": "{from} bis {to} pro Kilometer",
"planSaySpeed": "{from} bis {to} Kilometer pro Stunde",
"planSayHeartRate": "{from} bis {to} Schläge pro Minute",
"planSayZone": "Zone {zone}",
"planSaySplit": "{from} bis {to} pro 500 Meter",
"planSayWatts": "{from} bis {to} Watt",
"planSayStrokeRate": "{from} bis {to} Ruderschläge pro Minute",
"planSayHarder": "Etwas mehr",
"planSayEasier": "Etwas ruhiger",
"planSayDone": "Plan geschafft",
"stepTitle": "Schritt",
"stepKind": "Art",
"stepWarmUp": "Aufwärmen",

View file

@ -30,6 +30,15 @@
"@recordAutoPause": {"description": "The switch for auto-pause, as the recording starts."},
"recordAutoPauseHint": "When you stand still, and going on as you move again",
"recordStart": "Start",
"recordPlan": "Plan",
"recordNoPlan": "None",
"recordingLap": "Lap",
"recordingPlanDone": "Plan done",
"recordingStepOf": "Step {index} of {count}",
"@recordingStepOf": {"placeholders": {"index": {"type": "int"}, "count": {"type": "int"}}},
"recordingNextStep": "Next: {step}",
"@recordingNextStep": {"placeholders": {"step": {"type": "String"}}},
"recordingStepTapLap": "Until you tap Lap",
"recordLocationRefused": "Train needs your location to record this. You can allow it in the system's settings.",
"recordingWaitingForGps": "Waiting for GPS",
"recordingWaitingForGpsAccuracy": "Waiting for GPS · ±{metres} m",
@ -334,6 +343,34 @@
"planDeleted": "Plan deleted",
"planSteps": "{count, plural, =1{1 step} other{{count} steps}}",
"@planSteps": {"placeholders": {"count": {"type": "int"}}},
"planSayRound": "{round} of {rounds}",
"@planSayRound": {"placeholders": {"round": {"type": "int"}, "rounds": {"type": "int"}}},
"planSayMinutes": "{count, plural, =1{1 minute} other{{count} minutes}}",
"@planSayMinutes": {"placeholders": {"count": {"type": "int"}}},
"planSaySeconds": "{count, plural, =1{1 second} other{{count} seconds}}",
"@planSaySeconds": {"placeholders": {"count": {"type": "int"}}},
"planSayMetres": "{count, plural, =1{1 metre} other{{count} metres}}",
"@planSayMetres": {"placeholders": {"count": {"type": "int"}}},
"planSayKilometres": "{km} kilometres",
"@planSayKilometres": {"placeholders": {"km": {"type": "String"}}},
"planSayUntilLap": "until you tap Lap",
"planSayPace": "{from} to {to} a kilometre",
"@planSayPace": {"placeholders": {"from": {"type": "String"}, "to": {"type": "String"}}},
"planSaySpeed": "{from} to {to} kilometres an hour",
"@planSaySpeed": {"placeholders": {"from": {"type": "String"}, "to": {"type": "String"}}},
"planSayHeartRate": "{from} to {to} beats a minute",
"@planSayHeartRate": {"placeholders": {"from": {"type": "String"}, "to": {"type": "String"}}},
"planSayZone": "zone {zone}",
"@planSayZone": {"placeholders": {"zone": {"type": "int"}}},
"planSaySplit": "{from} to {to} per 500 metres",
"@planSaySplit": {"placeholders": {"from": {"type": "String"}, "to": {"type": "String"}}},
"planSayWatts": "{from} to {to} watts",
"@planSayWatts": {"placeholders": {"from": {"type": "String"}, "to": {"type": "String"}}},
"planSayStrokeRate": "{from} to {to} strokes a minute",
"@planSayStrokeRate": {"placeholders": {"from": {"type": "String"}, "to": {"type": "String"}}},
"planSayHarder": "Push a little",
"planSayEasier": "Ease off a little",
"planSayDone": "Plan done",
"stepTitle": "Step",
"stepKind": "Kind",
"stepWarmUp": "Warm-up",

View file

@ -226,6 +226,48 @@ abstract class AppLocalizations {
/// **'Start'**
String get recordStart;
/// No description provided for @recordPlan.
///
/// In en, this message translates to:
/// **'Plan'**
String get recordPlan;
/// No description provided for @recordNoPlan.
///
/// In en, this message translates to:
/// **'None'**
String get recordNoPlan;
/// No description provided for @recordingLap.
///
/// In en, this message translates to:
/// **'Lap'**
String get recordingLap;
/// No description provided for @recordingPlanDone.
///
/// In en, this message translates to:
/// **'Plan done'**
String get recordingPlanDone;
/// No description provided for @recordingStepOf.
///
/// In en, this message translates to:
/// **'Step {index} of {count}'**
String recordingStepOf(int index, int count);
/// No description provided for @recordingNextStep.
///
/// In en, this message translates to:
/// **'Next: {step}'**
String recordingNextStep(String step);
/// No description provided for @recordingStepTapLap.
///
/// In en, this message translates to:
/// **'Until you tap Lap'**
String get recordingStepTapLap;
/// No description provided for @recordLocationRefused.
///
/// In en, this message translates to:
@ -1660,6 +1702,102 @@ abstract class AppLocalizations {
/// **'{count, plural, =1{1 step} other{{count} steps}}'**
String planSteps(int count);
/// No description provided for @planSayRound.
///
/// In en, this message translates to:
/// **'{round} of {rounds}'**
String planSayRound(int round, int rounds);
/// No description provided for @planSayMinutes.
///
/// In en, this message translates to:
/// **'{count, plural, =1{1 minute} other{{count} minutes}}'**
String planSayMinutes(int count);
/// No description provided for @planSaySeconds.
///
/// In en, this message translates to:
/// **'{count, plural, =1{1 second} other{{count} seconds}}'**
String planSaySeconds(int count);
/// No description provided for @planSayMetres.
///
/// In en, this message translates to:
/// **'{count, plural, =1{1 metre} other{{count} metres}}'**
String planSayMetres(int count);
/// No description provided for @planSayKilometres.
///
/// In en, this message translates to:
/// **'{km} kilometres'**
String planSayKilometres(String km);
/// No description provided for @planSayUntilLap.
///
/// In en, this message translates to:
/// **'until you tap Lap'**
String get planSayUntilLap;
/// No description provided for @planSayPace.
///
/// In en, this message translates to:
/// **'{from} to {to} a kilometre'**
String planSayPace(String from, String to);
/// No description provided for @planSaySpeed.
///
/// In en, this message translates to:
/// **'{from} to {to} kilometres an hour'**
String planSaySpeed(String from, String to);
/// No description provided for @planSayHeartRate.
///
/// In en, this message translates to:
/// **'{from} to {to} beats a minute'**
String planSayHeartRate(String from, String to);
/// No description provided for @planSayZone.
///
/// In en, this message translates to:
/// **'zone {zone}'**
String planSayZone(int zone);
/// No description provided for @planSaySplit.
///
/// In en, this message translates to:
/// **'{from} to {to} per 500 metres'**
String planSaySplit(String from, String to);
/// No description provided for @planSayWatts.
///
/// In en, this message translates to:
/// **'{from} to {to} watts'**
String planSayWatts(String from, String to);
/// No description provided for @planSayStrokeRate.
///
/// In en, this message translates to:
/// **'{from} to {to} strokes a minute'**
String planSayStrokeRate(String from, String to);
/// No description provided for @planSayHarder.
///
/// In en, this message translates to:
/// **'Push a little'**
String get planSayHarder;
/// No description provided for @planSayEasier.
///
/// In en, this message translates to:
/// **'Ease off a little'**
String get planSayEasier;
/// No description provided for @planSayDone.
///
/// In en, this message translates to:
/// **'Plan done'**
String get planSayDone;
/// No description provided for @stepTitle.
///
/// In en, this message translates to:

View file

@ -76,6 +76,31 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get recordStart => 'Start';
@override
String get recordPlan => 'Plan';
@override
String get recordNoPlan => 'Keiner';
@override
String get recordingLap => 'Runde';
@override
String get recordingPlanDone => 'Plan geschafft';
@override
String recordingStepOf(int index, int count) {
return 'Schritt $index von $count';
}
@override
String recordingNextStep(String step) {
return 'Danach: $step';
}
@override
String get recordingStepTapLap => 'Bis du auf Runde tippst';
@override
String get recordLocationRefused =>
'Train braucht deinen Standort, um das aufzuzeichnen. Du kannst ihn in den Einstellungen des Systems erlauben.';
@ -943,6 +968,96 @@ class AppLocalizationsDe extends AppLocalizations {
return '$_temp0';
}
@override
String planSayRound(int round, int rounds) {
return '$round von $rounds';
}
@override
String planSayMinutes(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count Minuten',
one: '1 Minute',
);
return '$_temp0';
}
@override
String planSaySeconds(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count Sekunden',
one: '1 Sekunde',
);
return '$_temp0';
}
@override
String planSayMetres(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count Meter',
one: '1 Meter',
);
return '$_temp0';
}
@override
String planSayKilometres(String km) {
return '$km Kilometer';
}
@override
String get planSayUntilLap => 'bis du auf Runde tippst';
@override
String planSayPace(String from, String to) {
return '$from bis $to pro Kilometer';
}
@override
String planSaySpeed(String from, String to) {
return '$from bis $to Kilometer pro Stunde';
}
@override
String planSayHeartRate(String from, String to) {
return '$from bis $to Schläge pro Minute';
}
@override
String planSayZone(int zone) {
return 'Zone $zone';
}
@override
String planSaySplit(String from, String to) {
return '$from bis $to pro 500 Meter';
}
@override
String planSayWatts(String from, String to) {
return '$from bis $to Watt';
}
@override
String planSayStrokeRate(String from, String to) {
return '$from bis $to Ruderschläge pro Minute';
}
@override
String get planSayHarder => 'Etwas mehr';
@override
String get planSayEasier => 'Etwas ruhiger';
@override
String get planSayDone => 'Plan geschafft';
@override
String get stepTitle => 'Schritt';

View file

@ -75,6 +75,31 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get recordStart => 'Start';
@override
String get recordPlan => 'Plan';
@override
String get recordNoPlan => 'None';
@override
String get recordingLap => 'Lap';
@override
String get recordingPlanDone => 'Plan done';
@override
String recordingStepOf(int index, int count) {
return 'Step $index of $count';
}
@override
String recordingNextStep(String step) {
return 'Next: $step';
}
@override
String get recordingStepTapLap => 'Until you tap Lap';
@override
String get recordLocationRefused =>
'Train needs your location to record this. You can allow it in the system\'s settings.';
@ -936,6 +961,96 @@ class AppLocalizationsEn extends AppLocalizations {
return '$_temp0';
}
@override
String planSayRound(int round, int rounds) {
return '$round of $rounds';
}
@override
String planSayMinutes(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count minutes',
one: '1 minute',
);
return '$_temp0';
}
@override
String planSaySeconds(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count seconds',
one: '1 second',
);
return '$_temp0';
}
@override
String planSayMetres(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count metres',
one: '1 metre',
);
return '$_temp0';
}
@override
String planSayKilometres(String km) {
return '$km kilometres';
}
@override
String get planSayUntilLap => 'until you tap Lap';
@override
String planSayPace(String from, String to) {
return '$from to $to a kilometre';
}
@override
String planSaySpeed(String from, String to) {
return '$from to $to kilometres an hour';
}
@override
String planSayHeartRate(String from, String to) {
return '$from to $to beats a minute';
}
@override
String planSayZone(int zone) {
return 'zone $zone';
}
@override
String planSaySplit(String from, String to) {
return '$from to $to per 500 metres';
}
@override
String planSayWatts(String from, String to) {
return '$from to $to watts';
}
@override
String planSayStrokeRate(String from, String to) {
return '$from to $to strokes a minute';
}
@override
String get planSayHarder => 'Push a little';
@override
String get planSayEasier => 'Ease off a little';
@override
String get planSayDone => 'Plan done';
@override
String get stepTitle => 'Step';