Line data Source code
1 : import 'package:flutter/foundation.dart';
2 :
3 : import '../worker.dart';
4 :
5 : /// Represents a single file in a multi-file upload.
6 : @immutable
7 : class UploadFile {
8 2 : const UploadFile({
9 : required this.filePath,
10 : this.fieldName = 'file',
11 : this.fileName,
12 : this.mimeType,
13 : });
14 :
15 : /// Absolute path to the file on device.
16 : final String filePath;
17 :
18 : /// Multipart form field name (default: 'file').
19 : final String fieldName;
20 :
21 : /// Override the filename sent in the Content-Disposition header.
22 : /// Defaults to the basename of [filePath].
23 : final String? fileName;
24 :
25 : /// MIME type override. Auto-detected from extension if not provided.
26 : final String? mimeType;
27 :
28 6 : Map<String, dynamic> toMap() => {
29 6 : 'filePath': filePath,
30 6 : 'fileFieldName': fieldName,
31 7 : if (fileName != null) 'fileName': fileName,
32 7 : if (mimeType != null) 'mimeType': mimeType,
33 : };
34 : }
35 :
36 : /// Upload multiple files in a single multipart/form-data HTTP request.
37 : ///
38 : /// Reuses the existing [HttpUploadWorker] native implementation which
39 : /// already supports the `files` array format.
40 : ///
41 : /// Example:
42 : /// ```dart
43 : /// await NativeWorkManager.enqueue(
44 : /// taskId: 'batch-upload',
45 : /// trigger: const TaskTrigger.oneTime(),
46 : /// worker: NativeWorker.multiUpload(
47 : /// url: 'https://api.example.com/photos',
48 : /// files: [
49 : /// const UploadFile(filePath: '/cache/photo1.jpg', fieldName: 'photos'),
50 : /// const UploadFile(filePath: '/cache/photo2.jpg', fieldName: 'photos'),
51 : /// const UploadFile(filePath: '/cache/document.pdf', fieldName: 'docs',
52 : /// mimeType: 'application/pdf'),
53 : /// ],
54 : /// headers: {'Authorization': 'Bearer token'},
55 : /// additionalFields: {'albumId': '42'},
56 : /// ),
57 : /// constraints: const Constraints(requiresNetwork: true),
58 : /// );
59 : /// ```
60 : @immutable
61 : final class MultiUploadWorker extends Worker {
62 3 : const MultiUploadWorker({
63 : required this.url,
64 : required this.files,
65 : this.headers = const {},
66 : this.additionalFields = const {},
67 : this.timeout = const Duration(minutes: 10),
68 : this.useBackgroundSession = false,
69 : });
70 :
71 : /// Upload endpoint URL.
72 : final String url;
73 :
74 : /// Files to upload. Must not be empty, maximum 50 files.
75 : final List<UploadFile> files;
76 :
77 : /// HTTP headers to include in the request.
78 : final Map<String, String> headers;
79 :
80 : /// Additional multipart form fields (non-file fields).
81 : final Map<String, String> additionalFields;
82 :
83 : /// Request timeout.
84 : final Duration timeout;
85 :
86 : /// Use a background URLSession on iOS (survives app termination).
87 : final bool useBackgroundSession;
88 :
89 : // NET-011: MultiUploadWorker maps to the same native class as HttpUploadWorker because
90 : // the native implementation already supports multi-file uploads via a `files` array in the
91 : // config JSON. A dedicated 'MultiUploadWorker' native class would be identical; keeping one
92 : // implementation avoids duplication. Logs and crash reports will show "HttpUploadWorker" —
93 : // this is expected behaviour.
94 3 : @override
95 : String get workerClassName => 'HttpUploadWorker';
96 :
97 3 : @override
98 3 : Map<String, dynamic> toMap() => {
99 : 'workerType': 'httpUpload',
100 3 : 'url': url,
101 15 : 'files': files.map((f) => f.toMap()).toList(),
102 3 : 'headers': headers,
103 3 : 'additionalFields': additionalFields,
104 6 : 'timeoutMs': timeout.inMilliseconds,
105 3 : 'useBackgroundSession': useBackgroundSession,
106 : };
107 : }
|