feat(train): the heart-rate strap, recorded by the service itself (ADR 0030, D25-D27)

The Record sheet gains a Heart rate row: a sheet looks for straps
advertising the Heart Rate Service, the nearest first, once nearby
devices are allowed, and the one chosen is remembered in the person's
folder; it records with every workout unless switched off for one.
The recorder service connects to it with Android's own Bluetooth API,
no package (D26), parses Heart Rate Measurement in Kotlin (tested on
the JVM), writes each rate and the strap's state to the recording
file, and asks for a lost strap again for as long as it records. The
recording screen shows the heart rate, and "Strap lost" while it is
gone; a workout's page shows the heart's average and highest rate,
each split's average and its chart.

iOS's recording waits for a Mac (D25), after the rower.

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 15:50:36 +01:00
commit de5fd43e06
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -555,6 +555,108 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'This workout is gone.'**
String get workoutNotFound;
/// No description provided for @recordHeartRate.
///
/// In en, this message translates to:
/// **'Heart rate'**
String get recordHeartRate;
/// On the Record sheet, under Heart rate, where no strap is chosen.
///
/// In en, this message translates to:
/// **'No strap'**
String get recordNoStrap;
/// No description provided for @strapSheetTitle.
///
/// In en, this message translates to:
/// **'Heart-rate strap'**
String get strapSheetTitle;
/// No description provided for @strapSearching.
///
/// In en, this message translates to:
/// **'Looking for straps nearby…'**
String get strapSearching;
/// No description provided for @strapSearchingHint.
///
/// In en, this message translates to:
/// **'Put the strap on: most wake up only once they touch skin.'**
String get strapSearchingHint;
/// Under a strap found: its signal is strong.
///
/// In en, this message translates to:
/// **'close by'**
String get strapNear;
/// Under a strap found: its signal is weak.
///
/// In en, this message translates to:
/// **'further off'**
String get strapFar;
/// A strap that sends no name.
///
/// In en, this message translates to:
/// **'Unnamed strap'**
String get strapUnnamed;
/// No description provided for @strapForget.
///
/// In en, this message translates to:
/// **'Forget strap'**
String get strapForget;
/// No description provided for @strapRefused.
///
/// In en, this message translates to:
/// **'Train needs nearby devices to find your strap. You can allow it in the system\'s settings.'**
String get strapRefused;
/// No description provided for @strapBluetoothOff.
///
/// In en, this message translates to:
/// **'Bluetooth is off. Turn it on to find your strap.'**
String get strapBluetoothOff;
/// Under the heart rate, lower case.
///
/// In en, this message translates to:
/// **'heart rate'**
String get recordingHeartRate;
/// While recording: the strap is out of reach or off; Train keeps asking for it.
///
/// In en, this message translates to:
/// **'Strap lost'**
String get recordingStrapLost;
/// Lower case, under a figure.
///
/// In en, this message translates to:
/// **'average heart rate'**
String get workoutAverageHeartRate;
/// Lower case, under a figure.
///
/// In en, this message translates to:
/// **'highest heart rate'**
String get workoutMaxHeartRate;
/// No description provided for @workoutBpmValue.
///
/// In en, this message translates to:
/// **'{bpm} bpm'**
String workoutBpmValue(int bpm);
/// No description provided for @workoutHeartRateChart.
///
/// In en, this message translates to:
/// **'Heart rate'**
String get workoutHeartRateChart;
}
class _AppLocalizationsDelegate