feat(app): the home dashboard: streak, goal, week, activity

Home opens on a study dashboard. The streak's flame sits in the app bar
and opens a sheet of how it stands this week. Below: the study goal (a
ring of the day's or week's progress, set and removed in its sheet), the
week (each day studied, rested, missed or to come with the cards falling
due; days to go back and on; today or a day to come made a rest day),
and a year of activity as a heatmap, its weekdays staying in view while
the weeks scroll. A long press arranges the dashboard: drag a widget
elsewhere, hide or show it; the arrangement is synced.

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-01 22:13:34 +02:00
commit 111532435f
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,112 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
import 'package:halkyon_learn_flutter/src/feature/home/widget/goal_card.dart';
import 'package:halkyon_learn_flutter/src/feature/home/widget/streak.dart';
import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
import 'package:material_ui/material_ui.dart' hide Card;
import '../../../support/fake_card_gateway.dart';
import '../../../support/fake_review_gateway.dart';
import '../../../support/fake_settings_gateway.dart';
import '../../../support/pump_app.dart';
import '../../../support/test_dependencies.dart';
void main() {
late FakeCardGateway cardGateway;
late FakeReviewGateway reviewGateway;
late FakeSettingsGateway settingsGateway;
setUp(() {
cardGateway = FakeCardGateway();
reviewGateway = FakeReviewGateway();
settingsGateway = FakeSettingsGateway();
});
Future<void> showHome(WidgetTester tester) async {
tester.view.physicalSize = const Size(1200, 1600);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await pumpApp(
tester,
testDependencies(
cardGateway: cardGateway,
reviewGateway: reviewGateway,
settingsGateway: settingsGateway,
),
);
}
int streak(WidgetTester tester) =>
tester.widget<StreakChip>(find.byType(StreakChip)).streak;
testWidgets('a card rated today lights the streak and the heatmap', (
tester,
) async {
final cards = CardRepository(cardGateway);
addTearDown(cards.dispose);
final id = await cards.create(
'japanese',
CardContent(front: RichContent.empty(), back: RichContent.empty()),
);
await ReviewRepository(reviewGateway).record(
cardId: id,
rating: Rating.good,
reviewedAt: DateTime.now(),
duration: Duration.zero,
);
await showHome(tester);
expect(streak(tester), 1);
// The card written and the rating.
expect(find.text('2 activities in the last year'), findsOneWidget);
});
testWidgets('a goal is set in its sheet and shows its progress', (
tester,
) async {
await showHome(tester);
await tester.tap(find.text('Set a study goal'));
await tester.pumpAndSettle();
await tester.tap(find.text('Per week'));
await tester.pump();
await tester.tap(find.byTooltip('More'));
await tester.pump();
await tester.tap(find.text('Save goal'));
await tester.pumpAndSettle();
expect(find.text('Weekly goal'), findsOneWidget);
expect(find.text('0 of 21 cards this week'), findsOneWidget);
expect(find.byType(GoalCard), findsOneWidget);
});
testWidgets('resting today keeps the streak', (tester) async {
await showHome(tester);
expect(streak(tester), 0);
await tester.tap(find.text('Rest day'));
await tester.pumpAndSettle();
expect(streak(tester), 1);
});
testWidgets('a long press arranges the dashboard: a widget hidden stays '
'hidden', (tester) async {
await showHome(tester);
await tester.longPress(find.text('Set a study goal'));
await tester.pumpAndSettle();
expect(find.text('Arrange widgets'), findsOneWidget);
await tester.tap(find.byTooltip('Hide Study goal'));
await tester.pumpAndSettle();
await tester.tap(find.text('Done'));
await tester.pumpAndSettle();
expect(find.text('Set a study goal'), findsNothing);
expect(find.text('Your activity'), findsOneWidget);
});
}