Merge PR #1523 into 2.0.0-beta.2

This commit is contained in:
Levi committed 2026-09-17 21:47:44 +08:00
commit f7dfc4226b
11 files changed
+261 -20

No files matched your search

@@ -36,8 +36,15 @@ enum TaskAssetType {
}
class TaskFileNode {
const TaskFileNode({required this.path, required this.name, required this.sizeBytes, this.downloadedBytes});
const TaskFileNode({
required this.path,
required this.name,
required this.sizeBytes,
this.downloadedBytes,
this.resourceIndex,
});
final int? resourceIndex;
final String path;
final String name;
final int sizeBytes;
@@ -146,7 +153,7 @@ class TaskRecord {
totalBytes: totalBytes > 0 ? totalBytes : null,
speedBytes: task.progress.speed,
uploadedBytes: task.progress.uploaded,
files: _fileNodes(task.meta.res?.files),
files: _fileNodes(task.meta.res?.files, task.meta.opts.selectFiles),
uploading: task.uploading,
isFolder: task.meta.res?.name.isNotEmpty ?? false,
protocol: task.protocol,
@@ -314,12 +321,23 @@ int? _remainingSeconds(api_task.Task task, int totalBytes, int downloadedBytes)
return ((totalBytes - downloadedBytes) / speed).ceil();
}
List<TaskFileNode> _fileNodes(List<api_resource.FileInfo>? files) {
List<TaskFileNode> _fileNodes(List<api_resource.FileInfo>? files, List<int> selectedIndexes) {
if (files == null || files.isEmpty) {
return const [];
}
final selected = selectedIndexes.toSet();
return files
.map((file) => TaskFileNode(path: file.path, name: file.name, sizeBytes: file.size))
.asMap()
.entries
.where((entry) => selected.isEmpty || selected.contains(entry.key))
.map(
(entry) => TaskFileNode(
resourceIndex: entry.key,
path: entry.value.path,
name: entry.value.name,
sizeBytes: entry.value.size,
),
)
.toList(growable: false);
}
@@ -77,7 +77,7 @@ class _TaskFileTreeState extends State<TaskFileTree> {
.entries
.map(
(entry) => FileTreeItem<TaskFileNode>(
key: entry.key.toString(),
key: (entry.value.resourceIndex ?? entry.key).toString(),
path: entry.value.path,
name: entry.value.name,
size: entry.value.sizeBytes,
@@ -1,8 +1,48 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:gopeed/api/model/task.dart' as api;
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({
'id': 'bt-selection',
'name': 'bundle',
'protocol': 'bt',
'status': 'running',
'uploading': false,
'createdAt': '2026-01-01T00:00:00Z',
'updatedAt': '2026-01-01T00:00:00Z',
'meta': {
'req': {'url': 'magnet:?xt=urn:btih:test'},
'opts': {'path': '/downloads', 'selectFiles': selection},
'res': {
'name': 'bundle',
'size': size,
'files': [
{'name': 'a.txt', 'size': 10},
{'name': 'b.txt', 'size': 20},
{'name': 'c.txt', 'path': 'nested', 'size': 30},
],
},
},
'progress': {'used': 0, 'speed': 0, 'downloaded': 10, 'uploadSpeed': 0, 'uploaded': 0},
});
test('selected files retain original indexes for runtime progress', () {
final record = TaskRecord.fromApi(buildTask([2, 0], 40));
expect(record.files.map((file) => file.name), ['a.txt', 'c.txt']);
expect(record.files.map((file) => file.resourceIndex), [0, 2]);
expect(record.files.last.path, 'nested');
expect(record.totalBytes, 40);
expect(record.progress, 0.25);
});
test('empty selection preserves default all files', () {
final record = TaskRecord.fromApi(buildTask([], 60));
expect(record.files.length, 3);
expect(record.totalBytes, 60);
});
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'),