feat(train): a workout out as GPX, TCX or FIT (ADR 0030, D49)

11b: Export on a workout's page hands it to the share sheet as GPX (with
a track only; heart rate and cadence in Garmin's TrackPointExtension),
TCX (laps, heart rate, cadence, watts, a row's distance) or FIT (file
ID, records, timer events at the pauses, laps with their intensities,
session, activity), all from the same points: the precise fixes while
moving, else the rower's frames, else the heart rates; a plan's steps
are the laps. The FIT writer is our own (protocol 1.0, CRC-16/ARC),
tested by a reader written from the specification.

Not tried yet with Strava, intervals.icu or Garmin Connect.

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-29 14:37:48 +01:00
commit 128ba1f6c8
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -2653,6 +2653,46 @@ void main() {
expect(find.text('Pace'), findsNothing);
});
testWidgets('a run goes out as GPX, TCX or FIT, a row as TCX or FIT '
'alone (D49)', (tester) async {
await saveRun(DateTime(2026, 10, 27, 7, 30), strap: true);
await saveRow();
final files = FakeRouteFiles();
await start(
tester,
usedBefore: true,
workouts: workouts,
routeFiles: files,
);
await tester.tap(find.text('Morning run'));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Export'));
await tester.pumpAndSettle();
expect(find.text('GPX'), findsOneWidget);
expect(find.text('TCX'), findsOneWidget);
await tester.tap(find.text('FIT'));
await tester.runAsync(pumpEventQueue);
await tester.pumpAndSettle();
final fit = files.shared.single;
expect(fit.name, endsWith('.fit'));
expect(String.fromCharCodes(fit.bytes, 8, 12), '.FIT');
expect(files.sharedTypes.single, 'application/vnd.ant.fit');
await tester.pageBack();
await tester.pumpAndSettle();
await tester.tap(find.text('Evening row'));
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Export'));
await tester.pumpAndSettle();
expect(find.text('GPX'), findsNothing);
await tester.tap(find.text('TCX'));
await tester.runAsync(pumpEventQueue);
await tester.pumpAndSettle();
expect(utf8.decode(files.shared.last.bytes), contains('Sport="Other"'));
});
testWidgets("where the instance can't be asked for its map source, the "
'page says so and shows the rest', (tester) async {
await saveRun(DateTime(2026, 10, 27, 7, 30));