From 14814c3450f65a20590be8dd3d4336ecdf874f97 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 5 May 2023 14:58:46 -0400 Subject: [PATCH 1/3] Original combo PR as reviewed --- .../file_selector/file_selector/CHANGELOG.md | 3 +- .../lib/readme_standalone_excerpts.dart | 7 +- .../example/lib/save_text_page.dart | 6 +- .../file_selector/example/pubspec.yaml | 5 + .../file_selector/lib/file_selector.dart | 47 +++- .../file_selector/file_selector/pubspec.yaml | 7 +- .../test/file_selector_test.dart | 111 +++++++- .../file_selector_ios/CHANGELOG.md | 4 + .../file_selector_ios/example/pubspec.yaml | 5 + .../file_selector_ios/pubspec.yaml | 7 +- .../file_selector_linux/CHANGELOG.md | 3 +- .../example/lib/save_text_page.dart | 11 +- .../file_selector_linux/example/pubspec.yaml | 5 + .../lib/file_selector_linux.dart | 26 +- .../file_selector_linux/pubspec.yaml | 7 +- .../test/file_selector_linux_test.dart | 131 +++++++++- .../file_selector_macos/CHANGELOG.md | 3 +- .../example/lib/save_text_page.dart | 9 +- .../file_selector_macos/example/pubspec.yaml | 5 + .../lib/file_selector_macos.dart | 24 +- .../file_selector_macos/pubspec.yaml | 7 +- .../test/file_selector_macos_test.dart | 122 ++++++++- .../CHANGELOG.md | 4 + .../file_selector_interface.dart | 49 +++- .../lib/src/types/file_dialog_options.dart | 29 +++ .../lib/src/types/file_save_location.dart | 26 ++ .../lib/src/types/types.dart | 4 +- .../{x_type_group => }/x_type_group.dart | 0 .../pubspec.yaml | 2 +- ...file_selector_platform_interface_test.dart | 28 ++- .../file_selector_web/CHANGELOG.md | 3 +- .../file_selector_web/example/pubspec.yaml | 5 + .../lib/file_selector_web.dart | 10 + .../file_selector_web/pubspec.yaml | 7 +- .../file_selector_windows/CHANGELOG.md | 4 + .../example/lib/save_text_page.dart | 37 ++- .../example/pubspec.yaml | 5 + .../example/windows/runner/Runner.rc | 10 +- .../lib/file_selector_windows.dart | 46 +++- .../lib/src/messages.g.dart | 55 +++- .../pigeons/messages.dart | 23 +- .../file_selector_windows/pubspec.yaml | 9 +- .../test/file_selector_windows_test.dart | 134 +++++++++- .../file_selector_windows_test.mocks.dart | 58 ++++- .../test/test_api.g.dart | 37 ++- .../windows/file_dialog_controller.cpp | 4 + .../windows/file_dialog_controller.h | 1 + .../windows/file_selector_plugin.cpp | 31 ++- .../windows/file_selector_plugin.h | 4 +- .../windows/messages.g.cpp | 137 +++++++--- .../windows/messages.g.h | 67 +++-- .../test/file_selector_plugin_test.cpp | 238 +++++++++++------- .../test/test_file_dialog_controller.cpp | 7 + .../test/test_file_dialog_controller.h | 1 + 54 files changed, 1326 insertions(+), 304 deletions(-) create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/file_dialog_options.dart create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location.dart rename packages/file_selector/file_selector_platform_interface/lib/src/types/{x_type_group => }/x_type_group.dart (100%) diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index 98d02784d7c..5215787e070 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.9.4 +* Adds `getSaveLocation` and deprecates `getSavePath`. * Updates minimum supported macOS version to 10.14. * Updates minimum supported SDK version to Flutter 3.3/Dart 2.18. diff --git a/packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart b/packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart index e12be751716..cc0a051e218 100644 --- a/packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart +++ b/packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart @@ -39,8 +39,9 @@ class _MyAppState extends State { Future saveFile() async { // #docregion Save const String fileName = 'suggested_name.txt'; - final String? path = await getSavePath(suggestedName: fileName); - if (path == null) { + final FileSaveLocation? result = + await getSaveLocation(suggestedName: fileName); + if (result == null) { // Operation was canceled by the user. return; } @@ -49,7 +50,7 @@ class _MyAppState extends State { const String mimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: mimeType, name: fileName); - await textFile.saveTo(path); + await textFile.saveTo(result.path); // #enddocregion Save } diff --git a/packages/file_selector/file_selector/example/lib/save_text_page.dart b/packages/file_selector/file_selector/example/lib/save_text_page.dart index 751b91c7937..e782530914e 100644 --- a/packages/file_selector/file_selector/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector/example/lib/save_text_page.dart @@ -24,11 +24,11 @@ class SaveTextPage extends StatelessWidget { // file will be saved. In most cases, this parameter should not be provided. final String initialDirectory = (await getApplicationDocumentsDirectory()).path; - final String? path = await getSavePath( + final FileSaveLocation? result = await getSaveLocation( initialDirectory: initialDirectory, suggestedName: fileName, ); - if (path == null) { + if (result == null) { // Operation was canceled by the user. return; } @@ -39,7 +39,7 @@ class SaveTextPage extends StatelessWidget { final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); - await textFile.saveTo(path); + await textFile.saveTo(result.path); } @override diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml index f4de9cbc743..d6c8ad37d39 100644 --- a/packages/file_selector/file_selector/example/pubspec.yaml +++ b/packages/file_selector/file_selector/example/pubspec.yaml @@ -27,3 +27,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index c2249565c9e..e1739eda0c8 100644 --- a/packages/file_selector/file_selector/lib/file_selector.dart +++ b/packages/file_selector/file_selector/lib/file_selector.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; export 'package:file_selector_platform_interface/file_selector_platform_interface.dart' - show XFile, XTypeGroup; + show FileSaveLocation, XFile, XTypeGroup; /// Opens a file selection dialog and returns the path chosen by the user. /// @@ -92,17 +92,54 @@ Future> openFiles({ /// When not provided, the default OS label is used (for example, "Save"). /// /// Returns `null` if the user cancels the operation. +@Deprecated('Use getSaveLocation instead') Future getSavePath({ List acceptedTypeGroups = const [], String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async { - return FileSelectorPlatform.instance.getSavePath( + return (await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText)) + ?.path; +} + +/// Opens a save dialog and returns the target path chosen by the user. +/// +/// [acceptedTypeGroups] is a list of file type groups that can be selected in +/// the dialog. How this is displayed depends on the pltaform, for example: +/// - On Windows and Linux, each group will be an entry in a list of filter +/// options. +/// - On macOS, the union of all types allowed by all of the groups will be +/// allowed. +/// Throws an [ArgumentError] if any type groups do not include filters +/// supported by the current platform. +/// +/// [initialDirectory] is the full path to the directory that will be displayed +/// when the dialog is opened. When not provided, the platform will pick an +/// initial location. +/// +/// [suggestedName] is initial value of file name. +/// +/// [confirmButtonText] is the text in the confirmation button of the dialog. +/// When not provided, the default OS label is used (for example, "Save"). +/// +/// Returns `null` if the user cancels the operation. +Future getSaveLocation({ + List acceptedTypeGroups = const [], + String? initialDirectory, + String? suggestedName, + String? confirmButtonText, +}) async { + return FileSelectorPlatform.instance.getSaveLocation( acceptedTypeGroups: acceptedTypeGroups, - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText); + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText)); } /// Opens a directory selection dialog and returns the path chosen by the user. diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index 00f11e1af88..03764de006a 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for opening and saving files, or selecting directories, using native file selection UI. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.3 +version: 0.9.4 environment: sdk: ">=2.18.0 <4.0.0" @@ -38,3 +38,8 @@ dev_dependencies: sdk: flutter plugin_platform_interface: ^2.0.0 test: ^1.16.3 + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector/test/file_selector_test.dart b/packages/file_selector/file_selector/test/file_selector_test.dart index cdcebe07828..a9d684c0dff 100644 --- a/packages/file_selector/file_selector/test/file_selector_test.dart +++ b/packages/file_selector/file_selector/test/file_selector_test.dart @@ -144,7 +144,80 @@ void main() { }); }); - group('getSavePath', () { + group('getSaveLocation', () { + const String expectedSavePath = '/example/path'; + + test('works', () async { + const int expectedActiveFilter = 1; + fakePlatformImplementation + ..setExpectations( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + acceptedTypeGroups: acceptedTypeGroups, + suggestedName: suggestedName) + ..setPathsResponse([expectedSavePath], + activeFilter: expectedActiveFilter); + + final FileSaveLocation? location = await getSaveLocation( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + acceptedTypeGroups: acceptedTypeGroups, + suggestedName: suggestedName, + ); + + expect(location?.path, expectedSavePath); + expect(location?.activeFilter, acceptedTypeGroups[expectedActiveFilter]); + }); + + test('works with no arguments', () async { + fakePlatformImplementation.setPathsResponse([expectedSavePath]); + + final FileSaveLocation? location = await getSaveLocation(); + expect(location?.path, expectedSavePath); + }); + + test('sets the initial directory', () async { + fakePlatformImplementation + ..setExpectations(initialDirectory: initialDirectory) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocation? location = + await getSaveLocation(initialDirectory: initialDirectory); + expect(location?.path, expectedSavePath); + }); + + test('sets the button confirmation label', () async { + fakePlatformImplementation + ..setExpectations(confirmButtonText: confirmButtonText) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocation? location = + await getSaveLocation(confirmButtonText: confirmButtonText); + expect(location?.path, expectedSavePath); + }); + + test('sets the accepted type groups', () async { + fakePlatformImplementation + ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocation? location = + await getSaveLocation(acceptedTypeGroups: acceptedTypeGroups); + expect(location?.path, expectedSavePath); + }); + + test('sets the suggested name', () async { + fakePlatformImplementation + ..setExpectations(suggestedName: suggestedName) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocation? location = + await getSaveLocation(suggestedName: suggestedName); + expect(location?.path, expectedSavePath); + }); + }); + + group('getSavePath (deprecated)', () { const String expectedSavePath = '/example/path'; test('works', () async { @@ -321,6 +394,7 @@ class FakeFileSelector extends Fake // Return values. List? files; List? paths; + int? activeFilter; void setExpectations({ List acceptedTypeGroups = const [], @@ -339,9 +413,9 @@ class FakeFileSelector extends Fake this.files = files; } - // ignore: use_setters_to_change_properties - void setPathsResponse(List paths) { + void setPathsResponse(List paths, {int? activeFilter}) { this.paths = paths; + this.activeFilter = activeFilter; } @override @@ -374,12 +448,35 @@ class FakeFileSelector extends Fake String? initialDirectory, String? suggestedName, String? confirmButtonText, + }) async { + final FileSaveLocation? result = await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText, + ), + ); + return result?.path; + } + + @override + Future getSaveLocation({ + List? acceptedTypeGroups, + SaveDialogOptions options = const SaveDialogOptions(), }) async { expect(acceptedTypeGroups, this.acceptedTypeGroups); - expect(initialDirectory, this.initialDirectory); - expect(suggestedName, this.suggestedName); - expect(confirmButtonText, this.confirmButtonText); - return paths?[0]; + expect(options.initialDirectory, initialDirectory); + expect(options.suggestedName, suggestedName); + expect(options.confirmButtonText, confirmButtonText); + final String? path = paths?[0]; + final int? activeFilterIndex = activeFilter; + return path == null + ? null + : FileSaveLocation(path, + activeFilter: activeFilterIndex == null + ? null + : acceptedTypeGroups?[activeFilterIndex]); } @override diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index 8a48d5b089d..0c6d5658d31 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.2 + +* Adds `getSaveLocation` and deprecates `getSavePath`. + ## 0.5.1+4 * Updates references to the deprecated `macUTIs`. diff --git a/packages/file_selector/file_selector_ios/example/pubspec.yaml b/packages/file_selector/file_selector_ios/example/pubspec.yaml index 44642b6d912..aace887472b 100644 --- a/packages/file_selector/file_selector_ios/example/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/example/pubspec.yaml @@ -30,3 +30,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index dd3f7dfca53..a3b3d04160f 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_ios description: iOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.1+4 +version: 0.5.2 environment: sdk: ">=2.18.0 <4.0.0" @@ -27,3 +27,8 @@ dev_dependencies: sdk: flutter mockito: 5.4.1 pigeon: ^9.2.4 + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index 88886301d3a..5c9dc1dbcd8 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.9.2 +* Adds `getSaveLocation` and deprecates `getSavePath`. * Updates minimum supported SDK version to Flutter 3.3/Dart 2.18. ## 0.9.1+3 diff --git a/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart index 174c490c5fc..bfd83cfd239 100644 --- a/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart @@ -17,11 +17,12 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final String? path = await FileSelectorPlatform.instance.getSavePath( - // Operation was canceled by the user. - suggestedName: fileName, + final FileSaveLocation? result = + await FileSelectorPlatform.instance.getSaveLocation( + options: SaveDialogOptions(suggestedName: fileName), ); - if (path == null) { + // Operation was canceled by the user. + if (result == null) { return; } final String text = _contentController.text; @@ -29,7 +30,7 @@ class SaveTextPage extends StatelessWidget { const String fileMimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); - await textFile.saveTo(path); + await textFile.saveTo(result.path); } @override diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index 1596eb3f769..ed7db91f1bd 100644 --- a/packages/file_selector/file_selector_linux/example/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml @@ -20,3 +20,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart index b8e3df6a11b..5ae02ca3ade 100644 --- a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart +++ b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart @@ -82,19 +82,37 @@ class FileSelectorLinux extends FileSelectorPlatform { String? initialDirectory, String? suggestedName, String? confirmButtonText, + }) async { + return (await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText, + ))) + ?.path; + } + + @override + Future getSaveLocation({ + List? acceptedTypeGroups, + SaveDialogOptions options = const SaveDialogOptions(), }) async { final List> serializedTypeGroups = _serializeTypeGroups(acceptedTypeGroups); - return _channel.invokeMethod( + // TODO(stuartmorgan): Add the selected type group here and return it. See + // https://github.com/flutter/flutter/issues/107093 + final String? path = await _channel.invokeMethod( _getSavePathMethod, { if (serializedTypeGroups.isNotEmpty) _acceptedTypeGroupsKey: serializedTypeGroups, - _initialDirectoryKey: initialDirectory, - _suggestedNameKey: suggestedName, - _confirmButtonTextKey: confirmButtonText, + _initialDirectoryKey: options.initialDirectory, + _suggestedNameKey: options.suggestedName, + _confirmButtonTextKey: options.confirmButtonText, }, ); + return path == null ? null : FileSaveLocation(path); } @override diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index 238e0aef741..dd24a8474d8 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_linux description: Liunx implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.1+3 +version: 0.9.2 environment: sdk: ">=2.18.0 <4.0.0" @@ -25,3 +25,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart index 5127d28b7f6..f6b2cc66fe5 100644 --- a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart +++ b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart @@ -32,7 +32,7 @@ void main() { expect(FileSelectorPlatform.instance, isA()); }); - group('#openFile', () { + group('openFile', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -135,7 +135,7 @@ void main() { }); }); - group('#openFiles', () { + group('openFiles', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -209,7 +209,7 @@ void main() { ); await expectLater( - plugin.openFile(acceptedTypeGroups: [group]), + plugin.openFiles(acceptedTypeGroups: [group]), throwsArgumentError); }); @@ -218,7 +218,7 @@ void main() { label: 'any', ); - await plugin.openFile(acceptedTypeGroups: [group]); + await plugin.openFiles(acceptedTypeGroups: [group]); expectMethodCall( log, @@ -232,13 +232,120 @@ void main() { ], 'initialDirectory': null, 'confirmButtonText': null, - 'multiple': false, + 'multiple': true, + }, + ); + }); + }); + + group('getSaveLocation', () { + test('passes the accepted type groups correctly', () async { + const XTypeGroup group = XTypeGroup( + label: 'text', + extensions: ['txt'], + mimeTypes: ['text/plain'], + ); + + const XTypeGroup groupTwo = XTypeGroup( + label: 'image', + extensions: ['jpg'], + mimeTypes: ['image/jpg'], + ); + + await plugin + .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); + + expectMethodCall( + log, + 'getSavePath', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'text', + 'extensions': ['*.txt'], + 'mimeTypes': ['text/plain'], + }, + { + 'label': 'image', + 'extensions': ['*.jpg'], + 'mimeTypes': ['image/jpg'], + }, + ], + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': null, + }, + ); + }); + + test('passes initialDirectory correctly', () async { + await plugin.getSaveLocation( + options: + const SaveDialogOptions(initialDirectory: '/example/directory')); + + expectMethodCall( + log, + 'getSavePath', + arguments: { + 'initialDirectory': '/example/directory', + 'suggestedName': null, + 'confirmButtonText': null, + }, + ); + }); + + test('passes confirmButtonText correctly', () async { + await plugin.getSaveLocation( + options: const SaveDialogOptions(confirmButtonText: 'Open File')); + + expectMethodCall( + log, + 'getSavePath', + arguments: { + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': 'Open File', + }, + ); + }); + + test('throws for a type group that does not support Linux', () async { + const XTypeGroup group = XTypeGroup( + label: 'images', + webWildCards: ['images/*'], + ); + + await expectLater( + plugin.getSaveLocation(acceptedTypeGroups: [group]), + throwsArgumentError); + }); + + test('passes a wildcard group correctly', () async { + const XTypeGroup group = XTypeGroup( + label: 'any', + ); + + await plugin.getSaveLocation(acceptedTypeGroups: [group]); + + expectMethodCall( + log, + 'getSavePath', + arguments: { + 'acceptedTypeGroups': >[ + { + 'label': 'any', + 'extensions': ['*'], + }, + ], + 'initialDirectory': null, + 'suggestedName': null, + 'confirmButtonText': null, }, ); }); }); - group('#getSavePath', () { + group('getSavePath (deprecated)', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -313,7 +420,7 @@ void main() { ); await expectLater( - plugin.openFile(acceptedTypeGroups: [group]), + plugin.getSavePath(acceptedTypeGroups: [group]), throwsArgumentError); }); @@ -322,11 +429,11 @@ void main() { label: 'any', ); - await plugin.openFile(acceptedTypeGroups: [group]); + await plugin.getSavePath(acceptedTypeGroups: [group]); expectMethodCall( log, - 'openFile', + 'getSavePath', arguments: { 'acceptedTypeGroups': >[ { @@ -335,14 +442,14 @@ void main() { }, ], 'initialDirectory': null, + 'suggestedName': null, 'confirmButtonText': null, - 'multiple': false, }, ); }); }); - group('#getDirectoryPath', () { + group('getDirectoryPath', () { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPath(initialDirectory: '/example/directory'); @@ -369,7 +476,7 @@ void main() { }); }); - group('#getDirectoryPaths', () { + group('getDirectoryPaths', () { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPaths(initialDirectory: '/example/directory'); diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index e697974a390..7a197838bb9 100644 --- a/packages/file_selector/file_selector_macos/CHANGELOG.md +++ b/packages/file_selector/file_selector_macos/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.9.3 +* Adds `getSaveLocation` and deprecates `getSavePath`. * Updates minimum supported macOS version to 10.14. ## 0.9.2 diff --git a/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart index 79d9c83c831..6208e16ef7d 100644 --- a/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart @@ -17,10 +17,11 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final String? path = await FileSelectorPlatform.instance.getSavePath( - suggestedName: fileName, + final FileSaveLocation? result = + await FileSelectorPlatform.instance.getSaveLocation( + options: SaveDialogOptions(suggestedName: fileName), ); - if (path == null) { + if (result == null) { // Operation was canceled by the user. return; } @@ -29,7 +30,7 @@ class SaveTextPage extends StatelessWidget { const String fileMimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); - await textFile.saveTo(path); + await textFile.saveTo(result.path); } @override diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index 9e2d831f75c..81e8801c07b 100644 --- a/packages/file_selector/file_selector_macos/example/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/example/pubspec.yaml @@ -25,3 +25,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart index 293c1e20e77..a26b0ff11a1 100644 --- a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart +++ b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart @@ -60,12 +60,28 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? suggestedName, String? confirmButtonText, }) async { - return _hostApi.displaySavePanel(SavePanelOptions( + return (await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText, + ))) + ?.path; + } + + @override + Future getSaveLocation({ + List? acceptedTypeGroups, + SaveDialogOptions options = const SaveDialogOptions(), + }) async { + final String? path = await _hostApi.displaySavePanel(SavePanelOptions( allowedFileTypes: _allowedTypesFromTypeGroups(acceptedTypeGroups), - directoryPath: initialDirectory, - nameFieldStringValue: suggestedName, - prompt: confirmButtonText, + directoryPath: options.initialDirectory, + nameFieldStringValue: options.suggestedName, + prompt: options.confirmButtonText, )); + return path == null ? null : FileSaveLocation(path); } @override diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index 731a5cc8eed..c56482c6e14 100644 --- a/packages/file_selector/file_selector_macos/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_macos description: macOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.2 +version: 0.9.3 environment: sdk: ">=2.18.0 <4.0.0" @@ -28,3 +28,8 @@ dev_dependencies: sdk: flutter mockito: 5.4.1 pigeon: ^9.2.4 + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart index 6450e6f3b0a..c7268a6be89 100644 --- a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart +++ b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart @@ -227,7 +227,7 @@ void main() { }); }); - group('getSavePath', () { + group('getSavePath (deprecated)', () { test('works as expected with no arguments', () async { when(mockApi.displaySavePanel(any)).thenAnswer((_) async => 'foo'); @@ -344,6 +344,126 @@ void main() { }); }); + group('getSaveLocation', () { + test('works as expected with no arguments', () async { + when(mockApi.displaySavePanel(any)).thenAnswer((_) async => 'foo'); + + final FileSaveLocation? location = await plugin.getSaveLocation(); + + expect(location?.path, 'foo'); + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.allowedFileTypes, null); + expect(options.directoryPath, null); + expect(options.nameFieldStringValue, null); + expect(options.prompt, null); + }); + + test('handles cancel', () async { + when(mockApi.displaySavePanel(any)).thenAnswer((_) async => null); + + final FileSaveLocation? location = await plugin.getSaveLocation(); + + expect(location, null); + }); + + test('passes the accepted type groups correctly', () async { + const XTypeGroup group = XTypeGroup( + label: 'text', + extensions: ['txt'], + mimeTypes: ['text/plain'], + uniformTypeIdentifiers: ['public.text'], + ); + + const XTypeGroup groupTwo = XTypeGroup( + label: 'image', + extensions: ['jpg'], + mimeTypes: ['image/jpg'], + uniformTypeIdentifiers: ['public.image'], + webWildCards: ['image/*']); + + await plugin + .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); + + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.allowedFileTypes!.extensions, ['txt', 'jpg']); + expect(options.allowedFileTypes!.mimeTypes, + ['text/plain', 'image/jpg']); + expect(options.allowedFileTypes!.utis, + ['public.text', 'public.image']); + }); + + test('passes initialDirectory correctly', () async { + await plugin.getSaveLocation( + options: + const SaveDialogOptions(initialDirectory: '/example/directory')); + + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.directoryPath, '/example/directory'); + }); + + test('passes confirmButtonText correctly', () async { + await plugin.getSaveLocation( + options: const SaveDialogOptions(confirmButtonText: 'Open File')); + + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.prompt, 'Open File'); + }); + + test('throws for a type group that does not support macOS', () async { + const XTypeGroup group = XTypeGroup( + label: 'images', + webWildCards: ['images/*'], + ); + + await expectLater( + plugin.getSaveLocation(acceptedTypeGroups: [group]), + throwsArgumentError); + }); + + test('allows a wildcard group', () async { + const XTypeGroup group = XTypeGroup( + label: 'text', + ); + + await expectLater( + plugin.getSaveLocation(acceptedTypeGroups: [group]), + completes); + }); + + test('ignores all type groups if any of them is a wildcard', () async { + await plugin.getSaveLocation(acceptedTypeGroups: [ + const XTypeGroup( + label: 'text', + extensions: ['txt'], + mimeTypes: ['text/plain'], + uniformTypeIdentifiers: ['public.text'], + ), + const XTypeGroup( + label: 'image', + extensions: ['jpg'], + mimeTypes: ['image/jpg'], + uniformTypeIdentifiers: ['public.image'], + ), + const XTypeGroup( + label: 'any', + ), + ]); + + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.allowedFileTypes, null); + }); + }); + group('getDirectoryPath', () { test('works as expected with no arguments', () async { when(mockApi.displayOpenPanel(any)) diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md index ba4ed2cfe11..f33890efe58 100644 --- a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md +++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.6.0 + +* Adds `getSaveLocation` and deprecates `getSavePath`. + ## 2.5.1 * Adds compatibility with `http` 1.0. diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart index ad4fe617e44..7fe40c72f3a 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/platform_interface/file_selector_interface.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import '../../file_selector_platform_interface.dart'; @@ -37,7 +35,10 @@ abstract class FileSelectorPlatform extends PlatformInterface { } /// Opens a file dialog for loading files and returns a file path. - /// Returns `null` if user cancels the operation. + /// + /// Returns `null` if the user cancels the operation. + // TODO(stuartmorgan): Switch to FileDialogOptions if we ever need to + // duplicate this to add a parameter. Future openFile({ List? acceptedTypeGroups, String? initialDirectory, @@ -47,6 +48,10 @@ abstract class FileSelectorPlatform extends PlatformInterface { } /// Opens a file dialog for loading files and returns a list of file paths. + /// + /// Returns an empty list if the user cancels the operation. + // TODO(stuartmorgan): Switch to FileDialogOptions if we ever need to + // duplicate this to add a parameter. Future> openFiles({ List? acceptedTypeGroups, String? initialDirectory, @@ -55,8 +60,13 @@ abstract class FileSelectorPlatform extends PlatformInterface { throw UnimplementedError('openFiles() has not been implemented.'); } - /// Opens a file dialog for saving files and returns a file path at which to save. - /// Returns `null` if user cancels the operation. + /// Opens a file dialog for saving files and returns a file path at which to + /// save. + /// + /// Returns `null` if the user cancels the operation. + // TODO(stuartmorgan): Switch to FileDialogOptions if we ever need to + // duplicate this to add a parameter. + @Deprecated('Use getSaveLocation instead') Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, @@ -66,8 +76,28 @@ abstract class FileSelectorPlatform extends PlatformInterface { throw UnimplementedError('getSavePath() has not been implemented.'); } + /// Opens a file dialog for saving files and returns a file location at which + /// to save. + /// + /// Returns `null` if the user cancels the operation. + Future getSaveLocation({ + List? acceptedTypeGroups, + SaveDialogOptions options = const SaveDialogOptions(), + }) async { + final String? path = await getSavePath( + acceptedTypeGroups: acceptedTypeGroups, + initialDirectory: options.initialDirectory, + suggestedName: options.suggestedName, + confirmButtonText: options.confirmButtonText, + ); + return path == null ? null : FileSaveLocation(path); + } + /// Opens a file dialog for loading directories and returns a directory path. - /// Returns `null` if user cancels the operation. + /// + /// Returns `null` if the user cancels the operation. + // TODO(stuartmorgan): Switch to FileDialogOptions if we ever need to + // duplicate this to add a parameter. Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, @@ -75,7 +105,12 @@ abstract class FileSelectorPlatform extends PlatformInterface { throw UnimplementedError('getDirectoryPath() has not been implemented.'); } - /// Opens a file dialog for loading directories and returns multiple directory paths. + /// Opens a file dialog for loading directories and returns multiple directory + /// paths. + /// + /// Returns an empty list if the user cancels the operation. + // TODO(stuartmorgan): Switch to FileDialogOptions if we ever need to + // duplicate this to add a parameter. Future> getDirectoryPaths({ String? initialDirectory, String? confirmButtonText, diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/file_dialog_options.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_dialog_options.dart new file mode 100644 index 00000000000..5797e356b27 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_dialog_options.dart @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show immutable; + +/// Configuration options for any file selector dialog. +@immutable +class FileDialogOptions { + /// Creates a new options set with the given settings. + const FileDialogOptions({this.initialDirectory, this.confirmButtonText}); + + /// The initial directory the dialog should open with. + final String? initialDirectory; + + /// The label for the button that confirms selection. + final String? confirmButtonText; +} + +/// Configuration options for a save dialog. +@immutable +class SaveDialogOptions extends FileDialogOptions { + /// Creates a new options set with the given settings. + const SaveDialogOptions( + {super.initialDirectory, super.confirmButtonText, this.suggestedName}); + + /// The suggested name of the file to save or open. + final String? suggestedName; +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location.dart new file mode 100644 index 00000000000..4670e33fc53 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location.dart @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show immutable; + +import 'x_type_group.dart'; + +export 'x_type_group.dart'; + +/// The response from a save dialog. +@immutable +class FileSaveLocation { + /// Creates a result with the given [path] and optional other dialog state. + const FileSaveLocation(this.path, {this.activeFilter}); + + /// The path to save to. + final String path; + + /// The currently active filter group, if any. + /// + /// This is null on platforms that do not support user-selectable filter + /// groups in save dialogs (for example, macOS), or when no filter groups + /// were provided when showing the dialog. + final XTypeGroup? activeFilter; +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart index 9caee27c3e3..c23ff97a037 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/types.dart @@ -3,4 +3,6 @@ // found in the LICENSE file. export 'package:cross_file/cross_file.dart'; -export 'x_type_group/x_type_group.dart'; +export 'file_dialog_options.dart'; +export 'file_save_location.dart'; +export 'x_type_group.dart'; diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group.dart similarity index 100% rename from packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group/x_type_group.dart rename to packages/file_selector/file_selector_platform_interface/lib/src/types/x_type_group.dart diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index b7ce75d960f..a947c59eb39 100644 --- a/packages/file_selector/file_selector_platform_interface/pubspec.yaml +++ b/packages/file_selector/file_selector_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/file_selector issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.5.1 +version: 2.6.0 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart index 18334e885fc..7592c851227 100644 --- a/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart @@ -10,7 +10,7 @@ void main() { // Store the initial instance before any tests change it. final FileSelectorPlatform initialInstance = FileSelectorPlatform.instance; - group('$FileSelectorPlatform', () { + group('FileSelectorPlatform', () { test('$MethodChannelFileSelector() is the default instance', () { expect(initialInstance, isInstanceOf()); }); @@ -20,7 +20,7 @@ void main() { }); }); - group('#GetDirectoryPaths', () { + group('getDirectoryPaths', () { test('Should throw unimplemented exception', () async { final FileSelectorPlatform fileSelector = ExtendsFileSelectorPlatform(); @@ -29,6 +29,30 @@ void main() { }, throwsA(isA())); }); }); + + test('getSaveLocation falls back to getSavePath by default', () async { + final FileSelectorPlatform fileSelector = + OldFileSelectorPlatformImplementation(); + + final FileSaveLocation? result = await fileSelector.getSaveLocation(); + + expect(result?.path, OldFileSelectorPlatformImplementation.savePath); + expect(result?.activeFilter, null); + }); } class ExtendsFileSelectorPlatform extends FileSelectorPlatform {} + +class OldFileSelectorPlatformImplementation extends FileSelectorPlatform { + static const String savePath = '/a/path'; + // Only implement the deprecated getSavePath. + @override + Future getSavePath({ + List? acceptedTypeGroups, + String? initialDirectory, + String? suggestedName, + String? confirmButtonText, + }) async { + return savePath; + } +} diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 43ad4962f04..073f1c20ed8 100644 --- a/packages/file_selector/file_selector_web/CHANGELOG.md +++ b/packages/file_selector/file_selector_web/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.9.1 +* Adds `getSaveLocation` and deprecates `getSavePath`. * Updates minimum supported SDK version to Flutter 3.3/Dart 2.18. ## 0.9.0+4 diff --git a/packages/file_selector/file_selector_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml index bc8b984e367..9a7c108a0e4 100644 --- a/packages/file_selector/file_selector_web/example/pubspec.yaml +++ b/packages/file_selector/file_selector_web/example/pubspec.yaml @@ -19,3 +19,8 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_web/lib/file_selector_web.dart b/packages/file_selector/file_selector_web/lib/file_selector_web.dart index 748bb3aa0df..2380e274c46 100644 --- a/packages/file_selector/file_selector_web/lib/file_selector_web.dart +++ b/packages/file_selector/file_selector_web/lib/file_selector_web.dart @@ -60,6 +60,16 @@ class FileSelectorWeb extends FileSelectorPlatform { }) async => ''; + @override + Future getSaveLocation({ + List? acceptedTypeGroups, + SaveDialogOptions options = const SaveDialogOptions(), + }) async { + // This is intended to be passed to XFile, which ignores the path, so + // provide a non-null dummy value. + return const FileSaveLocation(''); + } + @override Future getDirectoryPath({ String? initialDirectory, diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 8a601cbb366..673c5a94c0c 100644 --- a/packages/file_selector/file_selector_web/pubspec.yaml +++ b/packages/file_selector/file_selector_web/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_web description: Web platform implementation of file_selector repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.0+4 +version: 0.9.1 environment: sdk: ">=2.18.0 <4.0.0" @@ -26,3 +26,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index 35163430e92..b747a45723a 100644 --- a/packages/file_selector/file_selector_windows/CHANGELOG.md +++ b/packages/file_selector/file_selector_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.3 + +* Adds `getSaveLocation` and deprecates `getSavePath`. + ## 0.9.2 * Adds `getDirectoryPaths` implementation. diff --git a/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart index 174c490c5fc..c92aff05569 100644 --- a/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io'; import 'dart:typed_data'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/material.dart'; @@ -17,19 +18,39 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final String? path = await FileSelectorPlatform.instance.getSavePath( - // Operation was canceled by the user. - suggestedName: fileName, + final FileSaveLocation? result = + await FileSelectorPlatform.instance.getSaveLocation( + options: SaveDialogOptions(suggestedName: fileName), + acceptedTypeGroups: const [ + XTypeGroup( + label: 'Plain text', + extensions: ['txt'], + ), + XTypeGroup( + label: 'JSON', + extensions: ['json'], + ), + ], ); - if (path == null) { + // Operation was canceled by the user. + if (result == null) { return; } + String path = result.path; + // Append an extension based on the selected type group if the user didn't + // include one. + if (!path.split(Platform.pathSeparator).last.contains('.')) { + final XTypeGroup? activeGroup = result.activeFilter; + if (activeGroup != null) { + // The group is one of the groups passed in above, each of which has + // exactly one `extensions` entry. + path = '$path.${activeGroup.extensions!.first}'; + } + } final String text = _contentController.text; final Uint8List fileData = Uint8List.fromList(text.codeUnits); - const String fileMimeType = 'text/plain'; - final XFile textFile = - XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); - await textFile.saveTo(path); + final XFile textFile = XFile.fromData(fileData, name: fileName); + await textFile.saveTo(result.path); } @override diff --git a/packages/file_selector/file_selector_windows/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml index a22b6dc19aa..0492eca6921 100644 --- a/packages/file_selector/file_selector_windows/example/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/example/pubspec.yaml @@ -25,3 +25,8 @@ dev_dependencies: flutter: uses-material-design: true + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc b/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc index 51812dcd487..e5666e0223f 100644 --- a/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc +++ b/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else -#define VERSION_AS_NUMBER 1,0,0 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart b/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart index 0e935ccdb9d..677811afb89 100644 --- a/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart +++ b/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart @@ -21,7 +21,7 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final List paths = await _hostApi.showOpenDialog( + final FileDialogResult result = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: false, selectFolders: false, @@ -29,7 +29,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return paths.isEmpty ? null : XFile(paths.first!); + return result.paths.isEmpty ? null : XFile(result.paths.first!); } @override @@ -38,7 +38,7 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final List paths = await _hostApi.showOpenDialog( + final FileDialogResult result = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: true, selectFolders: false, @@ -46,7 +46,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return paths.map((String? path) => XFile(path!)).toList(); + return result.paths.map((String? path) => XFile(path!)).toList(); } @override @@ -56,16 +56,36 @@ class FileSelectorWindows extends FileSelectorPlatform { String? suggestedName, String? confirmButtonText, }) async { - final List paths = await _hostApi.showSaveDialog( + return (await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText, + ))) + ?.path; + } + + @override + Future getSaveLocation({ + List? acceptedTypeGroups, + SaveDialogOptions options = const SaveDialogOptions(), + }) async { + final FileDialogResult result = await _hostApi.showSaveDialog( SelectionOptions( allowMultiple: false, selectFolders: false, allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups), ), - initialDirectory, - suggestedName, - confirmButtonText); - return paths.isEmpty ? null : paths.first!; + options.initialDirectory, + options.suggestedName, + options.confirmButtonText); + final int? groupIndex = result.typeGroupIndex; + return result.paths.isEmpty + ? null + : FileSaveLocation(result.paths.first!, + activeFilter: + groupIndex == null ? null : acceptedTypeGroups?[groupIndex]); } @override @@ -73,7 +93,7 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final List paths = await _hostApi.showOpenDialog( + final FileDialogResult result = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: false, selectFolders: true, @@ -81,7 +101,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return paths.isEmpty ? null : paths.first!; + return result.paths.isEmpty ? null : result.paths.first!; } @override @@ -89,7 +109,7 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final List paths = await _hostApi.showOpenDialog( + final FileDialogResult result = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: true, selectFolders: true, @@ -97,7 +117,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return paths.isEmpty ? [] : List.from(paths); + return result.paths.isEmpty ? [] : List.from(result.paths); } } diff --git a/packages/file_selector/file_selector_windows/lib/src/messages.g.dart b/packages/file_selector/file_selector_windows/lib/src/messages.g.dart index a61076b97b3..b5b4a794388 100644 --- a/packages/file_selector/file_selector_windows/lib/src/messages.g.dart +++ b/packages/file_selector/file_selector_windows/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v9.1.1), do not edit directly. +// Autogenerated from Pigeon (v10.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import @@ -68,16 +68,53 @@ class SelectionOptions { } } +/// The result from an open or save dialog. +class FileDialogResult { + FileDialogResult({ + required this.paths, + this.typeGroupIndex, + }); + + /// The selected paths. + /// + /// Empty if the dialog was canceled. + List paths; + + /// The type group index (into the list provided in [SelectionOptions]) of + /// the group that was selected when the dialog was confirmed. + /// + /// Null if no type groups were provided, or the dialog was canceled. + int? typeGroupIndex; + + Object encode() { + return [ + paths, + typeGroupIndex, + ]; + } + + static FileDialogResult decode(Object result) { + result as List; + return FileDialogResult( + paths: (result[0] as List?)!.cast(), + typeGroupIndex: result[1] as int?, + ); + } +} + class _FileSelectorApiCodec extends StandardMessageCodec { const _FileSelectorApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is SelectionOptions) { + if (value is FileDialogResult) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is TypeGroup) { + } else if (value is SelectionOptions) { buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is TypeGroup) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -87,8 +124,10 @@ class _FileSelectorApiCodec extends StandardMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: - return SelectionOptions.decode(readValue(buffer)!); + return FileDialogResult.decode(readValue(buffer)!); case 129: + return SelectionOptions.decode(readValue(buffer)!); + case 130: return TypeGroup.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -106,7 +145,7 @@ class FileSelectorApi { static const MessageCodec codec = _FileSelectorApiCodec(); - Future> showOpenDialog(SelectionOptions arg_options, + Future showOpenDialog(SelectionOptions arg_options, String? arg_initialDirectory, String? arg_confirmButtonText) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FileSelectorApi.showOpenDialog', codec, @@ -131,11 +170,11 @@ class FileSelectorApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as List?)!.cast(); + return (replyList[0] as FileDialogResult?)!; } } - Future> showSaveDialog( + Future showSaveDialog( SelectionOptions arg_options, String? arg_initialDirectory, String? arg_suggestedName, @@ -166,7 +205,7 @@ class FileSelectorApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as List?)!.cast(); + return (replyList[0] as FileDialogResult?)!; } } } diff --git a/packages/file_selector/file_selector_windows/pigeons/messages.dart b/packages/file_selector/file_selector_windows/pigeons/messages.dart index c3b3aff192b..8f82aec0b83 100644 --- a/packages/file_selector/file_selector_windows/pigeons/messages.dart +++ b/packages/file_selector/file_selector_windows/pigeons/messages.dart @@ -37,14 +37,33 @@ class SelectionOptions { List allowedTypes; } +/// The result from an open or save dialog. +class FileDialogResult { + FileDialogResult({required this.paths, this.typeGroupIndex}); + + /// The selected paths. + /// + /// Empty if the dialog was canceled. + // TODO(stuartmorgan): Make the generic type non-nullable once supported. + // https://github.com/flutter/flutter/issues/97848 + // The Dart code treats the values as non-nullable. + List paths; + + /// The type group index (into the list provided in [SelectionOptions]) of + /// the group that was selected when the dialog was confirmed. + /// + /// Null if no type groups were provided, or the dialog was canceled. + int? typeGroupIndex; +} + @HostApi(dartHostTestHandler: 'TestFileSelectorApi') abstract class FileSelectorApi { - List showOpenDialog( + FileDialogResult showOpenDialog( SelectionOptions options, String? initialDirectory, String? confirmButtonText, ); - List showSaveDialog( + FileDialogResult showSaveDialog( SelectionOptions options, String? initialDirectory, String? suggestedName, diff --git a/packages/file_selector/file_selector_windows/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index 39d5cf7abf6..8b45d914c00 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_windows description: Windows implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.2 +version: 0.9.3 environment: sdk: ">=2.18.0 <4.0.0" @@ -27,4 +27,9 @@ dev_dependencies: flutter_test: sdk: flutter mockito: 5.4.1 - pigeon: ^9.1.0 + pigeon: ^10.0.0 + +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins +dependency_overrides: + {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart index fbe3683af37..4f455ee3088 100644 --- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart +++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart @@ -30,9 +30,10 @@ void main() { expect(FileSelectorPlatform.instance, isA()); }); - group('#openFile', () { + group('openFile', () { setUp(() { - when(mockApi.showOpenDialog(any, any, any)).thenReturn(['foo']); + when(mockApi.showOpenDialog(any, any, any)) + .thenReturn(FileDialogResult(paths: ['foo'])); }); test('simple call works', () async { @@ -105,10 +106,10 @@ void main() { }); }); - group('#openFiles', () { + group('openFiles', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) - .thenReturn(['foo', 'bar']); + .thenReturn(FileDialogResult(paths: ['foo', 'bar'])); }); test('simple call works', () async { @@ -182,9 +183,10 @@ void main() { }); }); - group('#getDirectoryPath', () { + group('getDirectoryPath', () { setUp(() { - when(mockApi.showOpenDialog(any, any, any)).thenReturn(['foo']); + when(mockApi.showOpenDialog(any, any, any)) + .thenReturn(FileDialogResult(paths: ['foo'])); }); test('simple call works', () async { @@ -211,10 +213,10 @@ void main() { }); }); - group('#getDirectoryPaths', () { + group('getDirectoryPaths', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) - .thenReturn(['foo', 'bar']); + .thenReturn(FileDialogResult(paths: ['foo', 'bar'])); }); test('simple call works', () async { @@ -242,10 +244,122 @@ void main() { }); }); - group('#getSavePath', () { + group('getSaveLocation', () { + setUp(() { + when(mockApi.showSaveDialog(any, any, any, any)) + .thenReturn(FileDialogResult(paths: ['foo'])); + }); + + test('simple call works', () async { + final FileSaveLocation? location = await plugin.getSaveLocation(); + + expect(location?.path, 'foo'); + expect(location?.activeFilter, null); + final VerificationResult result = + verify(mockApi.showSaveDialog(captureAny, null, null, null)); + final SelectionOptions options = result.captured[0] as SelectionOptions; + expect(options.allowMultiple, false); + expect(options.selectFolders, false); + }); + + test('passes the accepted type groups correctly', () async { + const XTypeGroup group = XTypeGroup( + label: 'text', + extensions: ['txt'], + mimeTypes: ['text/plain'], + ); + + const XTypeGroup groupTwo = XTypeGroup( + label: 'image', + extensions: ['jpg'], + mimeTypes: ['image/jpg'], + ); + + await plugin + .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); + + final VerificationResult result = + verify(mockApi.showSaveDialog(captureAny, null, null, null)); + final SelectionOptions options = result.captured[0] as SelectionOptions; + expect( + _typeGroupListsMatch(options.allowedTypes, [ + TypeGroup(label: 'text', extensions: ['txt']), + TypeGroup(label: 'image', extensions: ['jpg']), + ]), + true); + }); + + test('returns the selected type group correctly', () async { + when(mockApi.showSaveDialog(any, any, any, any)).thenReturn( + FileDialogResult(paths: ['foo'], typeGroupIndex: 1)); + const XTypeGroup group = XTypeGroup( + label: 'text', + extensions: ['txt'], + mimeTypes: ['text/plain'], + ); + + const XTypeGroup groupTwo = XTypeGroup( + label: 'image', + extensions: ['jpg'], + mimeTypes: ['image/jpg'], + ); + + final FileSaveLocation? result = await plugin + .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); + + verify(mockApi.showSaveDialog(captureAny, null, null, null)); + + expect(result?.activeFilter, groupTwo); + }); + + test('passes initialDirectory correctly', () async { + await plugin.getSaveLocation( + options: + const SaveDialogOptions(initialDirectory: '/example/directory')); + + verify(mockApi.showSaveDialog(any, '/example/directory', null, null)); + }); + + test('passes suggestedName correctly', () async { + await plugin.getSaveLocation( + options: const SaveDialogOptions(suggestedName: 'baz.txt')); + + verify(mockApi.showSaveDialog(any, null, 'baz.txt', null)); + }); + + test('passes confirmButtonText correctly', () async { + await plugin.getSaveLocation( + options: const SaveDialogOptions(confirmButtonText: 'Save File')); + + verify(mockApi.showSaveDialog(any, null, null, 'Save File')); + }); + + test('throws for a type group that does not support Windows', () async { + const XTypeGroup group = XTypeGroup( + label: 'text', + mimeTypes: ['text/plain'], + ); + + await expectLater( + plugin.getSaveLocation(acceptedTypeGroups: [group]), + throwsArgumentError); + }); + + test('allows a wildcard group', () async { + const XTypeGroup group = XTypeGroup( + label: 'text', + ); + + await expectLater( + plugin.getSaveLocation(acceptedTypeGroups: [group]), + completes); + }); + }); + + group('getSavePath (deprecated)', () { setUp(() { when(mockApi.showSaveDialog(any, any, any, any)) - .thenReturn(['foo']); + .thenReturn(FileDialogResult(paths: ['foo'])); }); test('simple call works', () async { diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart index ae55f2e301d..7168e0c8d81 100644 --- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart +++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart @@ -1,12 +1,14 @@ -// Mocks generated by Mockito 5.4.0 from annotations +// Mocks generated by Mockito 5.4.1 from annotations // in file_selector_windows/test/file_selector_windows_test.dart. // Do not manually edit this file. +// @dart=2.19 + // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:file_selector_windows/src/messages.g.dart' as _i3; +import 'package:file_selector_windows/src/messages.g.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; -import 'test_api.g.dart' as _i2; +import 'test_api.g.dart' as _i3; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -19,18 +21,29 @@ import 'test_api.g.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +class _FakeFileDialogResult_0 extends _i1.SmartFake + implements _i2.FileDialogResult { + _FakeFileDialogResult_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [TestFileSelectorApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestFileSelectorApi extends _i1.Mock - implements _i2.TestFileSelectorApi { + implements _i3.TestFileSelectorApi { MockTestFileSelectorApi() { _i1.throwOnMissingStub(this); } @override - List showOpenDialog( - _i3.SelectionOptions? options, + _i2.FileDialogResult showOpenDialog( + _i2.SelectionOptions? options, String? initialDirectory, String? confirmButtonText, ) => @@ -43,11 +56,21 @@ class MockTestFileSelectorApi extends _i1.Mock confirmButtonText, ], ), - returnValue: [], - ) as List); + returnValue: _FakeFileDialogResult_0( + this, + Invocation.method( + #showOpenDialog, + [ + options, + initialDirectory, + confirmButtonText, + ], + ), + ), + ) as _i2.FileDialogResult); @override - List showSaveDialog( - _i3.SelectionOptions? options, + _i2.FileDialogResult showSaveDialog( + _i2.SelectionOptions? options, String? initialDirectory, String? suggestedName, String? confirmButtonText, @@ -62,6 +85,17 @@ class MockTestFileSelectorApi extends _i1.Mock confirmButtonText, ], ), - returnValue: [], - ) as List); + returnValue: _FakeFileDialogResult_0( + this, + Invocation.method( + #showSaveDialog, + [ + options, + initialDirectory, + suggestedName, + confirmButtonText, + ], + ), + ), + ) as _i2.FileDialogResult); } diff --git a/packages/file_selector/file_selector_windows/test/test_api.g.dart b/packages/file_selector/file_selector_windows/test/test_api.g.dart index f9ed8e5b63d..778ae4fc16c 100644 --- a/packages/file_selector/file_selector_windows/test/test_api.g.dart +++ b/packages/file_selector/file_selector_windows/test/test_api.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v9.1.1), do not edit directly. +// Autogenerated from Pigeon (v10.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports @@ -17,12 +17,15 @@ class _TestFileSelectorApiCodec extends StandardMessageCodec { const _TestFileSelectorApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is SelectionOptions) { + if (value is FileDialogResult) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is TypeGroup) { + } else if (value is SelectionOptions) { buffer.putUint8(129); writeValue(buffer, value.encode()); + } else if (value is TypeGroup) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -32,8 +35,10 @@ class _TestFileSelectorApiCodec extends StandardMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: - return SelectionOptions.decode(readValue(buffer)!); + return FileDialogResult.decode(readValue(buffer)!); case 129: + return SelectionOptions.decode(readValue(buffer)!); + case 130: return TypeGroup.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -42,12 +47,14 @@ class _TestFileSelectorApiCodec extends StandardMessageCodec { } abstract class TestFileSelectorApi { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = _TestFileSelectorApiCodec(); - List showOpenDialog(SelectionOptions options, + FileDialogResult showOpenDialog(SelectionOptions options, String? initialDirectory, String? confirmButtonText); - List showSaveDialog( + FileDialogResult showSaveDialog( SelectionOptions options, String? initialDirectory, String? suggestedName, @@ -60,9 +67,12 @@ abstract class TestFileSelectorApi { 'dev.flutter.pigeon.FileSelectorApi.showOpenDialog', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.showOpenDialog was null.'); final List args = (message as List?)!; @@ -71,7 +81,7 @@ abstract class TestFileSelectorApi { 'Argument for dev.flutter.pigeon.FileSelectorApi.showOpenDialog was null, expected non-null SelectionOptions.'); final String? arg_initialDirectory = (args[1] as String?); final String? arg_confirmButtonText = (args[2] as String?); - final List output = api.showOpenDialog( + final FileDialogResult output = api.showOpenDialog( arg_options!, arg_initialDirectory, arg_confirmButtonText); return [output]; }); @@ -82,9 +92,12 @@ abstract class TestFileSelectorApi { 'dev.flutter.pigeon.FileSelectorApi.showSaveDialog', codec, binaryMessenger: binaryMessenger); if (api == null) { - channel.setMockMessageHandler(null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, null); } else { - channel.setMockMessageHandler((Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(channel, + (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.showSaveDialog was null.'); final List args = (message as List?)!; @@ -94,7 +107,7 @@ abstract class TestFileSelectorApi { final String? arg_initialDirectory = (args[1] as String?); final String? arg_suggestedName = (args[2] as String?); final String? arg_confirmButtonText = (args[3] as String?); - final List output = api.showSaveDialog(arg_options!, + final FileDialogResult output = api.showSaveDialog(arg_options!, arg_initialDirectory, arg_suggestedName, arg_confirmButtonText); return [output]; }); diff --git a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp index 5820c4a5da4..af2a9affecc 100644 --- a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp +++ b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp @@ -51,6 +51,10 @@ HRESULT FileDialogController::GetResult(IShellItem** out_item) const { return dialog_->GetResult(out_item); } +HRESULT FileDialogController::GetFileTypeIndex(UINT* out_index) const { + return dialog_->GetFileTypeIndex(out_index); +} + HRESULT FileDialogController::GetResults(IShellItemArray** out_items) const { IFileOpenDialogPtr open_dialog; HRESULT result = dialog_->QueryInterface(IID_PPV_ARGS(&open_dialog)); diff --git a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h index f5c93974cbe..ab4929287e9 100644 --- a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h +++ b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h @@ -38,6 +38,7 @@ class FileDialogController { virtual HRESULT SetOptions(FILEOPENDIALOGOPTIONS options); virtual HRESULT Show(HWND parent); virtual HRESULT GetResult(IShellItem** out_item) const; + virtual HRESULT GetFileTypeIndex(UINT* out_index) const; // IFileOpenDialog wrapper. This will fail if the IFileDialog* provided to the // constructor was not an IFileOpenDialog instance. diff --git a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp index b9e6d211b2d..35697983108 100644 --- a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp +++ b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp @@ -29,8 +29,8 @@ namespace file_selector_windows { namespace { +using flutter::CustomEncodableValue; using flutter::EncodableList; -using flutter::EncodableMap; using flutter::EncodableValue; // The kind of file dialog to show. @@ -137,7 +137,7 @@ class DialogWrapper { for (const EncodableValue& filter_info_value : filters) { const auto& type_group = std::any_cast( - std::get(filter_info_value)); + std::get(filter_info_value)); filter_names.push_back(Utf16FromUtf8(type_group.label())); filter_extensions.push_back(L""); std::wstring& spec = filter_extensions.back(); @@ -158,8 +158,8 @@ class DialogWrapper { static_cast(filter_specs.size()), filter_specs.data()); } - // Displays the dialog, and returns the selected files, or nullopt on error. - std::optional Show(HWND parent_window) { + // Displays the dialog, and returns the result, or nullopt on error. + std::optional Show(HWND parent_window) { assert(dialog_controller_); last_result_ = dialog_controller_->Show(parent_window); if (!SUCCEEDED(last_result_)) { @@ -190,7 +190,14 @@ class DialogWrapper { } files.push_back(EncodableValue(GetPathForShellItem(shell_item))); } - return files; + FileDialogResult result(files, nullptr); + UINT file_type_index; + if (SUCCEEDED(dialog_controller_->GetFileTypeIndex(&file_type_index)) && + file_type_index > 0) { + // Convert from the one-based index to a Dart index. + result.set_type_group_index(file_type_index - 1); + } + return result; } // Returns the result of the last Win32 API call related to this object. @@ -205,7 +212,7 @@ class DialogWrapper { HRESULT last_result_; }; -ErrorOr ShowDialog( +ErrorOr ShowDialog( const FileDialogControllerFactory& dialog_factory, HWND parent_window, DialogMode mode, const SelectionOptions& options, const std::string* initial_directory, const std::string* suggested_name, @@ -243,16 +250,16 @@ ErrorOr ShowDialog( dialog.SetFileTypeFilters(options.allowed_types()); } - std::optional files = dialog.Show(parent_window); - if (!files) { + std::optional result = dialog.Show(parent_window); + if (!result) { if (dialog.last_result() != HRESULT_FROM_WIN32(ERROR_CANCELLED)) { return FlutterError("System error", "Could not show dialog", EncodableValue(dialog.last_result())); } else { - return EncodableList(); + return FileDialogResult(EncodableList(), nullptr); } } - return std::move(files.value()); + return std::move(result.value()); } // Returns the top-level window that owns |view|. @@ -282,14 +289,14 @@ FileSelectorPlugin::FileSelectorPlugin( FileSelectorPlugin::~FileSelectorPlugin() = default; -ErrorOr FileSelectorPlugin::ShowOpenDialog( +ErrorOr FileSelectorPlugin::ShowOpenDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* confirmButtonText) { return ShowDialog(*controller_factory_, get_root_window_(), DialogMode::open, options, initialDirectory, nullptr, confirmButtonText); } -ErrorOr FileSelectorPlugin::ShowSaveDialog( +ErrorOr FileSelectorPlugin::ShowSaveDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* suggestedName, const std::string* confirmButtonText) { return ShowDialog(*controller_factory_, get_root_window_(), DialogMode::save, diff --git a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h index 1388bfd3898..2f17f949049 100644 --- a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h +++ b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h @@ -32,10 +32,10 @@ class FileSelectorPlugin : public flutter::Plugin, public FileSelectorApi { virtual ~FileSelectorPlugin(); // FileSelectorApi - ErrorOr ShowOpenDialog( + ErrorOr ShowOpenDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* confirm_button_text) override; - ErrorOr ShowSaveDialog( + ErrorOr ShowSaveDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* suggestedName, const std::string* confirmButtonText) override; diff --git a/packages/file_selector/file_selector_windows/windows/messages.g.cpp b/packages/file_selector/file_selector_windows/windows/messages.g.cpp index 24b831e292e..a60fd92a974 100644 --- a/packages/file_selector/file_selector_windows/windows/messages.g.cpp +++ b/packages/file_selector/file_selector_windows/windows/messages.g.cpp @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v9.1.1), do not edit directly. +// Autogenerated from Pigeon (v10.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -26,10 +26,15 @@ using flutter::EncodableValue; // TypeGroup +TypeGroup::TypeGroup(const std::string& label, const EncodableList& extensions) + : label_(label), extensions_(extensions) {} + const std::string& TypeGroup::label() const { return label_; } + void TypeGroup::set_label(std::string_view value_arg) { label_ = value_arg; } const EncodableList& TypeGroup::extensions() const { return extensions_; } + void TypeGroup::set_extensions(const EncodableList& value_arg) { extensions_ = value_arg; } @@ -42,29 +47,28 @@ EncodableList TypeGroup::ToEncodableList() const { return list; } -TypeGroup::TypeGroup() {} - -TypeGroup::TypeGroup(const EncodableList& list) { - auto& encodable_label = list[0]; - if (const std::string* pointer_label = - std::get_if(&encodable_label)) { - label_ = *pointer_label; - } - auto& encodable_extensions = list[1]; - if (const EncodableList* pointer_extensions = - std::get_if(&encodable_extensions)) { - extensions_ = *pointer_extensions; - } +TypeGroup TypeGroup::FromEncodableList(const EncodableList& list) { + TypeGroup decoded(std::get(list[0]), + std::get(list[1])); + return decoded; } // SelectionOptions +SelectionOptions::SelectionOptions(bool allow_multiple, bool select_folders, + const EncodableList& allowed_types) + : allow_multiple_(allow_multiple), + select_folders_(select_folders), + allowed_types_(allowed_types) {} + bool SelectionOptions::allow_multiple() const { return allow_multiple_; } + void SelectionOptions::set_allow_multiple(bool value_arg) { allow_multiple_ = value_arg; } bool SelectionOptions::select_folders() const { return select_folders_; } + void SelectionOptions::set_select_folders(bool value_arg) { select_folders_ = value_arg; } @@ -72,6 +76,7 @@ void SelectionOptions::set_select_folders(bool value_arg) { const EncodableList& SelectionOptions::allowed_types() const { return allowed_types_; } + void SelectionOptions::set_allowed_types(const EncodableList& value_arg) { allowed_types_ = value_arg; } @@ -85,36 +90,77 @@ EncodableList SelectionOptions::ToEncodableList() const { return list; } -SelectionOptions::SelectionOptions() {} +SelectionOptions SelectionOptions::FromEncodableList( + const EncodableList& list) { + SelectionOptions decoded(std::get(list[0]), std::get(list[1]), + std::get(list[2])); + return decoded; +} -SelectionOptions::SelectionOptions(const EncodableList& list) { - auto& encodable_allow_multiple = list[0]; - if (const bool* pointer_allow_multiple = - std::get_if(&encodable_allow_multiple)) { - allow_multiple_ = *pointer_allow_multiple; - } - auto& encodable_select_folders = list[1]; - if (const bool* pointer_select_folders = - std::get_if(&encodable_select_folders)) { - select_folders_ = *pointer_select_folders; - } - auto& encodable_allowed_types = list[2]; - if (const EncodableList* pointer_allowed_types = - std::get_if(&encodable_allowed_types)) { - allowed_types_ = *pointer_allowed_types; +// FileDialogResult + +FileDialogResult::FileDialogResult(const EncodableList& paths) + : paths_(paths) {} + +FileDialogResult::FileDialogResult(const EncodableList& paths, + const int64_t* type_group_index) + : paths_(paths), + type_group_index_(type_group_index + ? std::optional(*type_group_index) + : std::nullopt) {} + +const EncodableList& FileDialogResult::paths() const { return paths_; } + +void FileDialogResult::set_paths(const EncodableList& value_arg) { + paths_ = value_arg; +} + +const int64_t* FileDialogResult::type_group_index() const { + return type_group_index_ ? &(*type_group_index_) : nullptr; +} + +void FileDialogResult::set_type_group_index(const int64_t* value_arg) { + type_group_index_ = + value_arg ? std::optional(*value_arg) : std::nullopt; +} + +void FileDialogResult::set_type_group_index(int64_t value_arg) { + type_group_index_ = value_arg; +} + +EncodableList FileDialogResult::ToEncodableList() const { + EncodableList list; + list.reserve(2); + list.push_back(EncodableValue(paths_)); + list.push_back(type_group_index_ ? EncodableValue(*type_group_index_) + : EncodableValue()); + return list; +} + +FileDialogResult FileDialogResult::FromEncodableList( + const EncodableList& list) { + FileDialogResult decoded(std::get(list[0])); + auto& encodable_type_group_index = list[1]; + if (!encodable_type_group_index.IsNull()) { + decoded.set_type_group_index(encodable_type_group_index.LongValue()); } + return decoded; } FileSelectorApiCodecSerializer::FileSelectorApiCodecSerializer() {} + EncodableValue FileSelectorApiCodecSerializer::ReadValueOfType( uint8_t type, flutter::ByteStreamReader* stream) const { switch (type) { case 128: - return CustomEncodableValue( - SelectionOptions(std::get(ReadValue(stream)))); + return CustomEncodableValue(FileDialogResult::FromEncodableList( + std::get(ReadValue(stream)))); case 129: - return CustomEncodableValue( - TypeGroup(std::get(ReadValue(stream)))); + return CustomEncodableValue(SelectionOptions::FromEncodableList( + std::get(ReadValue(stream)))); + case 130: + return CustomEncodableValue(TypeGroup::FromEncodableList( + std::get(ReadValue(stream)))); default: return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } @@ -124,8 +170,16 @@ void FileSelectorApiCodecSerializer::WriteValue( const EncodableValue& value, flutter::ByteStreamWriter* stream) const { if (const CustomEncodableValue* custom_value = std::get_if(&value)) { - if (custom_value->type() == typeid(SelectionOptions)) { + if (custom_value->type() == typeid(FileDialogResult)) { stream->WriteByte(128); + WriteValue( + EncodableValue( + std::any_cast(*custom_value).ToEncodableList()), + stream); + return; + } + if (custom_value->type() == typeid(SelectionOptions)) { + stream->WriteByte(129); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -133,7 +187,7 @@ void FileSelectorApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(TypeGroup)) { - stream->WriteByte(129); + stream->WriteByte(130); WriteValue(EncodableValue( std::any_cast(*custom_value).ToEncodableList()), stream); @@ -176,14 +230,15 @@ void FileSelectorApi::SetUp(flutter::BinaryMessenger* binary_messenger, const auto& encodable_confirm_button_text_arg = args.at(2); const auto* confirm_button_text_arg = std::get_if(&encodable_confirm_button_text_arg); - ErrorOr output = api->ShowOpenDialog( + ErrorOr output = api->ShowOpenDialog( options_arg, initial_directory_arg, confirm_button_text_arg); if (output.has_error()) { reply(WrapError(output.error())); return; } EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -219,7 +274,7 @@ void FileSelectorApi::SetUp(flutter::BinaryMessenger* binary_messenger, const auto& encodable_confirm_button_text_arg = args.at(3); const auto* confirm_button_text_arg = std::get_if(&encodable_confirm_button_text_arg); - ErrorOr output = api->ShowSaveDialog( + ErrorOr output = api->ShowSaveDialog( options_arg, initial_directory_arg, suggested_name_arg, confirm_button_text_arg); if (output.has_error()) { @@ -227,7 +282,8 @@ void FileSelectorApi::SetUp(flutter::BinaryMessenger* binary_messenger, return; } EncodableList wrapped; - wrapped.push_back(EncodableValue(std::move(output).TakeValue())); + wrapped.push_back( + CustomEncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -244,6 +300,7 @@ EncodableValue FileSelectorApi::WrapError(std::string_view error_message) { EncodableList{EncodableValue(std::string(error_message)), EncodableValue("Error"), EncodableValue()}); } + EncodableValue FileSelectorApi::WrapError(const FlutterError& error) { return EncodableValue(EncodableList{EncodableValue(error.code()), EncodableValue(error.message()), diff --git a/packages/file_selector/file_selector_windows/windows/messages.g.h b/packages/file_selector/file_selector_windows/windows/messages.g.h index 248ca89c977..ab8afd7d703 100644 --- a/packages/file_selector/file_selector_windows/windows/messages.g.h +++ b/packages/file_selector/file_selector_windows/windows/messages.g.h @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v9.1.1), do not edit directly. +// Autogenerated from Pigeon (v10.0.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_MESSAGES_G_H_ @@ -41,10 +41,10 @@ class FlutterError { template class ErrorOr { public: - ErrorOr(const T& rhs) { new (&v_) T(rhs); } - ErrorOr(const T&& rhs) { v_ = std::move(rhs); } - ErrorOr(const FlutterError& rhs) { new (&v_) FlutterError(rhs); } - ErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); } + ErrorOr(const T& rhs) : v_(rhs) {} + ErrorOr(const T&& rhs) : v_(std::move(rhs)) {} + ErrorOr(const FlutterError& rhs) : v_(rhs) {} + ErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {} bool has_error() const { return std::holds_alternative(v_); } const T& value() const { return std::get(v_); }; @@ -61,7 +61,10 @@ class ErrorOr { // Generated class from Pigeon that represents data sent in messages. class TypeGroup { public: - TypeGroup(); + // Constructs an object setting all fields. + explicit TypeGroup(const std::string& label, + const flutter::EncodableList& extensions); + const std::string& label() const; void set_label(std::string_view value_arg); @@ -69,7 +72,7 @@ class TypeGroup { void set_extensions(const flutter::EncodableList& value_arg); private: - TypeGroup(const flutter::EncodableList& list); + static TypeGroup FromEncodableList(const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class FileSelectorApi; friend class FileSelectorApiCodecSerializer; @@ -80,7 +83,10 @@ class TypeGroup { // Generated class from Pigeon that represents data sent in messages. class SelectionOptions { public: - SelectionOptions(); + // Constructs an object setting all fields. + explicit SelectionOptions(bool allow_multiple, bool select_folders, + const flutter::EncodableList& allowed_types); + bool allow_multiple() const; void set_allow_multiple(bool value_arg); @@ -91,7 +97,7 @@ class SelectionOptions { void set_allowed_types(const flutter::EncodableList& value_arg); private: - SelectionOptions(const flutter::EncodableList& list); + static SelectionOptions FromEncodableList(const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class FileSelectorApi; friend class FileSelectorApiCodecSerializer; @@ -100,16 +106,49 @@ class SelectionOptions { flutter::EncodableList allowed_types_; }; +// The result from an open or save dialog. +// +// Generated class from Pigeon that represents data sent in messages. +class FileDialogResult { + public: + // Constructs an object setting all non-nullable fields. + explicit FileDialogResult(const flutter::EncodableList& paths); + + // Constructs an object setting all fields. + explicit FileDialogResult(const flutter::EncodableList& paths, + const int64_t* type_group_index); + + // The selected paths. + // + // Empty if the dialog was canceled. + const flutter::EncodableList& paths() const; + void set_paths(const flutter::EncodableList& value_arg); + + // The type group index (into the list provided in [SelectionOptions]) of + // the group that was selected when the dialog was confirmed. + // + // Null if no type groups were provided, or the dialog was canceled. + const int64_t* type_group_index() const; + void set_type_group_index(const int64_t* value_arg); + void set_type_group_index(int64_t value_arg); + + private: + static FileDialogResult FromEncodableList(const flutter::EncodableList& list); + flutter::EncodableList ToEncodableList() const; + friend class FileSelectorApi; + friend class FileSelectorApiCodecSerializer; + flutter::EncodableList paths_; + std::optional type_group_index_; +}; + class FileSelectorApiCodecSerializer : public flutter::StandardCodecSerializer { public: + FileSelectorApiCodecSerializer(); inline static FileSelectorApiCodecSerializer& GetInstance() { static FileSelectorApiCodecSerializer sInstance; return sInstance; } - FileSelectorApiCodecSerializer(); - - public: void WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const override; @@ -125,10 +164,10 @@ class FileSelectorApi { FileSelectorApi(const FileSelectorApi&) = delete; FileSelectorApi& operator=(const FileSelectorApi&) = delete; virtual ~FileSelectorApi() {} - virtual ErrorOr ShowOpenDialog( + virtual ErrorOr ShowOpenDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* confirm_button_text) = 0; - virtual ErrorOr ShowSaveDialog( + virtual ErrorOr ShowSaveDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* suggested_name, const std::string* confirm_button_text) = 0; diff --git a/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp b/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp index 8efeb54f860..ee6d4dcded7 100644 --- a/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp +++ b/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp @@ -28,37 +28,8 @@ namespace { using flutter::CustomEncodableValue; using flutter::EncodableList; -using flutter::EncodableMap; using flutter::EncodableValue; -// These structs and classes are a workaround for -// https://github.com/flutter/flutter/issues/104286 and -// https://github.com/flutter/flutter/issues/104653. -struct AllowMultipleArg { - bool value = false; - AllowMultipleArg(bool val) : value(val) {} -}; -struct SelectFoldersArg { - bool value = false; - SelectFoldersArg(bool val) : value(val) {} -}; -SelectionOptions CreateOptions(AllowMultipleArg allow_multiple, - SelectFoldersArg select_folders, - const EncodableList& allowed_types) { - SelectionOptions options; - options.set_allow_multiple(allow_multiple.value); - options.set_select_folders(select_folders.value); - options.set_allowed_types(allowed_types); - return options; -} -TypeGroup CreateTypeGroup(std::string_view label, - const EncodableList& extensions) { - TypeGroup group; - group.set_label(label); - group.set_extensions(extensions); - return group; -} - } // namespace TEST(FileSelectorPlugin, TestOpenSimple) { @@ -87,17 +58,18 @@ TEST(FileSelectorPlugin, TestOpenSimple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), - EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + SelectionOptions(/* allow multiple = */ false, + /* select folders = */ false, EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); - EXPECT_EQ(paths.size(), 1); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); + EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestOpenWithArguments) { @@ -129,17 +101,18 @@ TEST(FileSelectorPlugin, TestOpenWithArguments) { // This directory must exist. std::string initial_directory("C:\\Program Files"); std::string confirm_button("Open it!"); - ErrorOr result = plugin.ShowOpenDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), - EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + SelectionOptions(/* allow multiple = */ false, + /* select folders = */ false, EncodableList()), &initial_directory, &confirm_button); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); - EXPECT_EQ(paths.size(), 1); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); + EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestOpenMultiple) { @@ -173,19 +146,20 @@ TEST(FileSelectorPlugin, TestOpenMultiple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - CreateOptions(AllowMultipleArg(true), SelectFoldersArg(false), - EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + SelectionOptions(/* allow multiple = */ true, + /* select folders = */ false, EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); - EXPECT_EQ(paths.size(), 2); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 2); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file_1.path())); EXPECT_EQ(std::get(paths[1]), Utf8FromUtf16(fake_selected_file_2.path())); + EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestOpenWithFilter) { @@ -196,18 +170,18 @@ TEST(FileSelectorPlugin, TestOpenWithFilter) { IID_PPV_ARGS(&fake_result_array)); const EncodableValue text_group = - CustomEncodableValue(CreateTypeGroup("Text", EncodableList({ - EncodableValue("txt"), - EncodableValue("json"), - }))); + CustomEncodableValue(TypeGroup("Text", EncodableList({ + EncodableValue("txt"), + EncodableValue("json"), + }))); const EncodableValue image_group = - CustomEncodableValue(CreateTypeGroup("Images", EncodableList({ - EncodableValue("png"), - EncodableValue("gif"), - EncodableValue("jpeg"), - }))); + CustomEncodableValue(TypeGroup("Images", EncodableList({ + EncodableValue("png"), + EncodableValue("gif"), + EncodableValue("jpeg"), + }))); const EncodableValue any_group = - CustomEncodableValue(CreateTypeGroup("Any", EncodableList())); + CustomEncodableValue(TypeGroup("Any", EncodableList())); bool shown = false; MockShow show_validator = [&shown, fake_result_array, fake_window]( @@ -234,21 +208,26 @@ TEST(FileSelectorPlugin, TestOpenWithFilter) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), - EncodableList({ - text_group, - image_group, - any_group, - })), - nullptr, nullptr); + ErrorOr result = + plugin.ShowOpenDialog(SelectionOptions(/* allow multiple = */ false, + /* select folders = */ false, + EncodableList({ + text_group, + image_group, + any_group, + })), + nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); - EXPECT_EQ(paths.size(), 1); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); + // The test dialog controller always reports the last group as + // selected, so that should be what the plugin returns. + ASSERT_NE(result.value().type_group_index(), nullptr); + EXPECT_EQ(*(result.value().type_group_index()), 2); } TEST(FileSelectorPlugin, TestOpenCancel) { @@ -265,15 +244,16 @@ TEST(FileSelectorPlugin, TestOpenCancel) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), - EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + SelectionOptions(/* allow multiple = */ false, + /* select folders = */ false, EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); + const EncodableList& paths = result.value().paths(); EXPECT_EQ(paths.size(), 0); + EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestSaveSimple) { @@ -299,17 +279,18 @@ TEST(FileSelectorPlugin, TestSaveSimple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowSaveDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), - EncodableList()), + ErrorOr result = plugin.ShowSaveDialog( + SelectionOptions(/* allow multiple = */ false, + /* select folders = */ false, EncodableList()), nullptr, nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); - EXPECT_EQ(paths.size(), 1); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); + EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestSaveWithArguments) { @@ -341,17 +322,78 @@ TEST(FileSelectorPlugin, TestSaveWithArguments) { std::string initial_directory("C:\\Program Files"); std::string suggested_name("a name"); std::string confirm_button("Save it!"); - ErrorOr result = plugin.ShowSaveDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), - EncodableList()), + ErrorOr result = plugin.ShowSaveDialog( + SelectionOptions(/* allow multiple = */ false, + /* select folders = */ false, EncodableList()), &initial_directory, &suggested_name, &confirm_button); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); - EXPECT_EQ(paths.size(), 1); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 1); + EXPECT_EQ(std::get(paths[0]), + Utf8FromUtf16(fake_selected_file.path())); + EXPECT_EQ(result.value().type_group_index(), nullptr); +} + +TEST(FileSelectorPlugin, TestSaveWithFilter) { + const HWND fake_window = reinterpret_cast(1337); + ScopedTestShellItem fake_selected_file; + + const EncodableValue text_group = + CustomEncodableValue(TypeGroup("Text", EncodableList({ + EncodableValue("txt"), + EncodableValue("json"), + }))); + const EncodableValue image_group = + CustomEncodableValue(TypeGroup("Images", EncodableList({ + EncodableValue("png"), + EncodableValue("gif"), + EncodableValue("jpeg"), + }))); + + bool shown = false; + MockShow show_validator = + [&shown, fake_result = fake_selected_file.file(), fake_window]( + const TestFileDialogController& dialog, HWND parent) { + shown = true; + EXPECT_EQ(parent, fake_window); + + // Validate filter. + const std::vector& filters = dialog.GetFileTypes(); + EXPECT_EQ(filters.size(), 2U); + if (filters.size() == 2U) { + EXPECT_EQ(filters[0].name, L"Text"); + EXPECT_EQ(filters[0].spec, L"*.txt;*.json"); + EXPECT_EQ(filters[1].name, L"Images"); + EXPECT_EQ(filters[1].spec, L"*.png;*.gif;*.jpeg"); + } + + return MockShowResult(fake_result); + }; + + FileSelectorPlugin plugin( + [fake_window] { return fake_window; }, + std::make_unique(show_validator)); + ErrorOr result = + plugin.ShowSaveDialog(SelectionOptions(/* allow multiple = */ false, + /* select folders = */ false, + EncodableList({ + text_group, + image_group, + })), + nullptr, nullptr, nullptr); + + EXPECT_TRUE(shown); + ASSERT_FALSE(result.has_error()); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); + // The test dialog controller always reports the last group as + // selected, so that should be what the plugin returns. + ASSERT_NE(result.value().type_group_index(), nullptr); + EXPECT_EQ(*(result.value().type_group_index()), 1); } TEST(FileSelectorPlugin, TestSaveCancel) { @@ -368,15 +410,16 @@ TEST(FileSelectorPlugin, TestSaveCancel) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowSaveDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), - EncodableList()), + ErrorOr result = plugin.ShowSaveDialog( + SelectionOptions(/* allow multiple = */ false, + /* select folders = */ false, EncodableList()), nullptr, nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); + const EncodableList& paths = result.value().paths(); EXPECT_EQ(paths.size(), 0); + EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestGetDirectorySimple) { @@ -408,16 +451,17 @@ TEST(FileSelectorPlugin, TestGetDirectorySimple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(true), - EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + SelectionOptions(/* allow multiple = */ false, + /* select folders = */ true, EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); - EXPECT_EQ(paths.size(), 1); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), "C:\\Program Files"); + EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestGetDirectoryMultiple) { @@ -454,19 +498,20 @@ TEST(FileSelectorPlugin, TestGetDirectoryMultiple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - CreateOptions(AllowMultipleArg(true), SelectFoldersArg(true), - EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + SelectionOptions(/* allow multiple = */ true, /* select folders = */ true, + EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); - EXPECT_EQ(paths.size(), 2); + const EncodableList& paths = result.value().paths(); + ASSERT_EQ(paths.size(), 2); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_dir_1.path())); EXPECT_EQ(std::get(paths[1]), Utf8FromUtf16(fake_selected_dir_2.path())); + EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestGetDirectoryCancel) { @@ -483,15 +528,16 @@ TEST(FileSelectorPlugin, TestGetDirectoryCancel) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - CreateOptions(AllowMultipleArg(false), SelectFoldersArg(true), - EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + SelectionOptions(/* allow multiple = */ false, + /* select folders = */ true, EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value(); + const EncodableList& paths = result.value().paths(); EXPECT_EQ(paths.size(), 0); + EXPECT_EQ(result.value().type_group_index(), nullptr); } } // namespace test diff --git a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp index 15065f916c8..e775aa627aa 100644 --- a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp +++ b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp @@ -60,6 +60,13 @@ HRESULT TestFileDialogController::GetResult(IShellItem** out_item) const { return S_OK; } +HRESULT TestFileDialogController::GetFileTypeIndex(UINT* out_index) const { + // Arbitrarily always return the last group. (No -1 because the return value + // from GetFileTypeIndex is defined to be one-indexed.) + *out_index = static_cast(filter_groups_.size()); + return S_OK; +} + HRESULT TestFileDialogController::GetResults( IShellItemArray** out_items) const { *out_items = std::get(mock_result_); diff --git a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h index 1c221fc219f..e3aa0936e76 100644 --- a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h +++ b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h @@ -56,6 +56,7 @@ class TestFileDialogController : public FileDialogController { HRESULT SetOkButtonLabel(const wchar_t* text) override; HRESULT Show(HWND parent) override; HRESULT GetResult(IShellItem** out_item) const override; + HRESULT GetFileTypeIndex(UINT* out_index) const override; HRESULT GetResults(IShellItemArray** out_items) const override; // Accessors for validating IFileDialogController setter calls. From 56d97e4cd0e23da0477c43f5153a881c1387d9a7 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 20 Jun 2023 09:18:39 -0400 Subject: [PATCH 2/3] Revert all but platform interface --- .../file_selector/file_selector/CHANGELOG.md | 3 +- .../lib/readme_standalone_excerpts.dart | 7 +- .../example/lib/save_text_page.dart | 6 +- .../file_selector/example/pubspec.yaml | 5 - .../file_selector/lib/file_selector.dart | 47 +--- .../file_selector/file_selector/pubspec.yaml | 7 +- .../test/file_selector_test.dart | 111 +------- .../file_selector_ios/CHANGELOG.md | 4 - .../file_selector_ios/example/pubspec.yaml | 5 - .../file_selector_ios/pubspec.yaml | 7 +- .../file_selector_linux/CHANGELOG.md | 3 +- .../example/lib/save_text_page.dart | 11 +- .../file_selector_linux/example/pubspec.yaml | 5 - .../lib/file_selector_linux.dart | 26 +- .../file_selector_linux/pubspec.yaml | 7 +- .../test/file_selector_linux_test.dart | 131 +--------- .../file_selector_macos/CHANGELOG.md | 3 +- .../example/lib/save_text_page.dart | 9 +- .../file_selector_macos/example/pubspec.yaml | 5 - .../lib/file_selector_macos.dart | 24 +- .../file_selector_macos/pubspec.yaml | 7 +- .../test/file_selector_macos_test.dart | 122 +-------- .../file_selector_web/CHANGELOG.md | 3 +- .../file_selector_web/example/pubspec.yaml | 5 - .../lib/file_selector_web.dart | 10 - .../file_selector_web/pubspec.yaml | 7 +- .../file_selector_windows/CHANGELOG.md | 4 - .../example/lib/save_text_page.dart | 37 +-- .../example/pubspec.yaml | 5 - .../example/windows/runner/Runner.rc | 10 +- .../lib/file_selector_windows.dart | 46 +--- .../lib/src/messages.g.dart | 55 +--- .../pigeons/messages.dart | 23 +- .../file_selector_windows/pubspec.yaml | 9 +- .../test/file_selector_windows_test.dart | 134 +--------- .../file_selector_windows_test.mocks.dart | 58 +---- .../test/test_api.g.dart | 37 +-- .../windows/file_dialog_controller.cpp | 4 - .../windows/file_dialog_controller.h | 1 - .../windows/file_selector_plugin.cpp | 31 +-- .../windows/file_selector_plugin.h | 4 +- .../windows/messages.g.cpp | 137 +++------- .../windows/messages.g.h | 67 ++--- .../test/file_selector_plugin_test.cpp | 238 +++++++----------- .../test/test_file_dialog_controller.cpp | 7 - .../test/test_file_dialog_controller.h | 1 - 46 files changed, 293 insertions(+), 1195 deletions(-) diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index 5215787e070..98d02784d7c 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,6 +1,5 @@ -## 0.9.4 +## NEXT -* Adds `getSaveLocation` and deprecates `getSavePath`. * Updates minimum supported macOS version to 10.14. * Updates minimum supported SDK version to Flutter 3.3/Dart 2.18. diff --git a/packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart b/packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart index cc0a051e218..e12be751716 100644 --- a/packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart +++ b/packages/file_selector/file_selector/example/lib/readme_standalone_excerpts.dart @@ -39,9 +39,8 @@ class _MyAppState extends State { Future saveFile() async { // #docregion Save const String fileName = 'suggested_name.txt'; - final FileSaveLocation? result = - await getSaveLocation(suggestedName: fileName); - if (result == null) { + final String? path = await getSavePath(suggestedName: fileName); + if (path == null) { // Operation was canceled by the user. return; } @@ -50,7 +49,7 @@ class _MyAppState extends State { const String mimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: mimeType, name: fileName); - await textFile.saveTo(result.path); + await textFile.saveTo(path); // #enddocregion Save } diff --git a/packages/file_selector/file_selector/example/lib/save_text_page.dart b/packages/file_selector/file_selector/example/lib/save_text_page.dart index e782530914e..751b91c7937 100644 --- a/packages/file_selector/file_selector/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector/example/lib/save_text_page.dart @@ -24,11 +24,11 @@ class SaveTextPage extends StatelessWidget { // file will be saved. In most cases, this parameter should not be provided. final String initialDirectory = (await getApplicationDocumentsDirectory()).path; - final FileSaveLocation? result = await getSaveLocation( + final String? path = await getSavePath( initialDirectory: initialDirectory, suggestedName: fileName, ); - if (result == null) { + if (path == null) { // Operation was canceled by the user. return; } @@ -39,7 +39,7 @@ class SaveTextPage extends StatelessWidget { final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); - await textFile.saveTo(result.path); + await textFile.saveTo(path); } @override diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml index d6c8ad37d39..f4de9cbc743 100644 --- a/packages/file_selector/file_selector/example/pubspec.yaml +++ b/packages/file_selector/file_selector/example/pubspec.yaml @@ -27,8 +27,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index e1739eda0c8..c2249565c9e 100644 --- a/packages/file_selector/file_selector/lib/file_selector.dart +++ b/packages/file_selector/file_selector/lib/file_selector.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; export 'package:file_selector_platform_interface/file_selector_platform_interface.dart' - show FileSaveLocation, XFile, XTypeGroup; + show XFile, XTypeGroup; /// Opens a file selection dialog and returns the path chosen by the user. /// @@ -92,54 +92,17 @@ Future> openFiles({ /// When not provided, the default OS label is used (for example, "Save"). /// /// Returns `null` if the user cancels the operation. -@Deprecated('Use getSaveLocation instead') Future getSavePath({ List acceptedTypeGroups = const [], String? initialDirectory, String? suggestedName, String? confirmButtonText, }) async { - return (await getSaveLocation( - acceptedTypeGroups: acceptedTypeGroups, - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText)) - ?.path; -} - -/// Opens a save dialog and returns the target path chosen by the user. -/// -/// [acceptedTypeGroups] is a list of file type groups that can be selected in -/// the dialog. How this is displayed depends on the pltaform, for example: -/// - On Windows and Linux, each group will be an entry in a list of filter -/// options. -/// - On macOS, the union of all types allowed by all of the groups will be -/// allowed. -/// Throws an [ArgumentError] if any type groups do not include filters -/// supported by the current platform. -/// -/// [initialDirectory] is the full path to the directory that will be displayed -/// when the dialog is opened. When not provided, the platform will pick an -/// initial location. -/// -/// [suggestedName] is initial value of file name. -/// -/// [confirmButtonText] is the text in the confirmation button of the dialog. -/// When not provided, the default OS label is used (for example, "Save"). -/// -/// Returns `null` if the user cancels the operation. -Future getSaveLocation({ - List acceptedTypeGroups = const [], - String? initialDirectory, - String? suggestedName, - String? confirmButtonText, -}) async { - return FileSelectorPlatform.instance.getSaveLocation( + return FileSelectorPlatform.instance.getSavePath( acceptedTypeGroups: acceptedTypeGroups, - options: SaveDialogOptions( - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText)); + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText); } /// Opens a directory selection dialog and returns the path chosen by the user. diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index 03764de006a..00f11e1af88 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for opening and saving files, or selecting directories, using native file selection UI. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.4 +version: 0.9.3 environment: sdk: ">=2.18.0 <4.0.0" @@ -38,8 +38,3 @@ dev_dependencies: sdk: flutter plugin_platform_interface: ^2.0.0 test: ^1.16.3 - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector/test/file_selector_test.dart b/packages/file_selector/file_selector/test/file_selector_test.dart index a9d684c0dff..cdcebe07828 100644 --- a/packages/file_selector/file_selector/test/file_selector_test.dart +++ b/packages/file_selector/file_selector/test/file_selector_test.dart @@ -144,80 +144,7 @@ void main() { }); }); - group('getSaveLocation', () { - const String expectedSavePath = '/example/path'; - - test('works', () async { - const int expectedActiveFilter = 1; - fakePlatformImplementation - ..setExpectations( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, - acceptedTypeGroups: acceptedTypeGroups, - suggestedName: suggestedName) - ..setPathsResponse([expectedSavePath], - activeFilter: expectedActiveFilter); - - final FileSaveLocation? location = await getSaveLocation( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, - acceptedTypeGroups: acceptedTypeGroups, - suggestedName: suggestedName, - ); - - expect(location?.path, expectedSavePath); - expect(location?.activeFilter, acceptedTypeGroups[expectedActiveFilter]); - }); - - test('works with no arguments', () async { - fakePlatformImplementation.setPathsResponse([expectedSavePath]); - - final FileSaveLocation? location = await getSaveLocation(); - expect(location?.path, expectedSavePath); - }); - - test('sets the initial directory', () async { - fakePlatformImplementation - ..setExpectations(initialDirectory: initialDirectory) - ..setPathsResponse([expectedSavePath]); - - final FileSaveLocation? location = - await getSaveLocation(initialDirectory: initialDirectory); - expect(location?.path, expectedSavePath); - }); - - test('sets the button confirmation label', () async { - fakePlatformImplementation - ..setExpectations(confirmButtonText: confirmButtonText) - ..setPathsResponse([expectedSavePath]); - - final FileSaveLocation? location = - await getSaveLocation(confirmButtonText: confirmButtonText); - expect(location?.path, expectedSavePath); - }); - - test('sets the accepted type groups', () async { - fakePlatformImplementation - ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) - ..setPathsResponse([expectedSavePath]); - - final FileSaveLocation? location = - await getSaveLocation(acceptedTypeGroups: acceptedTypeGroups); - expect(location?.path, expectedSavePath); - }); - - test('sets the suggested name', () async { - fakePlatformImplementation - ..setExpectations(suggestedName: suggestedName) - ..setPathsResponse([expectedSavePath]); - - final FileSaveLocation? location = - await getSaveLocation(suggestedName: suggestedName); - expect(location?.path, expectedSavePath); - }); - }); - - group('getSavePath (deprecated)', () { + group('getSavePath', () { const String expectedSavePath = '/example/path'; test('works', () async { @@ -394,7 +321,6 @@ class FakeFileSelector extends Fake // Return values. List? files; List? paths; - int? activeFilter; void setExpectations({ List acceptedTypeGroups = const [], @@ -413,9 +339,9 @@ class FakeFileSelector extends Fake this.files = files; } - void setPathsResponse(List paths, {int? activeFilter}) { + // ignore: use_setters_to_change_properties + void setPathsResponse(List paths) { this.paths = paths; - this.activeFilter = activeFilter; } @override @@ -448,35 +374,12 @@ class FakeFileSelector extends Fake String? initialDirectory, String? suggestedName, String? confirmButtonText, - }) async { - final FileSaveLocation? result = await getSaveLocation( - acceptedTypeGroups: acceptedTypeGroups, - options: SaveDialogOptions( - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText, - ), - ); - return result?.path; - } - - @override - Future getSaveLocation({ - List? acceptedTypeGroups, - SaveDialogOptions options = const SaveDialogOptions(), }) async { expect(acceptedTypeGroups, this.acceptedTypeGroups); - expect(options.initialDirectory, initialDirectory); - expect(options.suggestedName, suggestedName); - expect(options.confirmButtonText, confirmButtonText); - final String? path = paths?[0]; - final int? activeFilterIndex = activeFilter; - return path == null - ? null - : FileSaveLocation(path, - activeFilter: activeFilterIndex == null - ? null - : acceptedTypeGroups?[activeFilterIndex]); + expect(initialDirectory, this.initialDirectory); + expect(suggestedName, this.suggestedName); + expect(confirmButtonText, this.confirmButtonText); + return paths?[0]; } @override diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index 0c6d5658d31..8a48d5b089d 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.5.2 - -* Adds `getSaveLocation` and deprecates `getSavePath`. - ## 0.5.1+4 * Updates references to the deprecated `macUTIs`. diff --git a/packages/file_selector/file_selector_ios/example/pubspec.yaml b/packages/file_selector/file_selector_ios/example/pubspec.yaml index aace887472b..44642b6d912 100644 --- a/packages/file_selector/file_selector_ios/example/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/example/pubspec.yaml @@ -30,8 +30,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index a3b3d04160f..dd3f7dfca53 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_ios description: iOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.2 +version: 0.5.1+4 environment: sdk: ">=2.18.0 <4.0.0" @@ -27,8 +27,3 @@ dev_dependencies: sdk: flutter mockito: 5.4.1 pigeon: ^9.2.4 - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index 5c9dc1dbcd8..88886301d3a 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,6 +1,5 @@ -## 0.9.2 +## NEXT -* Adds `getSaveLocation` and deprecates `getSavePath`. * Updates minimum supported SDK version to Flutter 3.3/Dart 2.18. ## 0.9.1+3 diff --git a/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart index bfd83cfd239..174c490c5fc 100644 --- a/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart @@ -17,12 +17,11 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final FileSaveLocation? result = - await FileSelectorPlatform.instance.getSaveLocation( - options: SaveDialogOptions(suggestedName: fileName), + final String? path = await FileSelectorPlatform.instance.getSavePath( + // Operation was canceled by the user. + suggestedName: fileName, ); - // Operation was canceled by the user. - if (result == null) { + if (path == null) { return; } final String text = _contentController.text; @@ -30,7 +29,7 @@ class SaveTextPage extends StatelessWidget { const String fileMimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); - await textFile.saveTo(result.path); + await textFile.saveTo(path); } @override diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index ed7db91f1bd..1596eb3f769 100644 --- a/packages/file_selector/file_selector_linux/example/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml @@ -20,8 +20,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart index 5ae02ca3ade..b8e3df6a11b 100644 --- a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart +++ b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart @@ -82,37 +82,19 @@ class FileSelectorLinux extends FileSelectorPlatform { String? initialDirectory, String? suggestedName, String? confirmButtonText, - }) async { - return (await getSaveLocation( - acceptedTypeGroups: acceptedTypeGroups, - options: SaveDialogOptions( - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText, - ))) - ?.path; - } - - @override - Future getSaveLocation({ - List? acceptedTypeGroups, - SaveDialogOptions options = const SaveDialogOptions(), }) async { final List> serializedTypeGroups = _serializeTypeGroups(acceptedTypeGroups); - // TODO(stuartmorgan): Add the selected type group here and return it. See - // https://github.com/flutter/flutter/issues/107093 - final String? path = await _channel.invokeMethod( + return _channel.invokeMethod( _getSavePathMethod, { if (serializedTypeGroups.isNotEmpty) _acceptedTypeGroupsKey: serializedTypeGroups, - _initialDirectoryKey: options.initialDirectory, - _suggestedNameKey: options.suggestedName, - _confirmButtonTextKey: options.confirmButtonText, + _initialDirectoryKey: initialDirectory, + _suggestedNameKey: suggestedName, + _confirmButtonTextKey: confirmButtonText, }, ); - return path == null ? null : FileSaveLocation(path); } @override diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index dd24a8474d8..238e0aef741 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_linux description: Liunx implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_linux issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.2 +version: 0.9.1+3 environment: sdk: ">=2.18.0 <4.0.0" @@ -25,8 +25,3 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart index f6b2cc66fe5..5127d28b7f6 100644 --- a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart +++ b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart @@ -32,7 +32,7 @@ void main() { expect(FileSelectorPlatform.instance, isA()); }); - group('openFile', () { + group('#openFile', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -135,7 +135,7 @@ void main() { }); }); - group('openFiles', () { + group('#openFiles', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -209,7 +209,7 @@ void main() { ); await expectLater( - plugin.openFiles(acceptedTypeGroups: [group]), + plugin.openFile(acceptedTypeGroups: [group]), throwsArgumentError); }); @@ -218,7 +218,7 @@ void main() { label: 'any', ); - await plugin.openFiles(acceptedTypeGroups: [group]); + await plugin.openFile(acceptedTypeGroups: [group]); expectMethodCall( log, @@ -232,120 +232,13 @@ void main() { ], 'initialDirectory': null, 'confirmButtonText': null, - 'multiple': true, - }, - ); - }); - }); - - group('getSaveLocation', () { - test('passes the accepted type groups correctly', () async { - const XTypeGroup group = XTypeGroup( - label: 'text', - extensions: ['txt'], - mimeTypes: ['text/plain'], - ); - - const XTypeGroup groupTwo = XTypeGroup( - label: 'image', - extensions: ['jpg'], - mimeTypes: ['image/jpg'], - ); - - await plugin - .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); - - expectMethodCall( - log, - 'getSavePath', - arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'text', - 'extensions': ['*.txt'], - 'mimeTypes': ['text/plain'], - }, - { - 'label': 'image', - 'extensions': ['*.jpg'], - 'mimeTypes': ['image/jpg'], - }, - ], - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': null, - }, - ); - }); - - test('passes initialDirectory correctly', () async { - await plugin.getSaveLocation( - options: - const SaveDialogOptions(initialDirectory: '/example/directory')); - - expectMethodCall( - log, - 'getSavePath', - arguments: { - 'initialDirectory': '/example/directory', - 'suggestedName': null, - 'confirmButtonText': null, - }, - ); - }); - - test('passes confirmButtonText correctly', () async { - await plugin.getSaveLocation( - options: const SaveDialogOptions(confirmButtonText: 'Open File')); - - expectMethodCall( - log, - 'getSavePath', - arguments: { - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': 'Open File', - }, - ); - }); - - test('throws for a type group that does not support Linux', () async { - const XTypeGroup group = XTypeGroup( - label: 'images', - webWildCards: ['images/*'], - ); - - await expectLater( - plugin.getSaveLocation(acceptedTypeGroups: [group]), - throwsArgumentError); - }); - - test('passes a wildcard group correctly', () async { - const XTypeGroup group = XTypeGroup( - label: 'any', - ); - - await plugin.getSaveLocation(acceptedTypeGroups: [group]); - - expectMethodCall( - log, - 'getSavePath', - arguments: { - 'acceptedTypeGroups': >[ - { - 'label': 'any', - 'extensions': ['*'], - }, - ], - 'initialDirectory': null, - 'suggestedName': null, - 'confirmButtonText': null, + 'multiple': false, }, ); }); }); - group('getSavePath (deprecated)', () { + group('#getSavePath', () { test('passes the accepted type groups correctly', () async { const XTypeGroup group = XTypeGroup( label: 'text', @@ -420,7 +313,7 @@ void main() { ); await expectLater( - plugin.getSavePath(acceptedTypeGroups: [group]), + plugin.openFile(acceptedTypeGroups: [group]), throwsArgumentError); }); @@ -429,11 +322,11 @@ void main() { label: 'any', ); - await plugin.getSavePath(acceptedTypeGroups: [group]); + await plugin.openFile(acceptedTypeGroups: [group]); expectMethodCall( log, - 'getSavePath', + 'openFile', arguments: { 'acceptedTypeGroups': >[ { @@ -442,14 +335,14 @@ void main() { }, ], 'initialDirectory': null, - 'suggestedName': null, 'confirmButtonText': null, + 'multiple': false, }, ); }); }); - group('getDirectoryPath', () { + group('#getDirectoryPath', () { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPath(initialDirectory: '/example/directory'); @@ -476,7 +369,7 @@ void main() { }); }); - group('getDirectoryPaths', () { + group('#getDirectoryPaths', () { test('passes initialDirectory correctly', () async { await plugin.getDirectoryPaths(initialDirectory: '/example/directory'); diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index 7a197838bb9..e697974a390 100644 --- a/packages/file_selector/file_selector_macos/CHANGELOG.md +++ b/packages/file_selector/file_selector_macos/CHANGELOG.md @@ -1,6 +1,5 @@ -## 0.9.3 +## NEXT -* Adds `getSaveLocation` and deprecates `getSavePath`. * Updates minimum supported macOS version to 10.14. ## 0.9.2 diff --git a/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart index 6208e16ef7d..79d9c83c831 100644 --- a/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart @@ -17,11 +17,10 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final FileSaveLocation? result = - await FileSelectorPlatform.instance.getSaveLocation( - options: SaveDialogOptions(suggestedName: fileName), + final String? path = await FileSelectorPlatform.instance.getSavePath( + suggestedName: fileName, ); - if (result == null) { + if (path == null) { // Operation was canceled by the user. return; } @@ -30,7 +29,7 @@ class SaveTextPage extends StatelessWidget { const String fileMimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); - await textFile.saveTo(result.path); + await textFile.saveTo(path); } @override diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index 81e8801c07b..9e2d831f75c 100644 --- a/packages/file_selector/file_selector_macos/example/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/example/pubspec.yaml @@ -25,8 +25,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart index a26b0ff11a1..293c1e20e77 100644 --- a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart +++ b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart @@ -60,28 +60,12 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? suggestedName, String? confirmButtonText, }) async { - return (await getSaveLocation( - acceptedTypeGroups: acceptedTypeGroups, - options: SaveDialogOptions( - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText, - ))) - ?.path; - } - - @override - Future getSaveLocation({ - List? acceptedTypeGroups, - SaveDialogOptions options = const SaveDialogOptions(), - }) async { - final String? path = await _hostApi.displaySavePanel(SavePanelOptions( + return _hostApi.displaySavePanel(SavePanelOptions( allowedFileTypes: _allowedTypesFromTypeGroups(acceptedTypeGroups), - directoryPath: options.initialDirectory, - nameFieldStringValue: options.suggestedName, - prompt: options.confirmButtonText, + directoryPath: initialDirectory, + nameFieldStringValue: suggestedName, + prompt: confirmButtonText, )); - return path == null ? null : FileSaveLocation(path); } @override diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index c56482c6e14..731a5cc8eed 100644 --- a/packages/file_selector/file_selector_macos/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_macos description: macOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_macos issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.3 +version: 0.9.2 environment: sdk: ">=2.18.0 <4.0.0" @@ -28,8 +28,3 @@ dev_dependencies: sdk: flutter mockito: 5.4.1 pigeon: ^9.2.4 - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart index c7268a6be89..6450e6f3b0a 100644 --- a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart +++ b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart @@ -227,7 +227,7 @@ void main() { }); }); - group('getSavePath (deprecated)', () { + group('getSavePath', () { test('works as expected with no arguments', () async { when(mockApi.displaySavePanel(any)).thenAnswer((_) async => 'foo'); @@ -344,126 +344,6 @@ void main() { }); }); - group('getSaveLocation', () { - test('works as expected with no arguments', () async { - when(mockApi.displaySavePanel(any)).thenAnswer((_) async => 'foo'); - - final FileSaveLocation? location = await plugin.getSaveLocation(); - - expect(location?.path, 'foo'); - final VerificationResult result = - verify(mockApi.displaySavePanel(captureAny)); - final SavePanelOptions options = result.captured[0] as SavePanelOptions; - expect(options.allowedFileTypes, null); - expect(options.directoryPath, null); - expect(options.nameFieldStringValue, null); - expect(options.prompt, null); - }); - - test('handles cancel', () async { - when(mockApi.displaySavePanel(any)).thenAnswer((_) async => null); - - final FileSaveLocation? location = await plugin.getSaveLocation(); - - expect(location, null); - }); - - test('passes the accepted type groups correctly', () async { - const XTypeGroup group = XTypeGroup( - label: 'text', - extensions: ['txt'], - mimeTypes: ['text/plain'], - uniformTypeIdentifiers: ['public.text'], - ); - - const XTypeGroup groupTwo = XTypeGroup( - label: 'image', - extensions: ['jpg'], - mimeTypes: ['image/jpg'], - uniformTypeIdentifiers: ['public.image'], - webWildCards: ['image/*']); - - await plugin - .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); - - final VerificationResult result = - verify(mockApi.displaySavePanel(captureAny)); - final SavePanelOptions options = result.captured[0] as SavePanelOptions; - expect(options.allowedFileTypes!.extensions, ['txt', 'jpg']); - expect(options.allowedFileTypes!.mimeTypes, - ['text/plain', 'image/jpg']); - expect(options.allowedFileTypes!.utis, - ['public.text', 'public.image']); - }); - - test('passes initialDirectory correctly', () async { - await plugin.getSaveLocation( - options: - const SaveDialogOptions(initialDirectory: '/example/directory')); - - final VerificationResult result = - verify(mockApi.displaySavePanel(captureAny)); - final SavePanelOptions options = result.captured[0] as SavePanelOptions; - expect(options.directoryPath, '/example/directory'); - }); - - test('passes confirmButtonText correctly', () async { - await plugin.getSaveLocation( - options: const SaveDialogOptions(confirmButtonText: 'Open File')); - - final VerificationResult result = - verify(mockApi.displaySavePanel(captureAny)); - final SavePanelOptions options = result.captured[0] as SavePanelOptions; - expect(options.prompt, 'Open File'); - }); - - test('throws for a type group that does not support macOS', () async { - const XTypeGroup group = XTypeGroup( - label: 'images', - webWildCards: ['images/*'], - ); - - await expectLater( - plugin.getSaveLocation(acceptedTypeGroups: [group]), - throwsArgumentError); - }); - - test('allows a wildcard group', () async { - const XTypeGroup group = XTypeGroup( - label: 'text', - ); - - await expectLater( - plugin.getSaveLocation(acceptedTypeGroups: [group]), - completes); - }); - - test('ignores all type groups if any of them is a wildcard', () async { - await plugin.getSaveLocation(acceptedTypeGroups: [ - const XTypeGroup( - label: 'text', - extensions: ['txt'], - mimeTypes: ['text/plain'], - uniformTypeIdentifiers: ['public.text'], - ), - const XTypeGroup( - label: 'image', - extensions: ['jpg'], - mimeTypes: ['image/jpg'], - uniformTypeIdentifiers: ['public.image'], - ), - const XTypeGroup( - label: 'any', - ), - ]); - - final VerificationResult result = - verify(mockApi.displaySavePanel(captureAny)); - final SavePanelOptions options = result.captured[0] as SavePanelOptions; - expect(options.allowedFileTypes, null); - }); - }); - group('getDirectoryPath', () { test('works as expected with no arguments', () async { when(mockApi.displayOpenPanel(any)) diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 073f1c20ed8..43ad4962f04 100644 --- a/packages/file_selector/file_selector_web/CHANGELOG.md +++ b/packages/file_selector/file_selector_web/CHANGELOG.md @@ -1,6 +1,5 @@ -## 0.9.1 +## NEXT -* Adds `getSaveLocation` and deprecates `getSavePath`. * Updates minimum supported SDK version to Flutter 3.3/Dart 2.18. ## 0.9.0+4 diff --git a/packages/file_selector/file_selector_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml index 9a7c108a0e4..bc8b984e367 100644 --- a/packages/file_selector/file_selector_web/example/pubspec.yaml +++ b/packages/file_selector/file_selector_web/example/pubspec.yaml @@ -19,8 +19,3 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_web/lib/file_selector_web.dart b/packages/file_selector/file_selector_web/lib/file_selector_web.dart index 2380e274c46..748bb3aa0df 100644 --- a/packages/file_selector/file_selector_web/lib/file_selector_web.dart +++ b/packages/file_selector/file_selector_web/lib/file_selector_web.dart @@ -60,16 +60,6 @@ class FileSelectorWeb extends FileSelectorPlatform { }) async => ''; - @override - Future getSaveLocation({ - List? acceptedTypeGroups, - SaveDialogOptions options = const SaveDialogOptions(), - }) async { - // This is intended to be passed to XFile, which ignores the path, so - // provide a non-null dummy value. - return const FileSaveLocation(''); - } - @override Future getDirectoryPath({ String? initialDirectory, diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 673c5a94c0c..8a601cbb366 100644 --- a/packages/file_selector/file_selector_web/pubspec.yaml +++ b/packages/file_selector/file_selector_web/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_web description: Web platform implementation of file_selector repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.1 +version: 0.9.0+4 environment: sdk: ">=2.18.0 <4.0.0" @@ -26,8 +26,3 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index b747a45723a..35163430e92 100644 --- a/packages/file_selector/file_selector_windows/CHANGELOG.md +++ b/packages/file_selector/file_selector_windows/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.9.3 - -* Adds `getSaveLocation` and deprecates `getSavePath`. - ## 0.9.2 * Adds `getDirectoryPaths` implementation. diff --git a/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart index c92aff05569..174c490c5fc 100644 --- a/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:io'; import 'dart:typed_data'; import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; import 'package:flutter/material.dart'; @@ -18,39 +17,19 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final FileSaveLocation? result = - await FileSelectorPlatform.instance.getSaveLocation( - options: SaveDialogOptions(suggestedName: fileName), - acceptedTypeGroups: const [ - XTypeGroup( - label: 'Plain text', - extensions: ['txt'], - ), - XTypeGroup( - label: 'JSON', - extensions: ['json'], - ), - ], + final String? path = await FileSelectorPlatform.instance.getSavePath( + // Operation was canceled by the user. + suggestedName: fileName, ); - // Operation was canceled by the user. - if (result == null) { + if (path == null) { return; } - String path = result.path; - // Append an extension based on the selected type group if the user didn't - // include one. - if (!path.split(Platform.pathSeparator).last.contains('.')) { - final XTypeGroup? activeGroup = result.activeFilter; - if (activeGroup != null) { - // The group is one of the groups passed in above, each of which has - // exactly one `extensions` entry. - path = '$path.${activeGroup.extensions!.first}'; - } - } final String text = _contentController.text; final Uint8List fileData = Uint8List.fromList(text.codeUnits); - final XFile textFile = XFile.fromData(fileData, name: fileName); - await textFile.saveTo(result.path); + const String fileMimeType = 'text/plain'; + final XFile textFile = + XFile.fromData(fileData, mimeType: fileMimeType, name: fileName); + await textFile.saveTo(path); } @override diff --git a/packages/file_selector/file_selector_windows/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml index 0492eca6921..a22b6dc19aa 100644 --- a/packages/file_selector/file_selector_windows/example/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/example/pubspec.yaml @@ -25,8 +25,3 @@ dev_dependencies: flutter: uses-material-design: true - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../../file_selector/file_selector_platform_interface}} diff --git a/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc b/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc index e5666e0223f..51812dcd487 100644 --- a/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc +++ b/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc @@ -60,14 +60,14 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) -#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER #else -#define VERSION_AS_NUMBER 1,0,0,0 +#define VERSION_AS_NUMBER 1,0,0 #endif -#if defined(FLUTTER_VERSION) -#define VERSION_AS_STRING FLUTTER_VERSION +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME #else #define VERSION_AS_STRING "1.0.0" #endif diff --git a/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart b/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart index 677811afb89..0e935ccdb9d 100644 --- a/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart +++ b/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart @@ -21,7 +21,7 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final FileDialogResult result = await _hostApi.showOpenDialog( + final List paths = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: false, selectFolders: false, @@ -29,7 +29,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return result.paths.isEmpty ? null : XFile(result.paths.first!); + return paths.isEmpty ? null : XFile(paths.first!); } @override @@ -38,7 +38,7 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final FileDialogResult result = await _hostApi.showOpenDialog( + final List paths = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: true, selectFolders: false, @@ -46,7 +46,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return result.paths.map((String? path) => XFile(path!)).toList(); + return paths.map((String? path) => XFile(path!)).toList(); } @override @@ -56,36 +56,16 @@ class FileSelectorWindows extends FileSelectorPlatform { String? suggestedName, String? confirmButtonText, }) async { - return (await getSaveLocation( - acceptedTypeGroups: acceptedTypeGroups, - options: SaveDialogOptions( - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText, - ))) - ?.path; - } - - @override - Future getSaveLocation({ - List? acceptedTypeGroups, - SaveDialogOptions options = const SaveDialogOptions(), - }) async { - final FileDialogResult result = await _hostApi.showSaveDialog( + final List paths = await _hostApi.showSaveDialog( SelectionOptions( allowMultiple: false, selectFolders: false, allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups), ), - options.initialDirectory, - options.suggestedName, - options.confirmButtonText); - final int? groupIndex = result.typeGroupIndex; - return result.paths.isEmpty - ? null - : FileSaveLocation(result.paths.first!, - activeFilter: - groupIndex == null ? null : acceptedTypeGroups?[groupIndex]); + initialDirectory, + suggestedName, + confirmButtonText); + return paths.isEmpty ? null : paths.first!; } @override @@ -93,7 +73,7 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final FileDialogResult result = await _hostApi.showOpenDialog( + final List paths = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: false, selectFolders: true, @@ -101,7 +81,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return result.paths.isEmpty ? null : result.paths.first!; + return paths.isEmpty ? null : paths.first!; } @override @@ -109,7 +89,7 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - final FileDialogResult result = await _hostApi.showOpenDialog( + final List paths = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: true, selectFolders: true, @@ -117,7 +97,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return result.paths.isEmpty ? [] : List.from(result.paths); + return paths.isEmpty ? [] : List.from(paths); } } diff --git a/packages/file_selector/file_selector_windows/lib/src/messages.g.dart b/packages/file_selector/file_selector_windows/lib/src/messages.g.dart index b5b4a794388..a61076b97b3 100644 --- a/packages/file_selector/file_selector_windows/lib/src/messages.g.dart +++ b/packages/file_selector/file_selector_windows/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v10.0.1), do not edit directly. +// Autogenerated from Pigeon (v9.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import @@ -68,52 +68,15 @@ class SelectionOptions { } } -/// The result from an open or save dialog. -class FileDialogResult { - FileDialogResult({ - required this.paths, - this.typeGroupIndex, - }); - - /// The selected paths. - /// - /// Empty if the dialog was canceled. - List paths; - - /// The type group index (into the list provided in [SelectionOptions]) of - /// the group that was selected when the dialog was confirmed. - /// - /// Null if no type groups were provided, or the dialog was canceled. - int? typeGroupIndex; - - Object encode() { - return [ - paths, - typeGroupIndex, - ]; - } - - static FileDialogResult decode(Object result) { - result as List; - return FileDialogResult( - paths: (result[0] as List?)!.cast(), - typeGroupIndex: result[1] as int?, - ); - } -} - class _FileSelectorApiCodec extends StandardMessageCodec { const _FileSelectorApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is FileDialogResult) { + if (value is SelectionOptions) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is SelectionOptions) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); } else if (value is TypeGroup) { - buffer.putUint8(130); + buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -124,10 +87,8 @@ class _FileSelectorApiCodec extends StandardMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: - return FileDialogResult.decode(readValue(buffer)!); - case 129: return SelectionOptions.decode(readValue(buffer)!); - case 130: + case 129: return TypeGroup.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -145,7 +106,7 @@ class FileSelectorApi { static const MessageCodec codec = _FileSelectorApiCodec(); - Future showOpenDialog(SelectionOptions arg_options, + Future> showOpenDialog(SelectionOptions arg_options, String? arg_initialDirectory, String? arg_confirmButtonText) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.FileSelectorApi.showOpenDialog', codec, @@ -170,11 +131,11 @@ class FileSelectorApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as FileDialogResult?)!; + return (replyList[0] as List?)!.cast(); } } - Future showSaveDialog( + Future> showSaveDialog( SelectionOptions arg_options, String? arg_initialDirectory, String? arg_suggestedName, @@ -205,7 +166,7 @@ class FileSelectorApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (replyList[0] as FileDialogResult?)!; + return (replyList[0] as List?)!.cast(); } } } diff --git a/packages/file_selector/file_selector_windows/pigeons/messages.dart b/packages/file_selector/file_selector_windows/pigeons/messages.dart index 8f82aec0b83..c3b3aff192b 100644 --- a/packages/file_selector/file_selector_windows/pigeons/messages.dart +++ b/packages/file_selector/file_selector_windows/pigeons/messages.dart @@ -37,33 +37,14 @@ class SelectionOptions { List allowedTypes; } -/// The result from an open or save dialog. -class FileDialogResult { - FileDialogResult({required this.paths, this.typeGroupIndex}); - - /// The selected paths. - /// - /// Empty if the dialog was canceled. - // TODO(stuartmorgan): Make the generic type non-nullable once supported. - // https://github.com/flutter/flutter/issues/97848 - // The Dart code treats the values as non-nullable. - List paths; - - /// The type group index (into the list provided in [SelectionOptions]) of - /// the group that was selected when the dialog was confirmed. - /// - /// Null if no type groups were provided, or the dialog was canceled. - int? typeGroupIndex; -} - @HostApi(dartHostTestHandler: 'TestFileSelectorApi') abstract class FileSelectorApi { - FileDialogResult showOpenDialog( + List showOpenDialog( SelectionOptions options, String? initialDirectory, String? confirmButtonText, ); - FileDialogResult showSaveDialog( + List showSaveDialog( SelectionOptions options, String? initialDirectory, String? suggestedName, diff --git a/packages/file_selector/file_selector_windows/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index 8b45d914c00..39d5cf7abf6 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_windows description: Windows implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.3 +version: 0.9.2 environment: sdk: ">=2.18.0 <4.0.0" @@ -27,9 +27,4 @@ dev_dependencies: flutter_test: sdk: flutter mockito: 5.4.1 - pigeon: ^10.0.0 - -# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. -# See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#changing-federated-plugins -dependency_overrides: - {file_selector_platform_interface: {path: ../../file_selector/file_selector_platform_interface}} + pigeon: ^9.1.0 diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart index 4f455ee3088..fbe3683af37 100644 --- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart +++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart @@ -30,10 +30,9 @@ void main() { expect(FileSelectorPlatform.instance, isA()); }); - group('openFile', () { + group('#openFile', () { setUp(() { - when(mockApi.showOpenDialog(any, any, any)) - .thenReturn(FileDialogResult(paths: ['foo'])); + when(mockApi.showOpenDialog(any, any, any)).thenReturn(['foo']); }); test('simple call works', () async { @@ -106,10 +105,10 @@ void main() { }); }); - group('openFiles', () { + group('#openFiles', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) - .thenReturn(FileDialogResult(paths: ['foo', 'bar'])); + .thenReturn(['foo', 'bar']); }); test('simple call works', () async { @@ -183,10 +182,9 @@ void main() { }); }); - group('getDirectoryPath', () { + group('#getDirectoryPath', () { setUp(() { - when(mockApi.showOpenDialog(any, any, any)) - .thenReturn(FileDialogResult(paths: ['foo'])); + when(mockApi.showOpenDialog(any, any, any)).thenReturn(['foo']); }); test('simple call works', () async { @@ -213,10 +211,10 @@ void main() { }); }); - group('getDirectoryPaths', () { + group('#getDirectoryPaths', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) - .thenReturn(FileDialogResult(paths: ['foo', 'bar'])); + .thenReturn(['foo', 'bar']); }); test('simple call works', () async { @@ -244,122 +242,10 @@ void main() { }); }); - group('getSaveLocation', () { - setUp(() { - when(mockApi.showSaveDialog(any, any, any, any)) - .thenReturn(FileDialogResult(paths: ['foo'])); - }); - - test('simple call works', () async { - final FileSaveLocation? location = await plugin.getSaveLocation(); - - expect(location?.path, 'foo'); - expect(location?.activeFilter, null); - final VerificationResult result = - verify(mockApi.showSaveDialog(captureAny, null, null, null)); - final SelectionOptions options = result.captured[0] as SelectionOptions; - expect(options.allowMultiple, false); - expect(options.selectFolders, false); - }); - - test('passes the accepted type groups correctly', () async { - const XTypeGroup group = XTypeGroup( - label: 'text', - extensions: ['txt'], - mimeTypes: ['text/plain'], - ); - - const XTypeGroup groupTwo = XTypeGroup( - label: 'image', - extensions: ['jpg'], - mimeTypes: ['image/jpg'], - ); - - await plugin - .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); - - final VerificationResult result = - verify(mockApi.showSaveDialog(captureAny, null, null, null)); - final SelectionOptions options = result.captured[0] as SelectionOptions; - expect( - _typeGroupListsMatch(options.allowedTypes, [ - TypeGroup(label: 'text', extensions: ['txt']), - TypeGroup(label: 'image', extensions: ['jpg']), - ]), - true); - }); - - test('returns the selected type group correctly', () async { - when(mockApi.showSaveDialog(any, any, any, any)).thenReturn( - FileDialogResult(paths: ['foo'], typeGroupIndex: 1)); - const XTypeGroup group = XTypeGroup( - label: 'text', - extensions: ['txt'], - mimeTypes: ['text/plain'], - ); - - const XTypeGroup groupTwo = XTypeGroup( - label: 'image', - extensions: ['jpg'], - mimeTypes: ['image/jpg'], - ); - - final FileSaveLocation? result = await plugin - .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); - - verify(mockApi.showSaveDialog(captureAny, null, null, null)); - - expect(result?.activeFilter, groupTwo); - }); - - test('passes initialDirectory correctly', () async { - await plugin.getSaveLocation( - options: - const SaveDialogOptions(initialDirectory: '/example/directory')); - - verify(mockApi.showSaveDialog(any, '/example/directory', null, null)); - }); - - test('passes suggestedName correctly', () async { - await plugin.getSaveLocation( - options: const SaveDialogOptions(suggestedName: 'baz.txt')); - - verify(mockApi.showSaveDialog(any, null, 'baz.txt', null)); - }); - - test('passes confirmButtonText correctly', () async { - await plugin.getSaveLocation( - options: const SaveDialogOptions(confirmButtonText: 'Save File')); - - verify(mockApi.showSaveDialog(any, null, null, 'Save File')); - }); - - test('throws for a type group that does not support Windows', () async { - const XTypeGroup group = XTypeGroup( - label: 'text', - mimeTypes: ['text/plain'], - ); - - await expectLater( - plugin.getSaveLocation(acceptedTypeGroups: [group]), - throwsArgumentError); - }); - - test('allows a wildcard group', () async { - const XTypeGroup group = XTypeGroup( - label: 'text', - ); - - await expectLater( - plugin.getSaveLocation(acceptedTypeGroups: [group]), - completes); - }); - }); - - group('getSavePath (deprecated)', () { + group('#getSavePath', () { setUp(() { when(mockApi.showSaveDialog(any, any, any, any)) - .thenReturn(FileDialogResult(paths: ['foo'])); + .thenReturn(['foo']); }); test('simple call works', () async { diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart index 7168e0c8d81..ae55f2e301d 100644 --- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart +++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart @@ -1,14 +1,12 @@ -// Mocks generated by Mockito 5.4.1 from annotations +// Mocks generated by Mockito 5.4.0 from annotations // in file_selector_windows/test/file_selector_windows_test.dart. // Do not manually edit this file. -// @dart=2.19 - // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:file_selector_windows/src/messages.g.dart' as _i2; +import 'package:file_selector_windows/src/messages.g.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'test_api.g.dart' as _i3; +import 'test_api.g.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -21,29 +19,18 @@ import 'test_api.g.dart' as _i3; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeFileDialogResult_0 extends _i1.SmartFake - implements _i2.FileDialogResult { - _FakeFileDialogResult_0( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [TestFileSelectorApi]. /// /// See the documentation for Mockito's code generation for more information. class MockTestFileSelectorApi extends _i1.Mock - implements _i3.TestFileSelectorApi { + implements _i2.TestFileSelectorApi { MockTestFileSelectorApi() { _i1.throwOnMissingStub(this); } @override - _i2.FileDialogResult showOpenDialog( - _i2.SelectionOptions? options, + List showOpenDialog( + _i3.SelectionOptions? options, String? initialDirectory, String? confirmButtonText, ) => @@ -56,21 +43,11 @@ class MockTestFileSelectorApi extends _i1.Mock confirmButtonText, ], ), - returnValue: _FakeFileDialogResult_0( - this, - Invocation.method( - #showOpenDialog, - [ - options, - initialDirectory, - confirmButtonText, - ], - ), - ), - ) as _i2.FileDialogResult); + returnValue: [], + ) as List); @override - _i2.FileDialogResult showSaveDialog( - _i2.SelectionOptions? options, + List showSaveDialog( + _i3.SelectionOptions? options, String? initialDirectory, String? suggestedName, String? confirmButtonText, @@ -85,17 +62,6 @@ class MockTestFileSelectorApi extends _i1.Mock confirmButtonText, ], ), - returnValue: _FakeFileDialogResult_0( - this, - Invocation.method( - #showSaveDialog, - [ - options, - initialDirectory, - suggestedName, - confirmButtonText, - ], - ), - ), - ) as _i2.FileDialogResult); + returnValue: [], + ) as List); } diff --git a/packages/file_selector/file_selector_windows/test/test_api.g.dart b/packages/file_selector/file_selector_windows/test/test_api.g.dart index 778ae4fc16c..f9ed8e5b63d 100644 --- a/packages/file_selector/file_selector_windows/test/test_api.g.dart +++ b/packages/file_selector/file_selector_windows/test/test_api.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v10.0.1), do not edit directly. +// Autogenerated from Pigeon (v9.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import // ignore_for_file: avoid_relative_lib_imports @@ -17,14 +17,11 @@ class _TestFileSelectorApiCodec extends StandardMessageCodec { const _TestFileSelectorApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is FileDialogResult) { + if (value is SelectionOptions) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is SelectionOptions) { - buffer.putUint8(129); - writeValue(buffer, value.encode()); } else if (value is TypeGroup) { - buffer.putUint8(130); + buffer.putUint8(129); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -35,10 +32,8 @@ class _TestFileSelectorApiCodec extends StandardMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: - return FileDialogResult.decode(readValue(buffer)!); - case 129: return SelectionOptions.decode(readValue(buffer)!); - case 130: + case 129: return TypeGroup.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -47,14 +42,12 @@ class _TestFileSelectorApiCodec extends StandardMessageCodec { } abstract class TestFileSelectorApi { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => - TestDefaultBinaryMessengerBinding.instance; static const MessageCodec codec = _TestFileSelectorApiCodec(); - FileDialogResult showOpenDialog(SelectionOptions options, + List showOpenDialog(SelectionOptions options, String? initialDirectory, String? confirmButtonText); - FileDialogResult showSaveDialog( + List showSaveDialog( SelectionOptions options, String? initialDirectory, String? suggestedName, @@ -67,12 +60,9 @@ abstract class TestFileSelectorApi { 'dev.flutter.pigeon.FileSelectorApi.showOpenDialog', codec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + channel.setMockMessageHandler(null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, - (Object? message) async { + channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.showOpenDialog was null.'); final List args = (message as List?)!; @@ -81,7 +71,7 @@ abstract class TestFileSelectorApi { 'Argument for dev.flutter.pigeon.FileSelectorApi.showOpenDialog was null, expected non-null SelectionOptions.'); final String? arg_initialDirectory = (args[1] as String?); final String? arg_confirmButtonText = (args[2] as String?); - final FileDialogResult output = api.showOpenDialog( + final List output = api.showOpenDialog( arg_options!, arg_initialDirectory, arg_confirmButtonText); return [output]; }); @@ -92,12 +82,9 @@ abstract class TestFileSelectorApi { 'dev.flutter.pigeon.FileSelectorApi.showSaveDialog', codec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, null); + channel.setMockMessageHandler(null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(channel, - (Object? message) async { + channel.setMockMessageHandler((Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.FileSelectorApi.showSaveDialog was null.'); final List args = (message as List?)!; @@ -107,7 +94,7 @@ abstract class TestFileSelectorApi { final String? arg_initialDirectory = (args[1] as String?); final String? arg_suggestedName = (args[2] as String?); final String? arg_confirmButtonText = (args[3] as String?); - final FileDialogResult output = api.showSaveDialog(arg_options!, + final List output = api.showSaveDialog(arg_options!, arg_initialDirectory, arg_suggestedName, arg_confirmButtonText); return [output]; }); diff --git a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp index af2a9affecc..5820c4a5da4 100644 --- a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp +++ b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp @@ -51,10 +51,6 @@ HRESULT FileDialogController::GetResult(IShellItem** out_item) const { return dialog_->GetResult(out_item); } -HRESULT FileDialogController::GetFileTypeIndex(UINT* out_index) const { - return dialog_->GetFileTypeIndex(out_index); -} - HRESULT FileDialogController::GetResults(IShellItemArray** out_items) const { IFileOpenDialogPtr open_dialog; HRESULT result = dialog_->QueryInterface(IID_PPV_ARGS(&open_dialog)); diff --git a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h index ab4929287e9..f5c93974cbe 100644 --- a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h +++ b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h @@ -38,7 +38,6 @@ class FileDialogController { virtual HRESULT SetOptions(FILEOPENDIALOGOPTIONS options); virtual HRESULT Show(HWND parent); virtual HRESULT GetResult(IShellItem** out_item) const; - virtual HRESULT GetFileTypeIndex(UINT* out_index) const; // IFileOpenDialog wrapper. This will fail if the IFileDialog* provided to the // constructor was not an IFileOpenDialog instance. diff --git a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp index 35697983108..b9e6d211b2d 100644 --- a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp +++ b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp @@ -29,8 +29,8 @@ namespace file_selector_windows { namespace { -using flutter::CustomEncodableValue; using flutter::EncodableList; +using flutter::EncodableMap; using flutter::EncodableValue; // The kind of file dialog to show. @@ -137,7 +137,7 @@ class DialogWrapper { for (const EncodableValue& filter_info_value : filters) { const auto& type_group = std::any_cast( - std::get(filter_info_value)); + std::get(filter_info_value)); filter_names.push_back(Utf16FromUtf8(type_group.label())); filter_extensions.push_back(L""); std::wstring& spec = filter_extensions.back(); @@ -158,8 +158,8 @@ class DialogWrapper { static_cast(filter_specs.size()), filter_specs.data()); } - // Displays the dialog, and returns the result, or nullopt on error. - std::optional Show(HWND parent_window) { + // Displays the dialog, and returns the selected files, or nullopt on error. + std::optional Show(HWND parent_window) { assert(dialog_controller_); last_result_ = dialog_controller_->Show(parent_window); if (!SUCCEEDED(last_result_)) { @@ -190,14 +190,7 @@ class DialogWrapper { } files.push_back(EncodableValue(GetPathForShellItem(shell_item))); } - FileDialogResult result(files, nullptr); - UINT file_type_index; - if (SUCCEEDED(dialog_controller_->GetFileTypeIndex(&file_type_index)) && - file_type_index > 0) { - // Convert from the one-based index to a Dart index. - result.set_type_group_index(file_type_index - 1); - } - return result; + return files; } // Returns the result of the last Win32 API call related to this object. @@ -212,7 +205,7 @@ class DialogWrapper { HRESULT last_result_; }; -ErrorOr ShowDialog( +ErrorOr ShowDialog( const FileDialogControllerFactory& dialog_factory, HWND parent_window, DialogMode mode, const SelectionOptions& options, const std::string* initial_directory, const std::string* suggested_name, @@ -250,16 +243,16 @@ ErrorOr ShowDialog( dialog.SetFileTypeFilters(options.allowed_types()); } - std::optional result = dialog.Show(parent_window); - if (!result) { + std::optional files = dialog.Show(parent_window); + if (!files) { if (dialog.last_result() != HRESULT_FROM_WIN32(ERROR_CANCELLED)) { return FlutterError("System error", "Could not show dialog", EncodableValue(dialog.last_result())); } else { - return FileDialogResult(EncodableList(), nullptr); + return EncodableList(); } } - return std::move(result.value()); + return std::move(files.value()); } // Returns the top-level window that owns |view|. @@ -289,14 +282,14 @@ FileSelectorPlugin::FileSelectorPlugin( FileSelectorPlugin::~FileSelectorPlugin() = default; -ErrorOr FileSelectorPlugin::ShowOpenDialog( +ErrorOr FileSelectorPlugin::ShowOpenDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* confirmButtonText) { return ShowDialog(*controller_factory_, get_root_window_(), DialogMode::open, options, initialDirectory, nullptr, confirmButtonText); } -ErrorOr FileSelectorPlugin::ShowSaveDialog( +ErrorOr FileSelectorPlugin::ShowSaveDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* suggestedName, const std::string* confirmButtonText) { return ShowDialog(*controller_factory_, get_root_window_(), DialogMode::save, diff --git a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h index 2f17f949049..1388bfd3898 100644 --- a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h +++ b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h @@ -32,10 +32,10 @@ class FileSelectorPlugin : public flutter::Plugin, public FileSelectorApi { virtual ~FileSelectorPlugin(); // FileSelectorApi - ErrorOr ShowOpenDialog( + ErrorOr ShowOpenDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* confirm_button_text) override; - ErrorOr ShowSaveDialog( + ErrorOr ShowSaveDialog( const SelectionOptions& options, const std::string* initialDirectory, const std::string* suggestedName, const std::string* confirmButtonText) override; diff --git a/packages/file_selector/file_selector_windows/windows/messages.g.cpp b/packages/file_selector/file_selector_windows/windows/messages.g.cpp index a60fd92a974..24b831e292e 100644 --- a/packages/file_selector/file_selector_windows/windows/messages.g.cpp +++ b/packages/file_selector/file_selector_windows/windows/messages.g.cpp @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v10.0.1), do not edit directly. +// Autogenerated from Pigeon (v9.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #undef _HAS_EXCEPTIONS @@ -26,15 +26,10 @@ using flutter::EncodableValue; // TypeGroup -TypeGroup::TypeGroup(const std::string& label, const EncodableList& extensions) - : label_(label), extensions_(extensions) {} - const std::string& TypeGroup::label() const { return label_; } - void TypeGroup::set_label(std::string_view value_arg) { label_ = value_arg; } const EncodableList& TypeGroup::extensions() const { return extensions_; } - void TypeGroup::set_extensions(const EncodableList& value_arg) { extensions_ = value_arg; } @@ -47,28 +42,29 @@ EncodableList TypeGroup::ToEncodableList() const { return list; } -TypeGroup TypeGroup::FromEncodableList(const EncodableList& list) { - TypeGroup decoded(std::get(list[0]), - std::get(list[1])); - return decoded; +TypeGroup::TypeGroup() {} + +TypeGroup::TypeGroup(const EncodableList& list) { + auto& encodable_label = list[0]; + if (const std::string* pointer_label = + std::get_if(&encodable_label)) { + label_ = *pointer_label; + } + auto& encodable_extensions = list[1]; + if (const EncodableList* pointer_extensions = + std::get_if(&encodable_extensions)) { + extensions_ = *pointer_extensions; + } } // SelectionOptions -SelectionOptions::SelectionOptions(bool allow_multiple, bool select_folders, - const EncodableList& allowed_types) - : allow_multiple_(allow_multiple), - select_folders_(select_folders), - allowed_types_(allowed_types) {} - bool SelectionOptions::allow_multiple() const { return allow_multiple_; } - void SelectionOptions::set_allow_multiple(bool value_arg) { allow_multiple_ = value_arg; } bool SelectionOptions::select_folders() const { return select_folders_; } - void SelectionOptions::set_select_folders(bool value_arg) { select_folders_ = value_arg; } @@ -76,7 +72,6 @@ void SelectionOptions::set_select_folders(bool value_arg) { const EncodableList& SelectionOptions::allowed_types() const { return allowed_types_; } - void SelectionOptions::set_allowed_types(const EncodableList& value_arg) { allowed_types_ = value_arg; } @@ -90,77 +85,36 @@ EncodableList SelectionOptions::ToEncodableList() const { return list; } -SelectionOptions SelectionOptions::FromEncodableList( - const EncodableList& list) { - SelectionOptions decoded(std::get(list[0]), std::get(list[1]), - std::get(list[2])); - return decoded; -} - -// FileDialogResult - -FileDialogResult::FileDialogResult(const EncodableList& paths) - : paths_(paths) {} - -FileDialogResult::FileDialogResult(const EncodableList& paths, - const int64_t* type_group_index) - : paths_(paths), - type_group_index_(type_group_index - ? std::optional(*type_group_index) - : std::nullopt) {} - -const EncodableList& FileDialogResult::paths() const { return paths_; } - -void FileDialogResult::set_paths(const EncodableList& value_arg) { - paths_ = value_arg; -} - -const int64_t* FileDialogResult::type_group_index() const { - return type_group_index_ ? &(*type_group_index_) : nullptr; -} - -void FileDialogResult::set_type_group_index(const int64_t* value_arg) { - type_group_index_ = - value_arg ? std::optional(*value_arg) : std::nullopt; -} +SelectionOptions::SelectionOptions() {} -void FileDialogResult::set_type_group_index(int64_t value_arg) { - type_group_index_ = value_arg; -} - -EncodableList FileDialogResult::ToEncodableList() const { - EncodableList list; - list.reserve(2); - list.push_back(EncodableValue(paths_)); - list.push_back(type_group_index_ ? EncodableValue(*type_group_index_) - : EncodableValue()); - return list; -} - -FileDialogResult FileDialogResult::FromEncodableList( - const EncodableList& list) { - FileDialogResult decoded(std::get(list[0])); - auto& encodable_type_group_index = list[1]; - if (!encodable_type_group_index.IsNull()) { - decoded.set_type_group_index(encodable_type_group_index.LongValue()); +SelectionOptions::SelectionOptions(const EncodableList& list) { + auto& encodable_allow_multiple = list[0]; + if (const bool* pointer_allow_multiple = + std::get_if(&encodable_allow_multiple)) { + allow_multiple_ = *pointer_allow_multiple; + } + auto& encodable_select_folders = list[1]; + if (const bool* pointer_select_folders = + std::get_if(&encodable_select_folders)) { + select_folders_ = *pointer_select_folders; + } + auto& encodable_allowed_types = list[2]; + if (const EncodableList* pointer_allowed_types = + std::get_if(&encodable_allowed_types)) { + allowed_types_ = *pointer_allowed_types; } - return decoded; } FileSelectorApiCodecSerializer::FileSelectorApiCodecSerializer() {} - EncodableValue FileSelectorApiCodecSerializer::ReadValueOfType( uint8_t type, flutter::ByteStreamReader* stream) const { switch (type) { case 128: - return CustomEncodableValue(FileDialogResult::FromEncodableList( - std::get(ReadValue(stream)))); + return CustomEncodableValue( + SelectionOptions(std::get(ReadValue(stream)))); case 129: - return CustomEncodableValue(SelectionOptions::FromEncodableList( - std::get(ReadValue(stream)))); - case 130: - return CustomEncodableValue(TypeGroup::FromEncodableList( - std::get(ReadValue(stream)))); + return CustomEncodableValue( + TypeGroup(std::get(ReadValue(stream)))); default: return flutter::StandardCodecSerializer::ReadValueOfType(type, stream); } @@ -170,16 +124,8 @@ void FileSelectorApiCodecSerializer::WriteValue( const EncodableValue& value, flutter::ByteStreamWriter* stream) const { if (const CustomEncodableValue* custom_value = std::get_if(&value)) { - if (custom_value->type() == typeid(FileDialogResult)) { - stream->WriteByte(128); - WriteValue( - EncodableValue( - std::any_cast(*custom_value).ToEncodableList()), - stream); - return; - } if (custom_value->type() == typeid(SelectionOptions)) { - stream->WriteByte(129); + stream->WriteByte(128); WriteValue( EncodableValue( std::any_cast(*custom_value).ToEncodableList()), @@ -187,7 +133,7 @@ void FileSelectorApiCodecSerializer::WriteValue( return; } if (custom_value->type() == typeid(TypeGroup)) { - stream->WriteByte(130); + stream->WriteByte(129); WriteValue(EncodableValue( std::any_cast(*custom_value).ToEncodableList()), stream); @@ -230,15 +176,14 @@ void FileSelectorApi::SetUp(flutter::BinaryMessenger* binary_messenger, const auto& encodable_confirm_button_text_arg = args.at(2); const auto* confirm_button_text_arg = std::get_if(&encodable_confirm_button_text_arg); - ErrorOr output = api->ShowOpenDialog( + ErrorOr output = api->ShowOpenDialog( options_arg, initial_directory_arg, confirm_button_text_arg); if (output.has_error()) { reply(WrapError(output.error())); return; } EncodableList wrapped; - wrapped.push_back( - CustomEncodableValue(std::move(output).TakeValue())); + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -274,7 +219,7 @@ void FileSelectorApi::SetUp(flutter::BinaryMessenger* binary_messenger, const auto& encodable_confirm_button_text_arg = args.at(3); const auto* confirm_button_text_arg = std::get_if(&encodable_confirm_button_text_arg); - ErrorOr output = api->ShowSaveDialog( + ErrorOr output = api->ShowSaveDialog( options_arg, initial_directory_arg, suggested_name_arg, confirm_button_text_arg); if (output.has_error()) { @@ -282,8 +227,7 @@ void FileSelectorApi::SetUp(flutter::BinaryMessenger* binary_messenger, return; } EncodableList wrapped; - wrapped.push_back( - CustomEncodableValue(std::move(output).TakeValue())); + wrapped.push_back(EncodableValue(std::move(output).TakeValue())); reply(EncodableValue(std::move(wrapped))); } catch (const std::exception& exception) { reply(WrapError(exception.what())); @@ -300,7 +244,6 @@ EncodableValue FileSelectorApi::WrapError(std::string_view error_message) { EncodableList{EncodableValue(std::string(error_message)), EncodableValue("Error"), EncodableValue()}); } - EncodableValue FileSelectorApi::WrapError(const FlutterError& error) { return EncodableValue(EncodableList{EncodableValue(error.code()), EncodableValue(error.message()), diff --git a/packages/file_selector/file_selector_windows/windows/messages.g.h b/packages/file_selector/file_selector_windows/windows/messages.g.h index ab8afd7d703..248ca89c977 100644 --- a/packages/file_selector/file_selector_windows/windows/messages.g.h +++ b/packages/file_selector/file_selector_windows/windows/messages.g.h @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v10.0.1), do not edit directly. +// Autogenerated from Pigeon (v9.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_MESSAGES_G_H_ @@ -41,10 +41,10 @@ class FlutterError { template class ErrorOr { public: - ErrorOr(const T& rhs) : v_(rhs) {} - ErrorOr(const T&& rhs) : v_(std::move(rhs)) {} - ErrorOr(const FlutterError& rhs) : v_(rhs) {} - ErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {} + ErrorOr(const T& rhs) { new (&v_) T(rhs); } + ErrorOr(const T&& rhs) { v_ = std::move(rhs); } + ErrorOr(const FlutterError& rhs) { new (&v_) FlutterError(rhs); } + ErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); } bool has_error() const { return std::holds_alternative(v_); } const T& value() const { return std::get(v_); }; @@ -61,10 +61,7 @@ class ErrorOr { // Generated class from Pigeon that represents data sent in messages. class TypeGroup { public: - // Constructs an object setting all fields. - explicit TypeGroup(const std::string& label, - const flutter::EncodableList& extensions); - + TypeGroup(); const std::string& label() const; void set_label(std::string_view value_arg); @@ -72,7 +69,7 @@ class TypeGroup { void set_extensions(const flutter::EncodableList& value_arg); private: - static TypeGroup FromEncodableList(const flutter::EncodableList& list); + TypeGroup(const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class FileSelectorApi; friend class FileSelectorApiCodecSerializer; @@ -83,10 +80,7 @@ class TypeGroup { // Generated class from Pigeon that represents data sent in messages. class SelectionOptions { public: - // Constructs an object setting all fields. - explicit SelectionOptions(bool allow_multiple, bool select_folders, - const flutter::EncodableList& allowed_types); - + SelectionOptions(); bool allow_multiple() const; void set_allow_multiple(bool value_arg); @@ -97,7 +91,7 @@ class SelectionOptions { void set_allowed_types(const flutter::EncodableList& value_arg); private: - static SelectionOptions FromEncodableList(const flutter::EncodableList& list); + SelectionOptions(const flutter::EncodableList& list); flutter::EncodableList ToEncodableList() const; friend class FileSelectorApi; friend class FileSelectorApiCodecSerializer; @@ -106,49 +100,16 @@ class SelectionOptions { flutter::EncodableList allowed_types_; }; -// The result from an open or save dialog. -// -// Generated class from Pigeon that represents data sent in messages. -class FileDialogResult { - public: - // Constructs an object setting all non-nullable fields. - explicit FileDialogResult(const flutter::EncodableList& paths); - - // Constructs an object setting all fields. - explicit FileDialogResult(const flutter::EncodableList& paths, - const int64_t* type_group_index); - - // The selected paths. - // - // Empty if the dialog was canceled. - const flutter::EncodableList& paths() const; - void set_paths(const flutter::EncodableList& value_arg); - - // The type group index (into the list provided in [SelectionOptions]) of - // the group that was selected when the dialog was confirmed. - // - // Null if no type groups were provided, or the dialog was canceled. - const int64_t* type_group_index() const; - void set_type_group_index(const int64_t* value_arg); - void set_type_group_index(int64_t value_arg); - - private: - static FileDialogResult FromEncodableList(const flutter::EncodableList& list); - flutter::EncodableList ToEncodableList() const; - friend class FileSelectorApi; - friend class FileSelectorApiCodecSerializer; - flutter::EncodableList paths_; - std::optional type_group_index_; -}; - class FileSelectorApiCodecSerializer : public flutter::StandardCodecSerializer { public: - FileSelectorApiCodecSerializer(); inline static FileSelectorApiCodecSerializer& GetInstance() { static FileSelectorApiCodecSerializer sInstance; return sInstance; } + FileSelectorApiCodecSerializer(); + + public: void WriteValue(const flutter::EncodableValue& value, flutter::ByteStreamWriter* stream) const override; @@ -164,10 +125,10 @@ class FileSelectorApi { FileSelectorApi(const FileSelectorApi&) = delete; FileSelectorApi& operator=(const FileSelectorApi&) = delete; virtual ~FileSelectorApi() {} - virtual ErrorOr ShowOpenDialog( + virtual ErrorOr ShowOpenDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* confirm_button_text) = 0; - virtual ErrorOr ShowSaveDialog( + virtual ErrorOr ShowSaveDialog( const SelectionOptions& options, const std::string* initial_directory, const std::string* suggested_name, const std::string* confirm_button_text) = 0; diff --git a/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp b/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp index ee6d4dcded7..8efeb54f860 100644 --- a/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp +++ b/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp @@ -28,8 +28,37 @@ namespace { using flutter::CustomEncodableValue; using flutter::EncodableList; +using flutter::EncodableMap; using flutter::EncodableValue; +// These structs and classes are a workaround for +// https://github.com/flutter/flutter/issues/104286 and +// https://github.com/flutter/flutter/issues/104653. +struct AllowMultipleArg { + bool value = false; + AllowMultipleArg(bool val) : value(val) {} +}; +struct SelectFoldersArg { + bool value = false; + SelectFoldersArg(bool val) : value(val) {} +}; +SelectionOptions CreateOptions(AllowMultipleArg allow_multiple, + SelectFoldersArg select_folders, + const EncodableList& allowed_types) { + SelectionOptions options; + options.set_allow_multiple(allow_multiple.value); + options.set_select_folders(select_folders.value); + options.set_allowed_types(allowed_types); + return options; +} +TypeGroup CreateTypeGroup(std::string_view label, + const EncodableList& extensions) { + TypeGroup group; + group.set_label(label); + group.set_extensions(extensions); + return group; +} + } // namespace TEST(FileSelectorPlugin, TestOpenSimple) { @@ -58,18 +87,17 @@ TEST(FileSelectorPlugin, TestOpenSimple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - SelectionOptions(/* allow multiple = */ false, - /* select folders = */ false, EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), + EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 1); + const EncodableList& paths = result.value(); + EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); - EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestOpenWithArguments) { @@ -101,18 +129,17 @@ TEST(FileSelectorPlugin, TestOpenWithArguments) { // This directory must exist. std::string initial_directory("C:\\Program Files"); std::string confirm_button("Open it!"); - ErrorOr result = plugin.ShowOpenDialog( - SelectionOptions(/* allow multiple = */ false, - /* select folders = */ false, EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), + EncodableList()), &initial_directory, &confirm_button); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 1); + const EncodableList& paths = result.value(); + EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); - EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestOpenMultiple) { @@ -146,20 +173,19 @@ TEST(FileSelectorPlugin, TestOpenMultiple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - SelectionOptions(/* allow multiple = */ true, - /* select folders = */ false, EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + CreateOptions(AllowMultipleArg(true), SelectFoldersArg(false), + EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 2); + const EncodableList& paths = result.value(); + EXPECT_EQ(paths.size(), 2); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file_1.path())); EXPECT_EQ(std::get(paths[1]), Utf8FromUtf16(fake_selected_file_2.path())); - EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestOpenWithFilter) { @@ -170,18 +196,18 @@ TEST(FileSelectorPlugin, TestOpenWithFilter) { IID_PPV_ARGS(&fake_result_array)); const EncodableValue text_group = - CustomEncodableValue(TypeGroup("Text", EncodableList({ - EncodableValue("txt"), - EncodableValue("json"), - }))); + CustomEncodableValue(CreateTypeGroup("Text", EncodableList({ + EncodableValue("txt"), + EncodableValue("json"), + }))); const EncodableValue image_group = - CustomEncodableValue(TypeGroup("Images", EncodableList({ - EncodableValue("png"), - EncodableValue("gif"), - EncodableValue("jpeg"), - }))); + CustomEncodableValue(CreateTypeGroup("Images", EncodableList({ + EncodableValue("png"), + EncodableValue("gif"), + EncodableValue("jpeg"), + }))); const EncodableValue any_group = - CustomEncodableValue(TypeGroup("Any", EncodableList())); + CustomEncodableValue(CreateTypeGroup("Any", EncodableList())); bool shown = false; MockShow show_validator = [&shown, fake_result_array, fake_window]( @@ -208,26 +234,21 @@ TEST(FileSelectorPlugin, TestOpenWithFilter) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = - plugin.ShowOpenDialog(SelectionOptions(/* allow multiple = */ false, - /* select folders = */ false, - EncodableList({ - text_group, - image_group, - any_group, - })), - nullptr, nullptr); + ErrorOr result = plugin.ShowOpenDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), + EncodableList({ + text_group, + image_group, + any_group, + })), + nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 1); + const EncodableList& paths = result.value(); + EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); - // The test dialog controller always reports the last group as - // selected, so that should be what the plugin returns. - ASSERT_NE(result.value().type_group_index(), nullptr); - EXPECT_EQ(*(result.value().type_group_index()), 2); } TEST(FileSelectorPlugin, TestOpenCancel) { @@ -244,16 +265,15 @@ TEST(FileSelectorPlugin, TestOpenCancel) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - SelectionOptions(/* allow multiple = */ false, - /* select folders = */ false, EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), + EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); + const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 0); - EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestSaveSimple) { @@ -279,18 +299,17 @@ TEST(FileSelectorPlugin, TestSaveSimple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowSaveDialog( - SelectionOptions(/* allow multiple = */ false, - /* select folders = */ false, EncodableList()), + ErrorOr result = plugin.ShowSaveDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), + EncodableList()), nullptr, nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 1); + const EncodableList& paths = result.value(); + EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); - EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestSaveWithArguments) { @@ -322,78 +341,17 @@ TEST(FileSelectorPlugin, TestSaveWithArguments) { std::string initial_directory("C:\\Program Files"); std::string suggested_name("a name"); std::string confirm_button("Save it!"); - ErrorOr result = plugin.ShowSaveDialog( - SelectionOptions(/* allow multiple = */ false, - /* select folders = */ false, EncodableList()), + ErrorOr result = plugin.ShowSaveDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), + EncodableList()), &initial_directory, &suggested_name, &confirm_button); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 1); - EXPECT_EQ(std::get(paths[0]), - Utf8FromUtf16(fake_selected_file.path())); - EXPECT_EQ(result.value().type_group_index(), nullptr); -} - -TEST(FileSelectorPlugin, TestSaveWithFilter) { - const HWND fake_window = reinterpret_cast(1337); - ScopedTestShellItem fake_selected_file; - - const EncodableValue text_group = - CustomEncodableValue(TypeGroup("Text", EncodableList({ - EncodableValue("txt"), - EncodableValue("json"), - }))); - const EncodableValue image_group = - CustomEncodableValue(TypeGroup("Images", EncodableList({ - EncodableValue("png"), - EncodableValue("gif"), - EncodableValue("jpeg"), - }))); - - bool shown = false; - MockShow show_validator = - [&shown, fake_result = fake_selected_file.file(), fake_window]( - const TestFileDialogController& dialog, HWND parent) { - shown = true; - EXPECT_EQ(parent, fake_window); - - // Validate filter. - const std::vector& filters = dialog.GetFileTypes(); - EXPECT_EQ(filters.size(), 2U); - if (filters.size() == 2U) { - EXPECT_EQ(filters[0].name, L"Text"); - EXPECT_EQ(filters[0].spec, L"*.txt;*.json"); - EXPECT_EQ(filters[1].name, L"Images"); - EXPECT_EQ(filters[1].spec, L"*.png;*.gif;*.jpeg"); - } - - return MockShowResult(fake_result); - }; - - FileSelectorPlugin plugin( - [fake_window] { return fake_window; }, - std::make_unique(show_validator)); - ErrorOr result = - plugin.ShowSaveDialog(SelectionOptions(/* allow multiple = */ false, - /* select folders = */ false, - EncodableList({ - text_group, - image_group, - })), - nullptr, nullptr, nullptr); - - EXPECT_TRUE(shown); - ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 1); + const EncodableList& paths = result.value(); + EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); - // The test dialog controller always reports the last group as - // selected, so that should be what the plugin returns. - ASSERT_NE(result.value().type_group_index(), nullptr); - EXPECT_EQ(*(result.value().type_group_index()), 1); } TEST(FileSelectorPlugin, TestSaveCancel) { @@ -410,16 +368,15 @@ TEST(FileSelectorPlugin, TestSaveCancel) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowSaveDialog( - SelectionOptions(/* allow multiple = */ false, - /* select folders = */ false, EncodableList()), + ErrorOr result = plugin.ShowSaveDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false), + EncodableList()), nullptr, nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); + const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 0); - EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestGetDirectorySimple) { @@ -451,17 +408,16 @@ TEST(FileSelectorPlugin, TestGetDirectorySimple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - SelectionOptions(/* allow multiple = */ false, - /* select folders = */ true, EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(true), + EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 1); + const EncodableList& paths = result.value(); + EXPECT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), "C:\\Program Files"); - EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestGetDirectoryMultiple) { @@ -498,20 +454,19 @@ TEST(FileSelectorPlugin, TestGetDirectoryMultiple) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - SelectionOptions(/* allow multiple = */ true, /* select folders = */ true, - EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + CreateOptions(AllowMultipleArg(true), SelectFoldersArg(true), + EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); - ASSERT_EQ(paths.size(), 2); + const EncodableList& paths = result.value(); + EXPECT_EQ(paths.size(), 2); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_dir_1.path())); EXPECT_EQ(std::get(paths[1]), Utf8FromUtf16(fake_selected_dir_2.path())); - EXPECT_EQ(result.value().type_group_index(), nullptr); } TEST(FileSelectorPlugin, TestGetDirectoryCancel) { @@ -528,16 +483,15 @@ TEST(FileSelectorPlugin, TestGetDirectoryCancel) { FileSelectorPlugin plugin( [fake_window] { return fake_window; }, std::make_unique(show_validator)); - ErrorOr result = plugin.ShowOpenDialog( - SelectionOptions(/* allow multiple = */ false, - /* select folders = */ true, EncodableList()), + ErrorOr result = plugin.ShowOpenDialog( + CreateOptions(AllowMultipleArg(false), SelectFoldersArg(true), + EncodableList()), nullptr, nullptr); EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); - const EncodableList& paths = result.value().paths(); + const EncodableList& paths = result.value(); EXPECT_EQ(paths.size(), 0); - EXPECT_EQ(result.value().type_group_index(), nullptr); } } // namespace test diff --git a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp index e775aa627aa..15065f916c8 100644 --- a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp +++ b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp @@ -60,13 +60,6 @@ HRESULT TestFileDialogController::GetResult(IShellItem** out_item) const { return S_OK; } -HRESULT TestFileDialogController::GetFileTypeIndex(UINT* out_index) const { - // Arbitrarily always return the last group. (No -1 because the return value - // from GetFileTypeIndex is defined to be one-indexed.) - *out_index = static_cast(filter_groups_.size()); - return S_OK; -} - HRESULT TestFileDialogController::GetResults( IShellItemArray** out_items) const { *out_items = std::get(mock_result_); diff --git a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h index e3aa0936e76..1c221fc219f 100644 --- a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h +++ b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h @@ -56,7 +56,6 @@ class TestFileDialogController : public FileDialogController { HRESULT SetOkButtonLabel(const wchar_t* text) override; HRESULT Show(HWND parent) override; HRESULT GetResult(IShellItem** out_item) const override; - HRESULT GetFileTypeIndex(UINT* out_index) const override; HRESULT GetResults(IShellItemArray** out_items) const override; // Accessors for validating IFileDialogController setter calls. From 381716ff132537527971a3b6a141a76cc6373449 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 20 Jun 2023 09:23:17 -0400 Subject: [PATCH 3/3] temp ignores for deprecation --- packages/file_selector/file_selector/lib/file_selector.dart | 3 +++ .../file_selector_linux/example/lib/save_text_page.dart | 3 +++ .../file_selector_macos/example/lib/save_text_page.dart | 3 +++ .../file_selector_windows/example/lib/save_text_page.dart | 3 +++ 4 files changed, 12 insertions(+) diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index c2249565c9e..c75cb582335 100644 --- a/packages/file_selector/file_selector/lib/file_selector.dart +++ b/packages/file_selector/file_selector/lib/file_selector.dart @@ -98,6 +98,9 @@ Future getSavePath({ String? suggestedName, String? confirmButtonText, }) async { + // TODO(stuartmorgan): Update this to getSaveLocation in the next federated + // change PR. + // ignore: deprecated_member_use return FileSelectorPlatform.instance.getSavePath( acceptedTypeGroups: acceptedTypeGroups, initialDirectory: initialDirectory, diff --git a/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart index 174c490c5fc..2d259a62f49 100644 --- a/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart @@ -17,6 +17,9 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; + // TODO(stuartmorgan): Update this to getSaveLocation in the next federated + // change PR. + // ignore: deprecated_member_use final String? path = await FileSelectorPlatform.instance.getSavePath( // Operation was canceled by the user. suggestedName: fileName, diff --git a/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart index 79d9c83c831..84180ac51df 100644 --- a/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart @@ -17,6 +17,9 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; + // TODO(stuartmorgan): Update this to getSaveLocation in the next federated + // change PR. + // ignore: deprecated_member_use final String? path = await FileSelectorPlatform.instance.getSavePath( suggestedName: fileName, ); diff --git a/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart index 174c490c5fc..2d259a62f49 100644 --- a/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart @@ -17,6 +17,9 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; + // TODO(stuartmorgan): Update this to getSaveLocation in the next federated + // change PR. + // ignore: deprecated_member_use final String? path = await FileSelectorPlatform.instance.getSavePath( // Operation was canceled by the user. suggestedName: fileName,