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/README.md b/packages/file_selector/file_selector/README.md index 860be7fbe9d..dac9746891b 100644 --- a/packages/file_selector/file_selector/README.md +++ b/packages/file_selector/file_selector/README.md @@ -68,8 +68,9 @@ final List files = await openFiles(acceptedTypeGroups: [ ```dart 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; } @@ -78,7 +79,7 @@ final Uint8List fileData = Uint8List.fromList('Hello World!'.codeUnits); const String mimeType = 'text/plain'; final XFile textFile = XFile.fromData(fileData, mimeType: mimeType, name: fileName); -await textFile.saveTo(path); +await textFile.saveTo(result.path); ``` #### Get a directory path 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/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index c75cb582335..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,20 +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 { - // TODO(stuartmorgan): Update this to getSaveLocation in the next federated - // change PR. - // ignore: deprecated_member_use - 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..6b32deecc33 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" @@ -25,11 +25,11 @@ flutter: dependencies: file_selector_ios: ^0.5.0 - file_selector_linux: ^0.9.1 - file_selector_macos: ^0.9.1 - file_selector_platform_interface: ^2.3.0 - file_selector_web: ^0.9.0 - file_selector_windows: ^0.9.2 + file_selector_linux: ^0.9.2 + file_selector_macos: ^0.9.3 + file_selector_platform_interface: ^2.6.0 + file_selector_web: ^0.9.1 + file_selector_windows: ^0.9.3 flutter: sdk: flutter 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