From d920cc294feb572d42c5fb970aa651a1611f136b Mon Sep 17 00:00:00 2001 From: Ashwin Date: Sun, 27 Sep 2026 18:58:13 +0530 Subject: [PATCH] chore: update dependencies and version in pubspec.yaml - Bump version to 1.10.0+14 - Update http from ^1.1.0 to ^1.6.0 - Update google_sign_in from ^7.1.1 to ^7.2.0 - Update flutter_secure_storage from ^10.3.1 to ^11.2.0 - Update firebase_core from ^4.12.1 to ^4.15.0 - Update firebase_crashlytics from ^5.2.6 to ^5.4.0 - Update go_router from ^17.2.3 to ^18.0.1 - Update flutter_riverpod from ^3.3.1 to ^3.4.3 - Update table_calendar from ^3.2.0 to ^3.2.1 - Update intl from ^0.20.2 to ^0.20.3 - Update url_launcher from ^6.3.1 to ^6.3.2 - Update file_picker from ^11.0.3 to ^13.1.0 - Update google_fonts from ^8.1.0 to ^8.2.1 - Update lucide_icons_flutter from ^3.1.15 to ^3.1.20 - Add cross_file dependency for testing test: add FakePlatformFile for testing file uploads - Introduce FakePlatformFile to mock file picker behavior in tests - Update attachment_upload_test, csv_import_provider_test, support_ticket_test, and csv_import_sheet_test to use FakePlatformFile --- mobile/lib/providers/csv_import_provider.dart | 6 +- mobile/lib/providers/support_provider.dart | 11 +- .../invoice_template_form_screen.dart | 9 +- mobile/lib/services/attachment_upload.dart | 36 +- mobile/lib/services/record_export.dart | 6 +- .../lib/widgets/forms/csv_import_sheet.dart | 24 +- mobile/pubspec.lock | 453 +++++++----------- mobile/pubspec.yaml | 29 +- mobile/test/helpers/fake_platform_file.dart | 59 +++ mobile/test/unit/attachment_upload_test.dart | 27 +- .../test/unit/csv_import_provider_test.dart | 9 +- mobile/test/unit/support_ticket_test.dart | 4 +- .../test/widgets/csv_import_sheet_test.dart | 4 +- 13 files changed, 321 insertions(+), 356 deletions(-) create mode 100644 mobile/test/helpers/fake_platform_file.dart diff --git a/mobile/lib/providers/csv_import_provider.dart b/mobile/lib/providers/csv_import_provider.dart index 2325bb65..86fc75ee 100644 --- a/mobile/lib/providers/csv_import_provider.dart +++ b/mobile/lib/providers/csv_import_provider.dart @@ -433,14 +433,16 @@ class CsvImportNotifier extends Notifier { /// file is refused without loading it, and the size again on the bytes. Future choose(PlatformFile? picked) async { if (picked == null) return; - final early = _refuse(picked.name, picked.size); + // `lengthSync` is what the picker reported, without I/O. Unknown counts + // as 0 here; the bytes are checked again below either way. + final early = _refuse(picked.name, picked.lengthSync() ?? 0); if (early != null) { state = CsvImportState(error: early); return; } final Uint8List bytes; try { - bytes = picked.bytes ?? await picked.xFile.readAsBytes(); + bytes = await picked.readAsBytes(); } catch (_) { if (ref.mounted) { state = const CsvImportState( diff --git a/mobile/lib/providers/support_provider.dart b/mobile/lib/providers/support_provider.dart index 592204c2..0de6c154 100644 --- a/mobile/lib/providers/support_provider.dart +++ b/mobile/lib/providers/support_provider.dart @@ -75,7 +75,7 @@ class SupportRepository { required String body, PlatformFile? attachment, }) async { - final invalid = _attachmentError(attachment); + final invalid = await _attachmentError(attachment); if (invalid != null) return SupportMutationResult(error: invalid); final response = attachment == null ? await _api.post(ApiConfig.supportTickets, { @@ -102,7 +102,7 @@ class SupportRepository { required String body, PlatformFile? attachment, }) async { - final invalid = _attachmentError(attachment); + final invalid = await _attachmentError(attachment); if (invalid != null) return SupportMutationResult(error: invalid); final response = attachment == null ? await _api.post(ApiConfig.supportTicketReplies(ticketId), { @@ -118,12 +118,13 @@ class SupportRepository { return _ticketResult(response); } - String? _attachmentError(PlatformFile? file) { + Future _attachmentError(PlatformFile? file) async { if (file == null) return null; - if ((file.path ?? '').isEmpty) { + final size = await file.length(); + if ((file.path ?? '').isEmpty || size == null) { return 'That file could not be read. Try picking it again.'; } - if (file.size > attachmentMaxBytes) { + if (size > attachmentMaxBytes) { return 'Files must be 25 MB or smaller.'; } return null; diff --git a/mobile/lib/screens/invoices/invoice_template_form_screen.dart b/mobile/lib/screens/invoices/invoice_template_form_screen.dart index b8f86436..213a538c 100644 --- a/mobile/lib/screens/invoices/invoice_template_form_screen.dart +++ b/mobile/lib/screens/invoices/invoice_template_form_screen.dart @@ -120,14 +120,7 @@ class _InvoiceTemplateFormScreenState // images because `logo` is an ImageField: Pillow refuses anything else // server-side, and a 400 after an upload is a poor way to learn that. final result = await selectAttachment( - pickFile: () async { - final picked = await FilePicker.pickFiles( - type: FileType.image, - withReadStream: false, - ); - if (picked == null || picked.files.isEmpty) return null; - return picked.files.first; - }, + pickFile: () => FilePicker.pickFile(type: FileType.image), ); if (result.cancelled || !mounted) return; if (result.error != null) { diff --git a/mobile/lib/services/attachment_upload.dart b/mobile/lib/services/attachment_upload.dart index e4993c08..ba2cc061 100644 --- a/mobile/lib/services/attachment_upload.dart +++ b/mobile/lib/services/attachment_upload.dart @@ -74,10 +74,16 @@ Future selectAttachment({ 'That file could not be read. Try picking it again.', ); } - if (picked.size > attachmentMaxBytes) { + final size = await picked.length(); + if (size == null) { + return const AttachmentSelectionResult.failed( + 'That file could not be read. Try picking it again.', + ); + } + if (size > attachmentMaxBytes) { final limit = attachmentMaxBytes ~/ (1024 * 1024); return AttachmentSelectionResult.failed( - '${picked.name} is ${_megabytes(picked.size)} MB. ' + '${picked.name} is ${_megabytes(size)} MB. ' 'Files must be $limit MB or smaller.', ); } @@ -117,12 +123,12 @@ Future downloadAttachment({ } } -Future _saveFile(Uint8List bytes, String fileName) => - FilePicker.saveFile( +Future _saveFile(Uint8List bytes, String fileName) async => + (await FilePicker.saveFile( dialogTitle: 'Save attachment', fileName: fileName, bytes: bytes, - ); + ))?.toString(); /// Remove the temporary copy made by the native picker after a form uploads it. Future clearAttachmentPickerCache() => _clearPickerCache(); @@ -175,13 +181,21 @@ Future pickAndUploadAttachment({ ); } - if (picked.size > attachmentMaxBytes) { + final size = await picked.length(); + if (size == null) { + if (pickFile == null) await _clearPickerCache(); + return const AttachmentUploadResult.failed( + 'That file could not be read. Try picking it again.', + ); + } + + if (size > attachmentMaxBytes) { // Clear it here too. This is the case where the copy matters most: the // file that was too big to send is the one it hurts to keep. if (pickFile == null) await _clearPickerCache(); final limit = attachmentMaxBytes ~/ (1024 * 1024); return AttachmentUploadResult.failed( - '${picked.name} is ${_megabytes(picked.size)} MB. ' + '${picked.name} is ${_megabytes(size)} MB. ' 'Files must be $limit MB or smaller.', ); } @@ -203,15 +217,11 @@ Future pickAndUploadAttachment({ return AttachmentUploadResult.uploaded(response.data); } -Future _pickOneFile() async { - final result = await FilePicker.pickFiles(withReadStream: false); - if (result == null || result.files.isEmpty) return null; - return result.files.first; -} +Future _pickOneFile() => FilePicker.pickFile(); /// Drop the picker's cached copy of what was just uploaded. /// -/// `pickFiles` copies the chosen file into the app's cache directory, so +/// `pickFile` copies the chosen file into the app's cache directory, so /// without this every attachment a person sends stays on their phone a second /// time, in a directory they never see. Only when the real picker ran: an /// injected one has nothing to clear, and the platform channel is not there in diff --git a/mobile/lib/services/record_export.dart b/mobile/lib/services/record_export.dart index b093b6aa..d40280cc 100644 --- a/mobile/lib/services/record_export.dart +++ b/mobile/lib/services/record_export.dart @@ -81,9 +81,9 @@ Future exportRecordsCsv({ } } -Future _saveWithPicker(Uint8List bytes, String fileName) => - FilePicker.saveFile( +Future _saveWithPicker(Uint8List bytes, String fileName) async => + (await FilePicker.saveFile( dialogTitle: 'Save CSV export', fileName: fileName, bytes: bytes, - ); + ))?.toString(); diff --git a/mobile/lib/widgets/forms/csv_import_sheet.dart b/mobile/lib/widgets/forms/csv_import_sheet.dart index 9f959870..a6cc2e79 100644 --- a/mobile/lib/widgets/forms/csv_import_sheet.dart +++ b/mobile/lib/widgets/forms/csv_import_sheet.dart @@ -70,13 +70,9 @@ class _CsvImportSheetState extends ConsumerState { if (injected == null && picked != null) await clearAttachmentPickerCache(); } - static Future _pickOne() async { - // Any type, then refused by name: a CSV is labelled text/plain by some - // Android file managers, and filtering on MIME type would hide it. - final result = await FilePicker.pickFiles(); - if (result == null || result.files.isEmpty) return null; - return result.files.first; - } + // Any type, then refused by name: a CSV is labelled text/plain by some + // Android file managers, and filtering on MIME type would hide it. + static Future _pickOne() => FilePicker.pickFile(); /// Ask where to keep [content] as [fileName]. `null` when the person /// closed the dialog, a sentence to show otherwise. @@ -118,12 +114,14 @@ class _CsvImportSheetState extends ConsumerState { if (mounted) setState(() => _errorsNote = note); } - static Future _saveWithPicker(String fileName, List bytes) => - FilePicker.saveFile( - dialogTitle: 'Save CSV file', - fileName: fileName, - bytes: Uint8List.fromList(bytes), - ); + static Future _saveWithPicker( + String fileName, + List bytes, + ) async => (await FilePicker.saveFile( + dialogTitle: 'Save CSV file', + fileName: fileName, + bytes: Uint8List.fromList(bytes), + ))?.toString(); Future _commit() async { final ok = await _notifier.commit(); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index b27629ac..65c33fa4 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -1,38 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c - url: "https://pub.dev" - source: hosted - version: "99.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: "6727cf2ced9b104abca9daa278380be2eca2b98ce33d4b46f11708e387dc6b4d" + sha256: f6966125633a34f82d9be2ee895b755cff3554087b1d00a9eb884e5947f4bca9 url: "https://pub.dev" source: hosted - version: "1.3.76" - analyzer: + version: "1.3.77" + android_file_picker: dependency: transitive description: - name: analyzer - sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" + name: android_file_picker + sha256: "14ab27769b54c48d5a8a71aa9858372b7a3ae77572ea0a8aee744643c5d8c53d" url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "2.0.0" archive: dependency: transitive description: name: archive - sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + sha256: "6c5bcd986e06b94e3c40244af471750840a3d2341d1f9763a1100a14add517b4" url: "https://pub.dev" source: hosted - version: "4.0.9" + version: "4.3.0" args: dependency: transitive description: @@ -73,14 +65,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" - cli_config: - dependency: transitive - description: - name: cli_config - sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec - url: "https://pub.dev" - source: hosted - version: "0.2.0" cli_util: dependency: transitive description: @@ -93,18 +77,18 @@ packages: dependency: transitive description: name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" code_assets: dependency: transitive description: name: code_assets - sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + sha256: "828110d598123b5ea96c00c9f3c72105bf79f8ee36c20a39b26209ade421ec57" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.0" collection: dependency: transitive description: @@ -113,30 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - coverage: - dependency: transitive - description: - name: coverage - sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" - url: "https://pub.dev" - source: hosted - version: "1.15.1" cross_file: - dependency: transitive + dependency: "direct dev" description: name: cross_file - sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" + sha256: f141ea4f277af142a0356955707f6556f37b03947d39d55585981a06ca437bd6 url: "https://pub.dev" source: hosted - version: "0.3.5+4" + version: "0.3.5+5" crypto: dependency: transitive description: @@ -153,14 +121,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" + cupertino_ui: + dependency: transitive + description: + name: cupertino_ui + sha256: c23816577a1280cff409e989541767887011f8f1d8c1863a794ed308af46f2a9 + url: "https://pub.dev" + source: hosted + version: "1.1.1" dbus: dependency: transitive description: name: dbus - sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" + sha256: f7db8504e715b835f5ebe5fe74604ccd961d504c73389a5d8e81bafc2e2f3612 url: "https://pub.dev" source: hosted - version: "0.7.14" + version: "0.8.0" equatable: dependency: transitive description: @@ -185,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" file: dependency: transitive description: @@ -197,50 +181,82 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "29cc1fdb20613876cc7afc529738c1c0f11a9ca159b010edad0c566ac330847e" + sha256: "98c0b156b6380ba55bc767a14e2e6b3feb246e713ad40092424596fa79005bea" url: "https://pub.dev" source: hosted - version: "11.0.3" + version: "13.1.0" + file_picker_darwin: + dependency: transitive + description: + name: file_picker_darwin + sha256: "51aef9f4c80449c736e7cc02198675fda73689f0bd45a84da642cfeab0250d46" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file_picker_linux: + dependency: transitive + description: + name: file_picker_linux + sha256: "9c870771500c758eb46506c98532c3ee60e6e51d0a62dcde24a849ef213ea5ce" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + file_picker_platform_interface: + dependency: transitive + description: + name: file_picker_platform_interface + sha256: bbdc085a6f168e63f147e9ce8988f79fa0800a58e7f25a48f762c60837107ba5 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + file_picker_web: + dependency: transitive + description: + name: file_picker_web + sha256: b3004268da0c1b52baa18df430e7ec4438194de855a807d26b447cbf07ef3496 + url: "https://pub.dev" + source: hosted + version: "4.0.0" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "9478ca6700c02d315c6aba37e206e612317f98bb4f335bb1cbd6e0ce67dcf764" + sha256: "5fbbaa2aeb08a52feee559f92a62f5a6b04c9bdff81c8f608636771cf0d7b680" url: "https://pub.dev" source: hosted - version: "4.13.0" + version: "4.15.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: e28f9afdcb5b0f0a8ea74ea3b322f5a7592c81cb45dd9d189913bdae08a2089a + sha256: "9bfbc85faca09346471ac56fbcee00757ebcfd85887c6f602764eff0001902d8" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.1.1" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: f471a288b0101a45567548322ac4a5ad31e3ecbf87a576dcc0e424e6a11e04ea + sha256: "8ed84f5001dbd7a0390e2818d46bc858ceebb970edededdfc8d6552c76fc2f6e" url: "https://pub.dev" source: hosted - version: "3.10.0" + version: "3.12.0" firebase_crashlytics: dependency: "direct main" description: name: firebase_crashlytics - sha256: "96c1d85de9eddc07061d3f7e2fb75596e75a45c9cec9c2e3a7cc9f97e6f3a748" + sha256: f37fe0df162f08bd549114b784037c73a093c64d22625632f97dbbddf18669b9 url: "https://pub.dev" source: hosted - version: "5.2.7" + version: "5.4.0" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: "47d83b71fd39c580297c696a507a7c2652680ea2045e40c6c4e3c371b0ee7bc6" + sha256: "8270dcdb1800db3e5f888ce30a2d5e8f92c379c461f022297c83b2c794aa1792" url: "https://pub.dev" source: hosted - version: "3.8.27" + version: "3.9.0" fixnum: dependency: transitive description: @@ -278,54 +294,51 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" - flutter_plugin_android_lifecycle: + flutter_localizations: dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" - url: "https://pub.dev" - source: hosted - version: "2.0.35" + description: flutter + source: sdk + version: "0.0.0" flutter_riverpod: dependency: "direct main" description: name: flutter_riverpod - sha256: "56e81e662e6e54e59b231da57e90ac0d06ac67d8e3f2273c129a37ae6282b40c" + sha256: "2b7f9d2a3c730ac1a98221b420cef966b990fca7ab546ede324642ca57a533e3" url: "https://pub.dev" source: hosted - version: "3.4.2" + version: "3.4.3" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: "7686b1d6a29985dcbb808c59518226e603e3bfa7c0ddfd1a0d00e4cda77c868e" + sha256: d4e1fb6b2cb524868929e78dc0282fa000554b22060fb53789dc481c9fc95bb8 url: "https://pub.dev" source: hosted - version: "10.3.1" + version: "11.2.0" flutter_secure_storage_darwin: dependency: transitive description: name: flutter_secure_storage_darwin - sha256: "82329fa5cdf343773b1b6897dea959105a29f092454259edff92f9f6637e8149" + sha256: a031ceac9b070e62ef183cd7f7d1a8798ebde402d5049f0fe6eb3a51bd797a21 url: "https://pub.dev" source: hosted - version: "0.3.2" + version: "0.4.3" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "76fa9c841b3b1619fc5b5bc36efc7d158fa2356f223b6caeb1d0c80a54168546" + sha256: caa75bd78f017422912e3a904933113b2428eb79f981ac37acd2dd2a700458ea url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "788060052712555182aba55ecb5f8b6e5cb9cfe8f776c83249a61fe3ce877db4" + sha256: "64951127f001f546891c86f414972bf49ef2c4c8a4c92c65f5790cc0c9100045" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.1.1" flutter_secure_storage_web: dependency: transitive description: @@ -338,10 +351,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_windows - sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.2" flutter_test: dependency: "direct dev" description: flutter @@ -352,30 +365,14 @@ packages: description: flutter source: sdk version: "0.0.0" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" go_router: dependency: "direct main" description: name: go_router - sha256: "55973bf1b27790489f8710ea615fe939f911ddcc5986fe97305e4fc19fa29ee9" + sha256: "008ab21d89d0de8ccd79586838f7533f126c2d894f50bf25f2947888d3c225b5" url: "https://pub.dev" source: hosted - version: "17.4.0" + version: "18.0.1" google_fonts: dependency: "direct main" description: @@ -404,18 +401,18 @@ packages: dependency: transitive description: name: google_sign_in_android - sha256: "57782125965ca87b03d42a147d40b046f1fd6a09141a58913e1b86671a5ef22e" + sha256: c403315d87aba1f815a0a401093f97808cab6cb2bdd11ca431ff8587fd7f1c00 url: "https://pub.dev" source: hosted - version: "7.2.16" + version: "7.2.17" google_sign_in_ios: dependency: transitive description: name: google_sign_in_ios - sha256: ac1e4c1205267cb7999d1d81333fccffdfda29e853f434bbaf71525498bb6950 + sha256: c2154d2849310a0286aab2e35e3bee585f2034afdf8ac364c4d7b3638e4c40b6 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.5" google_sign_in_platform_interface: dependency: transitive description: @@ -436,10 +433,10 @@ packages: dependency: transitive description: name: hooks - sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + sha256: eaac480a35ec0814146c2c48d96aaa829e0e44a7662c88ae84c9edf4bc35651f url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.2.0" http: dependency: "direct main" description: @@ -448,14 +445,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.6.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" - source: hosted - version: "3.2.2" http_parser: dependency: transitive description: @@ -468,10 +457,10 @@ packages: dependency: transitive description: name: image - sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + sha256: a1e7f4951e538a568e14b856702afc9ae1d2f4b202daced8d22c1b9cd211ce89 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.10.1" intl: dependency: "direct main" description: @@ -480,14 +469,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.3" - io: - dependency: transitive - description: - name: io - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" - source: hosted - version: "1.0.5" jni: dependency: transitive description: @@ -500,10 +481,10 @@ packages: dependency: transitive description: name: jni_flutter - sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" + sha256: b2310cdd4c18c65c081ab141a41efa94aa26c65431803703ece51996f174f351 url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.3" jni_util: dependency: transitive description: @@ -580,10 +561,10 @@ packages: dependency: "direct main" description: name: lucide_icons_flutter - sha256: "234155b10641b8ef7bab8a077b93ea6c92fe849c06740cf89dcd86dcf350934f" + sha256: "50a21003cfe094e33b87e780d62ed5be84c32273dec1ba04547aab7df3cba00c" url: "https://pub.dev" source: hosted - version: "3.1.15" + version: "3.1.20" matcher: dependency: transitive description: @@ -600,6 +581,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" + material_ui: + dependency: transitive + description: + name: material_ui + sha256: "134ab2f0843545c4e661e4ba2753fe1bcf0753cb690d5a9ee4870638a40411e9" + url: "https://pub.dev" + source: hosted + version: "1.4.0" meta: dependency: transitive description: @@ -608,38 +597,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" objective_c: dependency: transitive description: name: objective_c - sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e + sha256: ad56fd53a78ff6b1472fa59ff2a4e8b8ccabafc586fc263a1dfad0b99b5553e3 url: "https://pub.dev" source: hosted - version: "9.5.0" + version: "9.6.0" package_config: dependency: transitive description: name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "3.0.0" path: dependency: transitive description: @@ -700,18 +673,18 @@ packages: dependency: transitive description: name: petitparser - sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + sha256: "2f1564aadb14215e478563b5406cb80729d022158c01dc50c96013c2f7eb4790" url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.1.0" platform: dependency: transitive description: name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec url: "https://pub.dev" source: hosted - version: "3.1.6" + version: "3.2.0" plugin_platform_interface: dependency: transitive description: @@ -720,14 +693,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - pool: - dependency: transitive - description: - name: pool - sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.dev" - source: hosted - version: "1.5.2" posix: dependency: transitive description: @@ -740,26 +705,26 @@ packages: dependency: transitive description: name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" record_use: dependency: transitive description: name: record_use - sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + sha256: "1cb8564af8d43b464294411db9217f5ec04891c6f22ee2c32d73ae05e88a6bd2" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "1.1.1" riverpod: dependency: transitive description: name: riverpod - sha256: "84351b3472a447f7b89f961f95c73287009d58c59d8cc1267425d995f78e50f1" + sha256: "484dfc873ea4c4f4240e5635444fa066f87b07862b7279ebd004a2b71fba4b7a" url: "https://pub.dev" source: hosted - version: "3.4.2" + version: "3.4.3" shared_preferences: dependency: "direct main" description: @@ -772,18 +737,18 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" + sha256: "1e12aafe408aa50da80edfd679a2a6bf63ba7ab37c7fa98286da459a757b3399" url: "https://pub.dev" source: hosted - version: "2.4.27" + version: "2.4.28" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea" url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.5.7" shared_preferences_linux: dependency: transitive description: @@ -816,38 +781,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.1" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" - source: hosted - version: "1.4.2" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.dev" - source: hosted - version: "1.1.3" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.dev" - source: hosted - version: "3.0.0" simple_gesture_detector: dependency: transitive description: @@ -861,22 +794,6 @@ packages: description: flutter source: sdk version: "0.0.0" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.dev" - source: hosted - version: "2.1.2" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" - url: "https://pub.dev" - source: hosted - version: "0.10.13" source_span: dependency: transitive description: @@ -889,10 +806,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" url: "https://pub.dev" source: hosted - version: "1.12.1" + version: "1.12.2" state_notifier: dependency: transitive description: @@ -921,10 +838,10 @@ packages: dependency: "direct main" description: name: table_calendar - sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982" + sha256: f276347cad425ef837a41e8d9ad43f3ee7d59227aa4c36d7430607a5a18fa3b3 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" term_glyph: dependency: transitive description: @@ -933,14 +850,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" - test: - dependency: transitive - description: - name: test - sha256: ca578dc12bb8b2f40b67b7d3bd2fac4f31c01a6ff7130a14e2597b919934507f - url: "https://pub.dev" - source: hosted - version: "1.31.1" test_api: dependency: transitive description: @@ -949,14 +858,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.12" - test_core: - dependency: transitive - description: - name: test_core - sha256: d2e98ec12998368dc59ddd47ab709f2cd55acd6b66dc7db764455a44082f4bc5 - url: "https://pub.dev" - source: hosted - version: "0.6.18" timezone: dependency: "direct main" description: @@ -985,34 +886,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + sha256: "611e87fb320b70d1dd721dc46af89c98aceccea9b31fde49e084591414e0c610" url: "https://pub.dev" source: hosted - version: "6.3.32" + version: "6.3.33" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + sha256: "8faa1aab294f1ab4040b43660c887b0418d5fa4f0cffef76a484e6aa1092eb4a" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.4.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + sha256: "10f86fef4c2c43563fa6c211ff9cf757adf4d3ab762c56bd430664a947d70cd0" url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.3" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + sha256: "5e835a3b869c2d70325349c81c5a45c28e20791265b67b2669da6b08c5cd5201" url: "https://pub.dev" source: hosted - version: "3.2.5" + version: "3.2.6" url_launcher_platform_interface: dependency: transitive description: @@ -1033,10 +934,10 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + sha256: "6c5ad3f22cd4c38e089b81963b3cd7bb83b111b2df5dce008bb066162f42e429" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" uuid: dependency: transitive description: @@ -1057,18 +958,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" url: "https://pub.dev" source: hosted - version: "15.2.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" - url: "https://pub.dev" - source: hosted - version: "1.2.1" + version: "15.3.0" web: dependency: transitive description: @@ -1077,38 +970,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.dev" - source: hosted - version: "3.0.3" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" win32: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "6.4.0" + windows_file_picker: + dependency: transitive + description: + name: windows_file_picker + sha256: "9f3aa833068b09e380fdc59560ad260a4a14e9397173abb533699f967443602e" + url: "https://pub.dev" + source: hosted + version: "2.0.0" xdg_directories: dependency: transitive description: @@ -1121,18 +998,18 @@ packages: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "7.0.1" yaml: dependency: transitive description: name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" sdks: - dart: ">=3.12.0 <4.0.0" - flutter: ">=3.44.0" + dart: ">=3.13.0 <4.0.0" + flutter: ">=3.47.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 7d0b2ce5..93f3256c 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -4,7 +4,7 @@ description: "CRM for everyone." # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: "none" # Remove this line if you wish to publish to pub.dev -version: 1.9.0+13 +version: 1.10.0+14 environment: sdk: ^3.8.1 @@ -13,44 +13,44 @@ dependencies: flutter: sdk: flutter - http: ^1.1.0 - google_sign_in: ^7.1.1 + http: ^1.6.0 + google_sign_in: ^7.2.0 shared_preferences: ^2.5.5 # Tokens live here, never in shared_preferences. Keystore-backed on Android, # Keychain on iOS. See lib/services/token_storage.dart. - flutter_secure_storage: ^10.3.1 + flutter_secure_storage: ^11.2.0 jwt_decoder: ^2.0.1 # Firebase / Crashlytics (Android only, no iOS GoogleService-Info.plist yet) - firebase_core: ^4.12.1 - firebase_crashlytics: ^5.2.6 + firebase_core: ^4.15.0 + firebase_crashlytics: ^5.4.0 # Not referenced by name anywhere in lib/, but SwitchListTile.adaptive in # deals_list_screen.dart pulls in Flutter's Cupertino library, whose retained # CupertinoIcons constants need this font present. Dropping it builds fine and # then renders a missing-glyph box. Keep it. cupertino_icons: ^1.0.9 # Navigation - go_router: ^17.2.3 + go_router: ^18.0.1 # State Management - flutter_riverpod: ^3.3.1 + flutter_riverpod: ^3.4.3 # Charts fl_chart: ^1.2.0 # Calendar - table_calendar: ^3.2.0 + table_calendar: ^3.2.1 # Utilities - intl: ^0.20.2 - url_launcher: ^6.3.1 + intl: ^0.20.3 + url_launcher: ^6.3.2 # Attachment picking. Chosen over image_picker because a CRM attaches # contracts and PDFs at least as often as photos, and this covers both. - file_picker: ^11.0.3 + file_picker: ^13.1.0 # Google Fonts for distinctive typography - google_fonts: ^8.1.0 - lucide_icons_flutter: ^3.1.15 + google_fonts: ^8.2.1 + lucide_icons_flutter: ^3.1.20 # The org's calendar day for form defaults (lib/services/org_date.dart). # Publisher labs.dart.dev. The `all` database, because `latest` and @@ -67,6 +67,7 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^6.0.0 flutter_launcher_icons: "^0.14.4" + cross_file: ^0.3.5+5 flutter_launcher_icons: android: "launcher_icon" diff --git a/mobile/test/helpers/fake_platform_file.dart b/mobile/test/helpers/fake_platform_file.dart new file mode 100644 index 00000000..6c6616d1 --- /dev/null +++ b/mobile/test/helpers/fake_platform_file.dart @@ -0,0 +1,59 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:cross_file/cross_file.dart'; +import 'package:file_picker/file_picker.dart'; + +/// A picked file for tests. +/// +/// `PlatformFile` is abstract since file_picker 12, and the real subclass is +/// built from a platform-channel map. This mirrors `AndroidPlatformFile`, the +/// one that ships: [size] is what the picker reported (0 means it did not say), +/// and `length()` falls back to measuring [bytes] or the file at [path]. +/// +/// [size] can disagree with the content on purpose, so a limit test can +/// report 25 MB for a file that holds three bytes. +final class FakePlatformFile extends PlatformFile { + FakePlatformFile({ + required this.name, + this.size = 0, + String? path, + this.bytes, + }) : uri = path == null ? Uri(path: name) : Uri.file(path); + + @override + final String name; + + @override + final Uri uri; + + final int size; + final Uint8List? bytes; + + @override + XFile get xFile => bytes != null + ? XFile.fromData(bytes!, name: name) + : XFile(path ?? name, name: name); + + @override + int? lengthSync() => size > 0 ? size : null; + + @override + Future length() async { + if (size > 0) return size; + if (bytes != null) return bytes!.length; + final onDisk = path; + if (onDisk == null) return null; + try { + return await File(onDisk).length(); + } catch (_) { + return null; + } + } + + @override + Future readAsBytes() => xFile.readAsBytes(); + + @override + Stream readAsByteStream() => xFile.openRead(); +} diff --git a/mobile/test/unit/attachment_upload_test.dart b/mobile/test/unit/attachment_upload_test.dart index 29c82def..0fe1e41d 100644 --- a/mobile/test/unit/attachment_upload_test.dart +++ b/mobile/test/unit/attachment_upload_test.dart @@ -6,6 +6,7 @@ import 'package:bottle_crm/services/attachment_upload.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; +import '../helpers/fake_platform_file.dart'; /// The app could delete an attachment it had no way to create. /// @@ -53,7 +54,7 @@ void main() { /// the boundary test from writing 25 MB on every run. PlatformFile file({String name = 'quote.pdf', int size = 2048}) { final onDisk = File('${tempDir.path}/$name')..writeAsBytesSync([1, 2, 3]); - return PlatformFile(name: name, size: size, path: onDisk.path); + return FakePlatformFile(name: name, size: size, path: onDisk.path); } setUp(() { @@ -188,12 +189,34 @@ void main() { final result = await pickAndUploadAttachment( target: AttachmentTarget.task, recordId: 'task-1', - pickFile: () async => PlatformFile(name: 'ghost.txt', size: 10), + pickFile: () async => FakePlatformFile(name: 'ghost.txt', size: 10), ); expect(result.succeeded, isFalse); expect(client.sent, isNull); }); + + test( + 'a pick whose size cannot be measured is reported, not sent', + () async { + // file_picker 13 returns null from length() when the picker reported no + // size and the copy cannot be read, so the limit could not be checked. + final result = await pickAndUploadAttachment( + target: AttachmentTarget.task, + recordId: 'task-1', + pickFile: () async => FakePlatformFile( + name: 'gone.pdf', + path: '${tempDir.path}/does-not-exist.pdf', + ), + ); + + expect( + result.error, + 'That file could not be read. Try picking it again.', + ); + expect(client.sent, isNull); + }, + ); }); group('when the server refuses', () { diff --git a/mobile/test/unit/csv_import_provider_test.dart b/mobile/test/unit/csv_import_provider_test.dart index 80067a2f..4aa3c53a 100644 --- a/mobile/test/unit/csv_import_provider_test.dart +++ b/mobile/test/unit/csv_import_provider_test.dart @@ -7,6 +7,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; +import '../helpers/fake_platform_file.dart'; /// Answers each request with the next queued reply and keeps what was sent, /// so a test can check the wire as well as the state. @@ -32,7 +33,7 @@ const _csv = 'first_name,last_name\nAda,Lovelace\nAlan,Turing\n'; PlatformFile _file({String name = 'people.csv', String content = _csv}) { final bytes = Uint8List.fromList(utf8.encode(content)); - return PlatformFile(name: name, size: bytes.length, bytes: bytes); + return FakePlatformFile(name: name, size: bytes.length, bytes: bytes); } String _previewBody({ @@ -261,7 +262,7 @@ void main() { test('a file the picker reports as over 5 MB, before reading it', () async { final bytes = Uint8List.fromList(utf8.encode(_csv)); await notifier().choose( - PlatformFile(name: 'big.csv', size: 6 * 1024 * 1024, bytes: bytes), + FakePlatformFile(name: 'big.csv', size: 6 * 1024 * 1024, bytes: bytes), ); expect( @@ -275,7 +276,7 @@ void main() { test('bytes over 5 MB when the picker did not know the size', () async { final bytes = Uint8List(csvImportMaxBytes + 1); await notifier().choose( - PlatformFile(name: 'big.csv', size: 0, bytes: bytes), + FakePlatformFile(name: 'big.csv', size: 0, bytes: bytes), ); expect(stateOf().error, contains('CSV files must be 5 MB or smaller')); @@ -286,7 +287,7 @@ void main() { client.replies.add((200, _previewBody())); final bytes = Uint8List(csvImportMaxBytes); await notifier().choose( - PlatformFile(name: 'edge.csv', size: bytes.length, bytes: bytes), + FakePlatformFile(name: 'edge.csv', size: bytes.length, bytes: bytes), ); expect(stateOf().error, isNull); diff --git a/mobile/test/unit/support_ticket_test.dart b/mobile/test/unit/support_ticket_test.dart index cb6872ca..7f509f53 100644 --- a/mobile/test/unit/support_ticket_test.dart +++ b/mobile/test/unit/support_ticket_test.dart @@ -5,10 +5,10 @@ import 'package:bottle_crm/config/api_config.dart'; import 'package:bottle_crm/data/models/support_ticket.dart'; import 'package:bottle_crm/providers/support_provider.dart'; import 'package:bottle_crm/services/api_service.dart'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; +import '../helpers/fake_platform_file.dart'; Map ticketJson({ List> messages = const [], @@ -109,7 +109,7 @@ void main() { subject: 'Export failed', category: SupportCategory.technical, body: 'Attached.', - attachment: PlatformFile( + attachment: FakePlatformFile( name: 'error.txt', size: file.lengthSync(), path: file.path, diff --git a/mobile/test/widgets/csv_import_sheet_test.dart b/mobile/test/widgets/csv_import_sheet_test.dart index 1484270a..b58658f1 100644 --- a/mobile/test/widgets/csv_import_sheet_test.dart +++ b/mobile/test/widgets/csv_import_sheet_test.dart @@ -4,11 +4,11 @@ import 'dart:typed_data'; import 'package:bottle_crm/providers/csv_import_provider.dart'; import 'package:bottle_crm/services/api_service.dart'; import 'package:bottle_crm/widgets/forms/csv_import_sheet.dart'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; +import '../helpers/fake_platform_file.dart'; class _QueueClient extends http.BaseClient { final List<(int, String)> replies = []; @@ -96,7 +96,7 @@ void main() { context, target, onImported: () => refreshed++, - pickFile: () async => PlatformFile( + pickFile: () async => FakePlatformFile( name: 'people.csv', size: bytes.length, bytes: bytes,