feat(task): record and display task completion time

Add DoneAt to the download task model: set when a task finishes and
cleared when it is downloaded again. The value flows through the API
model and task record, and is shown as "Completed at" in the task
drawer info tab next to the creation time.
This commit is contained in:
Levi committed 2026-09-20 08:56:54 +08:00
1 parent 694b43e3f9
commit 4b231f52c5
30 files changed
+82 -23

No files matched your search

@@ -4,7 +4,7 @@ import 'package:gopeed/core/icons/gopeed_icons.dart';
import 'package:gopeed/features/tasks/domain/task_record.dart';
void main() {
api.Task buildTask(List<int> selection, int size) => api.Task.fromJson({
api.Task buildTask(List<int> selection, int size, {String? doneAt}) => api.Task.fromJson({
'id': 'bt-selection',
'name': 'bundle',
'protocol': 'bt',
@@ -12,6 +12,7 @@ void main() {
'uploading': false,
'createdAt': '2026-01-01T00:00:00Z',
'updatedAt': '2026-01-01T00:00:00Z',
'doneAt': doneAt,
'meta': {
'req': {'url': 'magnet:?xt=urn:btih:test'},
'opts': {'path': '/downloads', 'selectFiles': selection},
@@ -43,6 +44,13 @@ void main() {
expect(record.totalBytes, 60);
});
test('completion time maps from api and stays null for legacy tasks', () {
final completed = TaskRecord.fromApi(buildTask([], 60, doneAt: '2026-01-02T03:04:05Z'));
expect(completed.doneAt, DateTime.parse('2026-01-02T03:04:05Z'));
final legacy = TaskRecord.fromApi(buildTask([], 60));
expect(legacy.doneAt, isNull);
});
test('file names preserve hash and question mark characters for type detection', () {
expect(
taskFileTypeIcon('Slow English Podcast #2 _ Eating, Talking, and Learning Natural English.mp4'),
+11 -1
View File
@@ -2649,7 +2649,7 @@ void main() {
await tester.tap(find.byKey(const ValueKey('settings-language-select')));
await tester.pumpAndSettle();
expect(find.text('English'), findsOneWidget);
expect(SettingsLanguageSelect.supportedValues, hasLength(21));
expect(SettingsLanguageSelect.supportedValues, hasLength(22));
expect(SettingsLanguageSelect.supportedValues, containsAll(<String>['zh', 'zh_TW', 'pt']));
expect(SettingsLanguageSelect.supportedValues, isNot(contains('zh_CN')));
expect(SettingsLanguageSelect.supportedValues, isNot(contains('pt_BR')));
@@ -4127,6 +4127,7 @@ void main() {
remaining: '1 minute remaining',
downloadDuration: const Duration(hours: 1, minutes: 2, seconds: 3),
createdAt: DateTime(2026, 8, 27, 12, 34, 56),
doneAt: DateTime(2026, 8, 28, 1, 2, 3),
);
await tester.pumpWidget(
@@ -4197,7 +4198,14 @@ void main() {
expect(find.text('50 B / 100 B'), findsNothing);
expect(find.text('Created'), findsOneWidget);
expect(find.text('2026-08-27 12:34:56'), findsOneWidget);
expect(find.text('Completed at'), findsOneWidget);
expect(find.text('2026-08-28 01:02:03'), findsOneWidget);
expect(
tester.getBottomLeft(find.text('2026-08-27 12:34:56')).dy,
lessThan(tester.getTopLeft(find.text('Completed at')).dy),
);
final infoDivider = find.byKey(const ValueKey('task-details-info-divider'));
expect(tester.getBottomLeft(find.text('Completed at')).dy, lessThan(tester.getTopLeft(infoDivider).dy));
expect(tester.getBottomLeft(find.text('Created')).dy, lessThan(tester.getTopLeft(infoDivider).dy));
expect(tester.getBottomLeft(infoDivider).dy, lessThan(tester.getTopLeft(find.text('Download Link')).dy));
expect(find.text('1 minute remaining'), findsNothing);
@@ -6164,6 +6172,7 @@ TaskRecord _taskRecord({
String? remaining,
Duration? downloadDuration,
DateTime? createdAt,
DateTime? doneAt,
bool isFolder = false,
Map<String, String> requestHeaders = const {},
}) {
@@ -6184,6 +6193,7 @@ TaskRecord _taskRecord({
remaining: remaining,
downloadDuration: downloadDuration,
createdAt: createdAt,
doneAt: doneAt,
requestHeaders: requestHeaders,
);
}