feat(learn): exporting shows its steps, counts and percent, with Abort

Exporting a studyset said "Exporting…" in a snack bar until the place to
keep it was asked. It now shows a sheet with its steps (reading the
content, gathering files for an archive, packing), how many of the step's
items are done and the percent of all of it, and Abort, which keeps
nothing (decided 9 Nov 2026); then asks where to keep the file as before.

Both exporters check between studysets, and the archive's between files;
packing runs on its isolate to its end, its result dropped once aborted.
ExportCubit runs an export's work behind the sheet (runExport), which
says how it ended: made, failed (a file missing named as before) or
aborted.

Whats-New: Exporting shows its steps and how far it has come, and Abort stops it.
Whats-New-De: Der Export zeigt seine Schritte und wie weit er ist, und „Abbrechen“ beendet ihn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-10 16:17:07 +01:00
commit 99b75645ad
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
@ -7,8 +8,11 @@ import 'package:halkyon_editor/halkyon_editor.dart';
import 'package:halkyon_learn_flutter/src/core/asset/data/asset_payload.dart';
import 'package:halkyon_learn_flutter/src/core/asset/data/asset_repository.dart';
import 'package:halkyon_learn_flutter/src/core/router/route_paths.dart';
import 'package:halkyon_learn_flutter/src/core/widget/work_progress_view.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/export/data/export_archive.dart';
import 'package:halkyon_learn_flutter/src/feature/export/data/studyset_markdown.dart';
import 'package:halkyon_learn_flutter/src/feature/export/widget/export_studyset.dart';
import 'package:halkyon_learn_flutter/src/feature/study/data/review_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
@ -34,6 +38,7 @@ void main() {
late MemoryAssetFileCache cache;
late FakeFileSaver saver;
late FakeReviewGateway reviewGateway;
late _HeldExportArchiver archiver;
setUp(() {
studysets = FakeStudysetGateway()
@ -46,6 +51,7 @@ void main() {
cache = MemoryAssetFileCache();
saver = FakeFileSaver();
reviewGateway = FakeReviewGateway();
archiver = _HeldExportArchiver();
});
Future<String> card(CardContent content) {
@ -74,6 +80,7 @@ void main() {
assetFiles: cache,
fileSaver: saver,
reviewGateway: reviewGateway,
exportArchiver: archiver,
),
location: RoutePaths.studyset('spanish'),
);
@ -297,6 +304,23 @@ void main() {
);
});
testWidgets('shows its steps under way; Abort keeps nothing (decided '
'9 Nov 2026)', (tester) async {
final hold = archiver.hold = Completer<void>();
await exportFromMenu(tester, choose: 'Archive');
expect(find.text('Step 3 of 3: Packing'), findsOneWidget);
await tester.tap(find.byKey(WorkProgressView.abortKey));
await tester.pump();
expect(find.text('Aborting…'), findsOneWidget);
hold.complete();
await tester.pumpAndSettle();
expect(saver.saved, isEmpty);
expect(find.text('Export aborted.'), findsOneWidget);
expect(find.textContaining('Exported'), findsNothing);
});
testWidgets('cancelled where to keep it, it says nothing more', (
tester,
) async {
@ -309,3 +333,14 @@ void main() {
expect(find.textContaining('Exported'), findsNothing);
});
}
/// Zips as the app does, once [hold] (where set) completes.
class _HeldExportArchiver() implements ExportArchiver {
Completer<void>? hold;
@override
Future<Uint8List> zip(List<ExportFile> files) async {
await hold?.future;
return zipExport(files);
}
}