feat(train): Profile opens on your body, the last 12 weeks and this year (ADR 0030, D60)
Above the days: a card with your body's weight and resting and maximal heart rate, tapped opening Your body; the last 12 weeks' time trained as bars, a week tapped showing its time, distance and workouts; and this calendar year by activity. Home's week card reads its time as the days do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
f04388225b
commit
5cfab59bd2
2 changed files with 713 additions and 46 deletions
|
|
@ -29,6 +29,7 @@ import 'package:halkyon_train_flutter/src/feature/plan/model/plan.dart';
|
|||
import 'package:halkyon_train_flutter/src/feature/plan/model/stored_plan.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/profile/data/profile_repository.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/profile/model/profile.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/profile/widget/body_card.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/profile/widget/profile_screen.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/data/announce_store.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/recording/data/device_store.dart';
|
||||
|
|
@ -44,6 +45,7 @@ import 'package:halkyon_train_flutter/src/feature/route/data/routing_gateway.dar
|
|||
import 'package:halkyon_train_flutter/src/feature/route/model/route.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/data/workout_repository.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/model/workout.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/widget/progress_cards.dart';
|
||||
import 'package:halkyon_train_flutter/src/feature/workout/widget/week_card.dart';
|
||||
import 'package:halkyon_train_flutter/src/tabs/maps_tab.dart';
|
||||
import 'package:halkyon_train_flutter/src/tabs/record_tab.dart';
|
||||
|
|
@ -661,6 +663,65 @@ void main() {
|
|||
expect(find.text('52'), findsOneWidget);
|
||||
expect(find.text('Female'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("Profile's card shows the figures given, opens Your body, and "
|
||||
'follows a change (D60)', (tester) async {
|
||||
await tester.runAsync(
|
||||
() => profile.save(
|
||||
const ProfileContent(
|
||||
weightKg: 72.5,
|
||||
restingHeartRate: 52,
|
||||
maxHeartRate: 188,
|
||||
),
|
||||
),
|
||||
);
|
||||
await start(tester, usedBefore: true, profile: profile);
|
||||
await goTab(tester, 'Profile');
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pump();
|
||||
|
||||
final card = find.byType(BodyCard);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: card,
|
||||
matching: find.text('72.5 kg · resting 52 bpm · max 188 bpm'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.runAsync(
|
||||
() => profile.save(const ProfileContent(restingHeartRate: 50)),
|
||||
);
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pump();
|
||||
expect(
|
||||
find.descendant(of: card, matching: find.text('resting 50 bpm')),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.tap(card);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('50'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("without figures, Profile's card says what they're for", (
|
||||
tester,
|
||||
) async {
|
||||
await start(tester, usedBefore: true, profile: profile);
|
||||
await goTab(tester, 'Profile');
|
||||
await tester.runAsync(pumpEventQueue);
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(BodyCard),
|
||||
matching: find.text(
|
||||
'What your band and your heart-rate zones work from',
|
||||
),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('the Polar Loop (ADR 0030, D34, D35)', () {
|
||||
|
|
@ -3143,6 +3204,52 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
testWidgets('Profile charts the last 12 weeks, this week chosen, a week '
|
||||
'tapped showing its own, and sums up the year by activity (D60)', (
|
||||
tester,
|
||||
) async {
|
||||
tester.platformDispatcher.localesTestValue = const [Locale('en', 'GB')];
|
||||
addTearDown(tester.platformDispatcher.clearLocalesTestValue);
|
||||
// Saturday, 31 October 2026.
|
||||
await withClock(Clock.fixed(DateTime(2026, 10, 31, 15)), () async {
|
||||
await saveRun(DateTime(2026, 10, 27, 7, 30));
|
||||
await saveRow();
|
||||
await saveRun(DateTime(2026, 9, 3, 18));
|
||||
await startWith(tester);
|
||||
await goTab(tester, 'Profile');
|
||||
|
||||
final weeks = find.byType(WeeksCard);
|
||||
Finder inWeeks(String text) =>
|
||||
find.descendant(of: weeks, matching: find.text(text));
|
||||
expect(inWeeks('Last 12 weeks'), findsOneWidget);
|
||||
expect(inWeeks('This week'), findsOneWidget);
|
||||
// 12:30 running and 30:00 rowing.
|
||||
expect(inWeeks('42 min · 2.50 km · 2 workouts'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.bySemanticsLabel('Week of 31 Aug'));
|
||||
await tester.pump();
|
||||
expect(inWeeks('Week of 31 Aug'), findsOneWidget);
|
||||
expect(inWeeks('12 min · 2.50 km · 1 workout'), findsOneWidget);
|
||||
|
||||
final year = find.byType(YearCard);
|
||||
expect(
|
||||
find.descendant(of: year, matching: find.text('2026')),
|
||||
findsOneWidget,
|
||||
);
|
||||
double top(String text) => tester
|
||||
.getTopLeft(find.descendant(of: year, matching: find.text(text)))
|
||||
.dy;
|
||||
expect(top('Rowing indoors'), lessThan(top('Running')));
|
||||
expect(
|
||||
find.descendant(
|
||||
of: year,
|
||||
matching: find.text('2 workouts · 25 min · 5.00 km'),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets("a workout's page shows its map, totals, splits and charts, "
|
||||
'and where the map comes from', (tester) async {
|
||||
await saveRun(DateTime(2026, 10, 27, 7, 30));
|
||||
|
|
|
|||
|
|
@ -109,4 +109,84 @@ void main() {
|
|||
|
||||
expect(week.longestDay, const Duration(hours: 1));
|
||||
});
|
||||
|
||||
group('the weeks Profile shows (D60)', () {
|
||||
test('twelve, oldest first, the last this one', () {
|
||||
final weeks = weeksBack(
|
||||
[
|
||||
workout(DateTime(2026, 10, 27, 7), moving: const Duration(hours: 1)),
|
||||
workout(DateTime(2026, 8, 12, 7), moving: const Duration(hours: 2)),
|
||||
// Thirteen weeks back: out.
|
||||
workout(DateTime(2026, 7, 29, 7)),
|
||||
],
|
||||
now: now,
|
||||
firstDay: DateTime.monday,
|
||||
);
|
||||
|
||||
expect(weeks, hasLength(12));
|
||||
expect(weeks.last.start, DateTime(2026, 10, 26));
|
||||
expect(weeks.first.start, DateTime(2026, 8, 10));
|
||||
expect(weeks.last.time, const Duration(hours: 1));
|
||||
expect(weeks.first.time, const Duration(hours: 2));
|
||||
expect(weeks.map((week) => week.count).reduce((a, b) => a + b), 2);
|
||||
});
|
||||
|
||||
test('by the calendar across a change of the clocks', () {
|
||||
// Clocks went back on 25 October 2026 in Europe.
|
||||
final weeks = weeksBack(
|
||||
const [],
|
||||
now: now,
|
||||
firstDay: DateTime.monday,
|
||||
);
|
||||
for (final week in weeks) {
|
||||
expect(week.start.weekday, DateTime.monday);
|
||||
expect(week.start.hour, 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group('the year Profile shows (D60)', () {
|
||||
test('per activity, the most trained first, this calendar year alone', () {
|
||||
final year = yearSummary([
|
||||
workout(
|
||||
DateTime(2026, 3, 1, 7),
|
||||
moving: const Duration(hours: 1),
|
||||
metres: 10000,
|
||||
),
|
||||
workout(
|
||||
DateTime(2026, 10, 30, 7),
|
||||
moving: const Duration(hours: 1),
|
||||
metres: 12000,
|
||||
),
|
||||
WorkoutContent(
|
||||
activity: Activity.cycling,
|
||||
startedAt: DateTime(2026, 6, 1, 9).toUtc(),
|
||||
endedAt: DateTime(2026, 6, 1, 12).toUtc(),
|
||||
distanceMetres: 80000,
|
||||
),
|
||||
// Last year's.
|
||||
workout(DateTime(2025, 12, 31, 20), metres: 5000),
|
||||
], now: now);
|
||||
|
||||
expect(year.year, 2026);
|
||||
expect(year.activities, [
|
||||
(
|
||||
activity: Activity.cycling,
|
||||
count: 1,
|
||||
time: const Duration(hours: 3),
|
||||
metres: 80000,
|
||||
),
|
||||
(
|
||||
activity: Activity.running,
|
||||
count: 2,
|
||||
time: const Duration(hours: 2),
|
||||
metres: 22000,
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test('a year without workouts lists none', () {
|
||||
expect(yearSummary(const [], now: now).activities, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue