From fe1b7be5c5d22f8860efeb87089c5a654a0a9c05 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 5 May 2023 14:58:46 -0400 Subject: [PATCH 01/16] WIP --- .../file_selector_interface.dart | 27 ++++++++++++++++--- .../lib/src/types/file_dialog_options.dart | 14 ++++++++++ .../src/types/file_save_location_result.dart | 16 +++++++++++ .../lib/src/types/types.dart | 2 ++ 4 files changed, 55 insertions(+), 4 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_result.dart 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 ad4fe617e44e..0839cecd89e3 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'; @@ -55,7 +53,9 @@ 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. + /// Opens a file dialog for saving files and returns a file path at which to + /// save. + /// /// Returns `null` if user cancels the operation. Future getSavePath({ List? acceptedTypeGroups, @@ -66,7 +66,25 @@ 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 user cancels the operation. + Future getSaveLocation({ + List? acceptedTypeGroups, + FileDialogOptions options = const FileDialogOptions(), + }) async { + final String? path = await getSavePath( + acceptedTypeGroups: acceptedTypeGroups, + initialDirectory: options.initialDirectory, + suggestedName: options.suggestedName, + confirmButtonText: options.confirmButtonText, + ); + return path == null ? null : FileSaveLocationResult(path); + } + /// Opens a file dialog for loading directories and returns a directory path. + /// /// Returns `null` if user cancels the operation. Future getDirectoryPath({ String? initialDirectory, @@ -75,7 +93,8 @@ 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. 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 000000000000..627ccdf75dd3 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_dialog_options.dart @@ -0,0 +1,14 @@ +// 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; + +@immutable +class FileDialogOptions { + const FileDialogOptions( + {this.initialDirectory, this.suggestedName, this.confirmButtonText}); + final String? initialDirectory; + final String? suggestedName; + final String? confirmButtonText; +} diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart new file mode 100644 index 000000000000..063540b3c799 --- /dev/null +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart @@ -0,0 +1,16 @@ +// 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/x_type_group.dart'; + +export 'x_type_group/x_type_group.dart'; + +@immutable +class FileSaveLocationResult { + const FileSaveLocationResult(this.path, {this.activeFilter}); + final String path; + 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 9caee27c3e35..beef3d78f1c0 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 'file_dialog_options.dart'; +export 'file_save_result.dart'; export 'x_type_group/x_type_group.dart'; From 7566d9f0aa603b0d7c003531bfbd4a2c027c29da Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 31 May 2023 13:24:41 -0400 Subject: [PATCH 02/16] Split and comment --- .../file_selector_interface.dart | 4 ++-- .../lib/src/types/file_dialog_options.dart | 21 ++++++++++++++++--- .../src/types/file_save_location_result.dart | 14 +++++++++++-- .../lib/src/types/types.dart | 4 ++-- .../{x_type_group => }/x_type_group.dart | 0 5 files changed, 34 insertions(+), 9 deletions(-) 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_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 0839cecd89e3..91c99dfdb9f0 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 @@ -69,10 +69,10 @@ abstract class FileSelectorPlatform extends PlatformInterface { /// Opens a file dialog for saving files and returns a file location at which /// to save. /// - /// Returns `null` if user cancels the operation. + /// Returns `null` if the user cancels the operation. Future getSaveLocation({ List? acceptedTypeGroups, - FileDialogOptions options = const FileDialogOptions(), + SaveDialogOptions options = const SaveDialogOptions(), }) async { final String? path = await getSavePath( acceptedTypeGroups: acceptedTypeGroups, 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 index 627ccdf75dd3..5797e356b273 100644 --- 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 @@ -4,11 +4,26 @@ import 'package:flutter/foundation.dart' show immutable; +/// Configuration options for any file selector dialog. @immutable class FileDialogOptions { - const FileDialogOptions( - {this.initialDirectory, this.suggestedName, this.confirmButtonText}); + /// 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; - final String? suggestedName; + + /// 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_result.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart index 063540b3c799..23f629d8c328 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart @@ -4,13 +4,23 @@ import 'package:flutter/foundation.dart' show immutable; -import 'x_type_group/x_type_group.dart'; +import 'x_type_group.dart'; -export 'x_type_group/x_type_group.dart'; +export 'x_type_group.dart'; +/// The response from a save dialog. @immutable class FileSaveLocationResult { + /// Creates a result with the given [path] and optional other dialog state. const FileSaveLocationResult(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 beef3d78f1c0..6d2cbd049ccc 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 @@ -4,5 +4,5 @@ export 'package:cross_file/cross_file.dart'; export 'file_dialog_options.dart'; -export 'file_save_result.dart'; -export 'x_type_group/x_type_group.dart'; +export 'file_save_location_result.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 From 9c8e3598c17d679a59e90be7ede7f8ab63d0aac4 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 5 Jun 2023 11:45:35 -0400 Subject: [PATCH 03/16] Pathify deps --- packages/file_selector/file_selector/example/pubspec.yaml | 5 +++++ packages/file_selector/file_selector/pubspec.yaml | 5 +++++ .../file_selector/file_selector_ios/example/pubspec.yaml | 5 +++++ packages/file_selector/file_selector_ios/pubspec.yaml | 5 +++++ .../file_selector/file_selector_linux/example/pubspec.yaml | 5 +++++ packages/file_selector/file_selector_linux/pubspec.yaml | 5 +++++ .../file_selector/file_selector_macos/example/pubspec.yaml | 5 +++++ packages/file_selector/file_selector_macos/pubspec.yaml | 5 +++++ .../file_selector/file_selector_web/example/pubspec.yaml | 5 +++++ packages/file_selector/file_selector_web/pubspec.yaml | 5 +++++ .../file_selector/file_selector_windows/example/pubspec.yaml | 5 +++++ packages/file_selector/file_selector_windows/pubspec.yaml | 5 +++++ 12 files changed, 60 insertions(+) diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml index f4de9cbc743f..d6c8ad37d39a 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/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index 00f11e1af886..8d8a7f46f6d0 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -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_ios/example/pubspec.yaml b/packages/file_selector/file_selector_ios/example/pubspec.yaml index 44642b6d9126..aace887472ba 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 dd3f7dfca532..f999444ac340 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -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/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index 1596eb3f769f..ed7db91f1bda 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/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index 238e0aef741b..3c729a60b36f 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -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_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index 9e2d831f75cf..81e8801c07b9 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/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index 731a5cc8eed7..b70420f4fc5b 100644 --- a/packages/file_selector/file_selector_macos/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/pubspec.yaml @@ -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_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml index bc8b984e367d..9a7c108a0e49 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/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 8a601cbb3668..8ce85d2e12bc 100644 --- a/packages/file_selector/file_selector_web/pubspec.yaml +++ b/packages/file_selector/file_selector_web/pubspec.yaml @@ -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/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml index a22b6dc19aa9..0492eca6921b 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/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index 39d5cf7abf69..1fb2879af14f 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -28,3 +28,8 @@ dev_dependencies: sdk: flutter mockito: 5.4.1 pigeon: ^9.1.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}} From 3535dc2004dce3168126a87c3d70f55a898670e5 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 5 Jun 2023 12:11:26 -0400 Subject: [PATCH 04/16] Deprecate old version --- .../lib/readme_standalone_excerpts.dart | 7 +-- .../example/lib/save_text_page.dart | 10 ++-- .../file_selector/lib/file_selector.dart | 47 +++++++++++++++++-- .../example/lib/save_text_page.dart | 11 +++-- .../example/lib/save_text_page.dart | 9 ++-- .../file_selector_interface.dart | 22 +++++++-- .../example/lib/save_text_page.dart | 11 +++-- 7 files changed, 85 insertions(+), 32 deletions(-) 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 434bb90264ea..79197aeafeee 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 @@ -41,8 +41,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 FileSaveLocationResult? result = + await getSaveLocation(suggestedName: fileName); + if (result == null) { // Operation was canceled by the user. return; } @@ -51,7 +52,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 a159352570f6..7a58be45703a 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 @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// TODO(tarrinneal): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231) -// ignore: unnecessary_import -import 'dart:typed_data'; - import 'package:file_selector/file_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -28,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 FileSaveLocationResult? result = await getSaveLocation( initialDirectory: initialDirectory, suggestedName: fileName, ); - if (path == null) { + if (result == null) { // Operation was canceled by the user. return; } @@ -43,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 c2249565c9e8..2ff56ab908d8 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 FileSaveLocationResult, 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_linux/example/lib/save_text_page.dart b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart index 174c490c5fc7..e09e2ca7c50f 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 FileSaveLocationResult? 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_macos/example/lib/save_text_page.dart b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart index 79d9c83c8315..27603cf5e05b 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 FileSaveLocationResult? 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_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 91c99dfdb9f0..6880371c44e8 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 @@ -35,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, @@ -45,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, @@ -56,7 +63,10 @@ abstract class FileSelectorPlatform extends PlatformInterface { /// Opens a file dialog for saving files and returns a file path at which to /// save. /// - /// 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. + @Deprecated('Use getSaveLocation instead') Future getSavePath({ List? acceptedTypeGroups, String? initialDirectory, @@ -85,7 +95,9 @@ abstract class FileSelectorPlatform extends PlatformInterface { /// 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, @@ -95,6 +107,10 @@ abstract class FileSelectorPlatform extends PlatformInterface { /// 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_windows/example/lib/save_text_page.dart b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart index 174c490c5fc7..e09e2ca7c50f 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,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 FileSaveLocationResult? 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 From 31edcc4b764893035301ac107c6be430258c09ed Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 5 Jun 2023 14:52:59 -0400 Subject: [PATCH 05/16] Add initial passthrough implementations of the new method --- .../lib/file_selector_linux.dart | 24 +++++++++++++++---- .../lib/file_selector_macos.dart | 24 +++++++++++++++---- .../lib/file_selector_web.dart | 10 ++++++++ .../lib/file_selector_windows.dart | 23 ++++++++++++++---- 4 files changed, 69 insertions(+), 12 deletions(-) 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 b8e3df6a11bd..1af533c0bc7b 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,35 @@ 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( + 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 : FileSaveLocationResult(path); } @override 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 293c1e20e773..d7379f8e2dec 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 : FileSaveLocationResult(path); } @override 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 748bb3aa0df0..a56390815ba4 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 FileSaveLocationResult(''); + } + @override Future getDirectoryPath({ String? initialDirectory, 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 0e935ccdb9d8..a5a67dedd4f2 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 @@ -55,6 +55,21 @@ class FileSelectorWindows 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 paths = await _hostApi.showSaveDialog( SelectionOptions( @@ -62,10 +77,10 @@ class FileSelectorWindows extends FileSelectorPlatform { selectFolders: false, allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups), ), - initialDirectory, - suggestedName, - confirmButtonText); - return paths.isEmpty ? null : paths.first!; + options.initialDirectory, + options.suggestedName, + options.confirmButtonText); + return paths.isEmpty ? null : FileSaveLocationResult(paths.first!); } @override From 4f7da27ce63b00c535106b801d0e627be30be595 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 6 Jun 2023 11:30:15 -0400 Subject: [PATCH 06/16] Test the interface fallback --- ...file_selector_platform_interface_test.dart | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) 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 18334e885fc7..53e2ddce7864 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 FileSaveLocationResult? 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; + } +} From af2e10ee860c631ae0f43ba021847a7770d541e4 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 6 Jun 2023 14:21:03 -0400 Subject: [PATCH 07/16] Add unit tests for higher levels --- .../test/file_selector_test.dart | 111 ++++++++++++++- .../test/file_selector_linux_test.dart | 131 ++++++++++++++++-- .../test/file_selector_macos_test.dart | 122 +++++++++++++++- .../test/file_selector_windows_test.dart | 99 ++++++++++++- 4 files changed, 438 insertions(+), 25 deletions(-) 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 cdcebe078280..4e600cd4b074 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 FileSaveLocationResult? 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 FileSaveLocationResult? location = await getSaveLocation(); + expect(location?.path, expectedSavePath); + }); + + test('sets the initial directory', () async { + fakePlatformImplementation + ..setExpectations(initialDirectory: initialDirectory) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocationResult? location = + await getSaveLocation(initialDirectory: initialDirectory); + expect(location?.path, expectedSavePath); + }); + + test('sets the button confirmation label', () async { + fakePlatformImplementation + ..setExpectations(confirmButtonText: confirmButtonText) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocationResult? location = + await getSaveLocation(confirmButtonText: confirmButtonText); + expect(location?.path, expectedSavePath); + }); + + test('sets the accepted type groups', () async { + fakePlatformImplementation + ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocationResult? location = + await getSaveLocation(acceptedTypeGroups: acceptedTypeGroups); + expect(location?.path, expectedSavePath); + }); + + test('sets the suggested name', () async { + fakePlatformImplementation + ..setExpectations(suggestedName: suggestedName) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocationResult? 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 { + return (await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText, + ), + )) + ?.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 + : FileSaveLocationResult(path, + activeFilter: activeFilterIndex == null + ? null + : acceptedTypeGroups?[activeFilterIndex]); } @override 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 5127d28b7f67..f6b2cc66fe54 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/test/file_selector_macos_test.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart index 6450e6f3b0a7..6592c6279f1f 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 FileSaveLocationResult? 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 FileSaveLocationResult? 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_windows/test/file_selector_windows_test.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart index fbe3683af37d..7f836fe52633 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,7 +30,7 @@ void main() { expect(FileSelectorPlatform.instance, isA()); }); - group('#openFile', () { + group('openFile', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)).thenReturn(['foo']); }); @@ -105,7 +105,7 @@ void main() { }); }); - group('#openFiles', () { + group('openFiles', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) .thenReturn(['foo', 'bar']); @@ -182,7 +182,7 @@ void main() { }); }); - group('#getDirectoryPath', () { + group('getDirectoryPath', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)).thenReturn(['foo']); }); @@ -211,7 +211,7 @@ void main() { }); }); - group('#getDirectoryPaths', () { + group('getDirectoryPaths', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) .thenReturn(['foo', 'bar']); @@ -242,7 +242,96 @@ void main() { }); }); - group('#getSavePath', () { + group('getSaveLocation', () { + setUp(() { + when(mockApi.showSaveDialog(any, any, any, any)) + .thenReturn(['foo']); + }); + + test('simple call works', () async { + final FileSaveLocationResult? 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('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']); From cee261f263bb2b34d2719fea0bcb73e684265b74 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 14 Jun 2023 16:24:48 -0400 Subject: [PATCH 08/16] Implement for Windows --- .../example/lib/save_text_page.dart | 24 +- .../example/windows/runner/Runner.rc | 10 +- .../lib/file_selector_windows.dart | 25 +- .../lib/src/messages.g.dart | 55 ++++- .../pigeons/messages.dart | 23 +- .../file_selector_windows/pubspec.yaml | 2 +- .../test/file_selector_windows_test.dart | 37 ++- .../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 | 222 +++++++++++------- .../test/test_file_dialog_controller.cpp | 7 + .../test/test_file_dialog_controller.h | 1 + 18 files changed, 530 insertions(+), 215 deletions(-) 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 e09e2ca7c50f..e2135a622293 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'; @@ -20,16 +21,33 @@ class SaveTextPage extends StatelessWidget { final FileSaveLocationResult? result = await FileSelectorPlatform.instance.getSaveLocation( options: SaveDialogOptions(suggestedName: fileName), + acceptedTypeGroups: const [ + XTypeGroup( + label: 'Plain text', + extensions: ['txt'], + ), + XTypeGroup( + label: 'JSON', + extensions: ['json'], + ), + ], ); // 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) { + 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); + final XFile textFile = XFile.fromData(fileData, name: fileName); await textFile.saveTo(result.path); } 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 51812dcd4878..e5666e0223f1 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 a5a67dedd4f2..37e1d7cad9c2 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 @@ -71,7 +71,7 @@ class FileSelectorWindows extends FileSelectorPlatform { List? acceptedTypeGroups, SaveDialogOptions options = const SaveDialogOptions(), }) async { - final List paths = await _hostApi.showSaveDialog( + final FileDialogResult result = await _hostApi.showSaveDialog( SelectionOptions( allowMultiple: false, selectFolders: false, @@ -80,7 +80,12 @@ class FileSelectorWindows extends FileSelectorPlatform { options.initialDirectory, options.suggestedName, options.confirmButtonText); - return paths.isEmpty ? null : FileSaveLocationResult(paths.first!); + final int? groupIndex = result.typeGroupIndex; + return result.paths.isEmpty + ? null + : FileSaveLocationResult(result.paths.first!, + activeFilter: + groupIndex == null ? null : acceptedTypeGroups?[groupIndex]); } @override @@ -88,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, @@ -96,7 +101,7 @@ class FileSelectorWindows extends FileSelectorPlatform { ), initialDirectory, confirmButtonText); - return paths.isEmpty ? null : paths.first!; + return result.paths.isEmpty ? null : result.paths.first!; } @override @@ -104,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, @@ -112,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 a61076b97b32..b5b4a7943888 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 c3b3aff192b8..8f82aec0b838 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 1fb2879af14f..d0e5deed6c8f 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -27,7 +27,7 @@ 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 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 7f836fe52633..aaf4ac148c12 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 @@ -32,7 +32,8 @@ void main() { 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 { @@ -108,7 +109,7 @@ void main() { group('openFiles', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) - .thenReturn(['foo', 'bar']); + .thenReturn(FileDialogResult(paths: ['foo', 'bar'])); }); test('simple call works', () async { @@ -184,7 +185,8 @@ void main() { 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 { @@ -214,7 +216,7 @@ void main() { group('getDirectoryPaths', () { setUp(() { when(mockApi.showOpenDialog(any, any, any)) - .thenReturn(['foo', 'bar']); + .thenReturn(FileDialogResult(paths: ['foo', 'bar'])); }); test('simple call works', () async { @@ -245,7 +247,7 @@ void main() { group('getSaveLocation', () { setUp(() { when(mockApi.showSaveDialog(any, any, any, any)) - .thenReturn(['foo']); + .thenReturn(FileDialogResult(paths: ['foo'])); }); test('simple call works', () async { @@ -287,6 +289,29 @@ void main() { 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 FileSaveLocationResult? 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: @@ -334,7 +359,7 @@ void main() { 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 ae55f2e301d3..7168e0c8d816 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 f9ed8e5b63db..778ae4fc16c3 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 5820c4a5da40..af2a9affecc1 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 f5c93974cbe9..ab4929287e9f 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 b9e6d211b2d1..35697983108e 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 1388bfd3898d..2f17f949049c 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 24b831e292e4..a60fd92a974c 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 248ca89c977f..ab8afd7d703b 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 8efeb54f8604..9f10b893f2d8 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(); + const EncodableList& paths = result.value().paths(); 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) { @@ -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(); + const EncodableList& paths = result.value().paths(); 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) { @@ -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(); + const EncodableList& paths = result.value().paths(); 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) { @@ -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(); + const EncodableList& paths = result.value().paths(); 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) { @@ -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(); + const EncodableList& paths = result.value().paths(); 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) { @@ -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(); + const EncodableList& paths = result.value().paths(); + 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, 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(); 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) { @@ -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(); + const EncodableList& paths = result.value().paths(); 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) { @@ -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(); + const EncodableList& paths = result.value().paths(); 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) { @@ -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 15065f916c8b..e775aa627aa3 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 1c221fc219f9..e3aa0936e764 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 8d9a32f566aa5668be327b5f41bb1f0f1ac2be3c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 15 Jun 2023 13:30:17 -0400 Subject: [PATCH 09/16] Bump versions --- packages/file_selector/file_selector/CHANGELOG.md | 3 ++- packages/file_selector/file_selector/pubspec.yaml | 2 +- packages/file_selector/file_selector_ios/CHANGELOG.md | 4 ++++ packages/file_selector/file_selector_ios/pubspec.yaml | 2 +- packages/file_selector/file_selector_linux/CHANGELOG.md | 3 ++- packages/file_selector/file_selector_linux/pubspec.yaml | 2 +- packages/file_selector/file_selector_macos/CHANGELOG.md | 3 ++- packages/file_selector/file_selector_macos/pubspec.yaml | 2 +- .../file_selector_platform_interface/CHANGELOG.md | 4 ++++ .../file_selector_platform_interface/pubspec.yaml | 2 +- packages/file_selector/file_selector_web/CHANGELOG.md | 3 ++- packages/file_selector/file_selector_web/pubspec.yaml | 2 +- packages/file_selector/file_selector_windows/CHANGELOG.md | 4 ++++ packages/file_selector/file_selector_windows/pubspec.yaml | 2 +- 14 files changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index 98d02784d7c8..5215787e070f 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/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index 8d8a7f46f6d0..03764de006a8 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" diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index 8a48d5b089db..0c6d5658d31a 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/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index f999444ac340..a3b3d04160fe 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" diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index 88886301d3a1..5c9dc1dbcd85 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/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index 3c729a60b36f..dd24a8474d8a 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" diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index e697974a390f..7a197838bb95 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/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index b70420f4fc5b..c56482c6e141 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" diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md index ba4ed2cfe11f..f33890efe580 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/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index b7ce75d960f8..a947c59eb392 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_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 43ad4962f044..073f1c20ed82 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/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 8ce85d2e12bc..673c5a94c0c8 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" diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index 35163430e921..b747a45723a0 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/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index d0e5deed6c8f..8b45d914c00c 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" From f315e22a0c681c89b1f90da3f6f51d2960fb1d1f Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 15 Jun 2023 14:22:31 -0400 Subject: [PATCH 10/16] Linux TODO --- .../file_selector_linux/lib/file_selector_linux.dart | 2 ++ 1 file changed, 2 insertions(+) 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 1af533c0bc7b..2f6785c5d2b9 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 @@ -100,6 +100,8 @@ class FileSelectorLinux extends FileSelectorPlatform { }) 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( _getSavePathMethod, { From 7854c6334fafa2f1453d0d5c9ae84bab02ce150a Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 16 Jun 2023 16:39:14 -0400 Subject: [PATCH 11/16] Review feedback --- .../lib/readme_standalone_excerpts.dart | 2 +- .../example/lib/save_text_page.dart | 2 +- .../file_selector/lib/file_selector.dart | 4 +-- .../test/file_selector_test.dart | 22 ++++++++-------- .../example/lib/save_text_page.dart | 2 +- .../lib/file_selector_linux.dart | 4 +-- .../example/lib/save_text_page.dart | 2 +- .../lib/file_selector_macos.dart | 4 +-- .../test/file_selector_macos_test.dart | 4 +-- .../file_selector_interface.dart | 4 +-- .../src/types/file_save_location_result.dart | 26 ------------------- .../lib/src/types/types.dart | 2 +- ...file_selector_platform_interface_test.dart | 4 +-- .../lib/file_selector_web.dart | 4 +-- .../example/lib/save_text_page.dart | 4 ++- .../lib/file_selector_windows.dart | 4 +-- .../test/file_selector_windows_test.dart | 4 +-- .../test/file_selector_plugin_test.cpp | 18 ++++++------- 18 files changed, 46 insertions(+), 70 deletions(-) delete mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart 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 158d3a2f3265..cc0a051e2189 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,7 +39,7 @@ class _MyAppState extends State { Future saveFile() async { // #docregion Save const String fileName = 'suggested_name.txt'; - final FileSaveLocationResult? result = + final FileSaveLocation? result = await getSaveLocation(suggestedName: fileName); if (result == null) { // Operation was canceled by the user. 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 7a58be45703a..e782530914e2 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,7 +24,7 @@ class SaveTextPage extends StatelessWidget { // file will be saved. In most cases, this parameter should not be provided. final String initialDirectory = (await getApplicationDocumentsDirectory()).path; - final FileSaveLocationResult? result = await getSaveLocation( + final FileSaveLocation? result = await getSaveLocation( initialDirectory: initialDirectory, suggestedName: fileName, ); diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index 2ff56ab908d8..e1739eda0c8d 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 FileSaveLocationResult, XFile, XTypeGroup; + show FileSaveLocation, XFile, XTypeGroup; /// Opens a file selection dialog and returns the path chosen by the user. /// @@ -128,7 +128,7 @@ Future getSavePath({ /// When not provided, the default OS label is used (for example, "Save"). /// /// Returns `null` if the user cancels the operation. -Future getSaveLocation({ +Future getSaveLocation({ List acceptedTypeGroups = const [], String? initialDirectory, String? suggestedName, 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 4e600cd4b074..a9d684c0dffb 100644 --- a/packages/file_selector/file_selector/test/file_selector_test.dart +++ b/packages/file_selector/file_selector/test/file_selector_test.dart @@ -158,7 +158,7 @@ void main() { ..setPathsResponse([expectedSavePath], activeFilter: expectedActiveFilter); - final FileSaveLocationResult? location = await getSaveLocation( + final FileSaveLocation? location = await getSaveLocation( initialDirectory: initialDirectory, confirmButtonText: confirmButtonText, acceptedTypeGroups: acceptedTypeGroups, @@ -172,7 +172,7 @@ void main() { test('works with no arguments', () async { fakePlatformImplementation.setPathsResponse([expectedSavePath]); - final FileSaveLocationResult? location = await getSaveLocation(); + final FileSaveLocation? location = await getSaveLocation(); expect(location?.path, expectedSavePath); }); @@ -181,7 +181,7 @@ void main() { ..setExpectations(initialDirectory: initialDirectory) ..setPathsResponse([expectedSavePath]); - final FileSaveLocationResult? location = + final FileSaveLocation? location = await getSaveLocation(initialDirectory: initialDirectory); expect(location?.path, expectedSavePath); }); @@ -191,7 +191,7 @@ void main() { ..setExpectations(confirmButtonText: confirmButtonText) ..setPathsResponse([expectedSavePath]); - final FileSaveLocationResult? location = + final FileSaveLocation? location = await getSaveLocation(confirmButtonText: confirmButtonText); expect(location?.path, expectedSavePath); }); @@ -201,7 +201,7 @@ void main() { ..setExpectations(acceptedTypeGroups: acceptedTypeGroups) ..setPathsResponse([expectedSavePath]); - final FileSaveLocationResult? location = + final FileSaveLocation? location = await getSaveLocation(acceptedTypeGroups: acceptedTypeGroups); expect(location?.path, expectedSavePath); }); @@ -211,7 +211,7 @@ void main() { ..setExpectations(suggestedName: suggestedName) ..setPathsResponse([expectedSavePath]); - final FileSaveLocationResult? location = + final FileSaveLocation? location = await getSaveLocation(suggestedName: suggestedName); expect(location?.path, expectedSavePath); }); @@ -449,19 +449,19 @@ class FakeFileSelector extends Fake String? suggestedName, String? confirmButtonText, }) async { - return (await getSaveLocation( + final FileSaveLocation? result = await getSaveLocation( acceptedTypeGroups: acceptedTypeGroups, options: SaveDialogOptions( initialDirectory: initialDirectory, suggestedName: suggestedName, confirmButtonText: confirmButtonText, ), - )) - ?.path; + ); + return result?.path; } @override - Future getSaveLocation({ + Future getSaveLocation({ List? acceptedTypeGroups, SaveDialogOptions options = const SaveDialogOptions(), }) async { @@ -473,7 +473,7 @@ class FakeFileSelector extends Fake final int? activeFilterIndex = activeFilter; return path == null ? null - : FileSaveLocationResult(path, + : FileSaveLocation(path, activeFilter: activeFilterIndex == null ? null : acceptedTypeGroups?[activeFilterIndex]); 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 e09e2ca7c50f..bfd83cfd2393 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,7 +17,7 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final FileSaveLocationResult? result = + final FileSaveLocation? result = await FileSelectorPlatform.instance.getSaveLocation( options: SaveDialogOptions(suggestedName: fileName), ); 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 2f6785c5d2b9..5ae02ca3ade8 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 @@ -94,7 +94,7 @@ class FileSelectorLinux extends FileSelectorPlatform { } @override - Future getSaveLocation({ + Future getSaveLocation({ List? acceptedTypeGroups, SaveDialogOptions options = const SaveDialogOptions(), }) async { @@ -112,7 +112,7 @@ class FileSelectorLinux extends FileSelectorPlatform { _confirmButtonTextKey: options.confirmButtonText, }, ); - return path == null ? null : FileSaveLocationResult(path); + return path == null ? null : FileSaveLocation(path); } @override 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 27603cf5e05b..6208e16ef7d5 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,7 +17,7 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final FileSaveLocationResult? result = + final FileSaveLocation? result = await FileSelectorPlatform.instance.getSaveLocation( options: SaveDialogOptions(suggestedName: fileName), ); 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 d7379f8e2dec..a26b0ff11a13 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 @@ -71,7 +71,7 @@ class FileSelectorMacOS extends FileSelectorPlatform { } @override - Future getSaveLocation({ + Future getSaveLocation({ List? acceptedTypeGroups, SaveDialogOptions options = const SaveDialogOptions(), }) async { @@ -81,7 +81,7 @@ class FileSelectorMacOS extends FileSelectorPlatform { nameFieldStringValue: options.suggestedName, prompt: options.confirmButtonText, )); - return path == null ? null : FileSaveLocationResult(path); + return path == null ? null : FileSaveLocation(path); } @override 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 6592c6279f1f..c7268a6be891 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 @@ -348,7 +348,7 @@ void main() { test('works as expected with no arguments', () async { when(mockApi.displaySavePanel(any)).thenAnswer((_) async => 'foo'); - final FileSaveLocationResult? location = await plugin.getSaveLocation(); + final FileSaveLocation? location = await plugin.getSaveLocation(); expect(location?.path, 'foo'); final VerificationResult result = @@ -363,7 +363,7 @@ void main() { test('handles cancel', () async { when(mockApi.displaySavePanel(any)).thenAnswer((_) async => null); - final FileSaveLocationResult? location = await plugin.getSaveLocation(); + final FileSaveLocation? location = await plugin.getSaveLocation(); expect(location, null); }); 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 6880371c44e8..7fe40c72f3a0 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 @@ -80,7 +80,7 @@ abstract class FileSelectorPlatform extends PlatformInterface { /// to save. /// /// Returns `null` if the user cancels the operation. - Future getSaveLocation({ + Future getSaveLocation({ List? acceptedTypeGroups, SaveDialogOptions options = const SaveDialogOptions(), }) async { @@ -90,7 +90,7 @@ abstract class FileSelectorPlatform extends PlatformInterface { suggestedName: options.suggestedName, confirmButtonText: options.confirmButtonText, ); - return path == null ? null : FileSaveLocationResult(path); + return path == null ? null : FileSaveLocation(path); } /// Opens a file dialog for loading directories and returns a directory path. diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart b/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart deleted file mode 100644 index 23f629d8c328..000000000000 --- a/packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location_result.dart +++ /dev/null @@ -1,26 +0,0 @@ -// 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 FileSaveLocationResult { - /// Creates a result with the given [path] and optional other dialog state. - const FileSaveLocationResult(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 6d2cbd049ccc..c23ff97a0372 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 @@ -4,5 +4,5 @@ export 'package:cross_file/cross_file.dart'; export 'file_dialog_options.dart'; -export 'file_save_location_result.dart'; +export 'file_save_location.dart'; export 'x_type_group.dart'; 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 53e2ddce7864..7592c8512279 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 @@ -34,7 +34,7 @@ void main() { final FileSelectorPlatform fileSelector = OldFileSelectorPlatformImplementation(); - final FileSaveLocationResult? result = await fileSelector.getSaveLocation(); + final FileSaveLocation? result = await fileSelector.getSaveLocation(); expect(result?.path, OldFileSelectorPlatformImplementation.savePath); expect(result?.activeFilter, null); @@ -45,7 +45,7 @@ class ExtendsFileSelectorPlatform extends FileSelectorPlatform {} class OldFileSelectorPlatformImplementation extends FileSelectorPlatform { static const String savePath = '/a/path'; -// Only implement the deprecated getSavePath. + // Only implement the deprecated getSavePath. @override Future getSavePath({ List? acceptedTypeGroups, 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 a56390815ba4..2380e274c46a 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 @@ -61,13 +61,13 @@ class FileSelectorWeb extends FileSelectorPlatform { ''; @override - Future getSaveLocation({ + 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 FileSaveLocationResult(''); + return const FileSaveLocation(''); } @override 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 e2135a622293..c92aff055697 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 @@ -18,7 +18,7 @@ class SaveTextPage extends StatelessWidget { Future _saveFile() async { final String fileName = _nameController.text; - final FileSaveLocationResult? result = + final FileSaveLocation? result = await FileSelectorPlatform.instance.getSaveLocation( options: SaveDialogOptions(suggestedName: fileName), acceptedTypeGroups: const [ @@ -42,6 +42,8 @@ class SaveTextPage extends StatelessWidget { 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}'; } } 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 37e1d7cad9c2..677811afb891 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 @@ -67,7 +67,7 @@ class FileSelectorWindows extends FileSelectorPlatform { } @override - Future getSaveLocation({ + Future getSaveLocation({ List? acceptedTypeGroups, SaveDialogOptions options = const SaveDialogOptions(), }) async { @@ -83,7 +83,7 @@ class FileSelectorWindows extends FileSelectorPlatform { final int? groupIndex = result.typeGroupIndex; return result.paths.isEmpty ? null - : FileSaveLocationResult(result.paths.first!, + : FileSaveLocation(result.paths.first!, activeFilter: groupIndex == null ? null : acceptedTypeGroups?[groupIndex]); } 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 aaf4ac148c12..4f455ee3088b 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 @@ -251,7 +251,7 @@ void main() { }); test('simple call works', () async { - final FileSaveLocationResult? location = await plugin.getSaveLocation(); + final FileSaveLocation? location = await plugin.getSaveLocation(); expect(location?.path, 'foo'); expect(location?.activeFilter, null); @@ -304,7 +304,7 @@ void main() { mimeTypes: ['image/jpg'], ); - final FileSaveLocationResult? result = await plugin + final FileSaveLocation? result = await plugin .getSaveLocation(acceptedTypeGroups: [group, groupTwo]); verify(mockApi.showSaveDialog(captureAny, null, null, null)); 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 9f10b893f2d8..ee6d4dcded79 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 @@ -66,7 +66,7 @@ TEST(FileSelectorPlugin, TestOpenSimple) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 1); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); EXPECT_EQ(result.value().type_group_index(), nullptr); @@ -109,7 +109,7 @@ TEST(FileSelectorPlugin, TestOpenWithArguments) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 1); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); EXPECT_EQ(result.value().type_group_index(), nullptr); @@ -154,7 +154,7 @@ TEST(FileSelectorPlugin, TestOpenMultiple) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 2); + ASSERT_EQ(paths.size(), 2); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file_1.path())); EXPECT_EQ(std::get(paths[1]), @@ -221,7 +221,7 @@ TEST(FileSelectorPlugin, TestOpenWithFilter) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 1); + 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 @@ -287,7 +287,7 @@ TEST(FileSelectorPlugin, TestSaveSimple) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 1); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); EXPECT_EQ(result.value().type_group_index(), nullptr); @@ -330,7 +330,7 @@ TEST(FileSelectorPlugin, TestSaveWithArguments) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 1); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_file.path())); EXPECT_EQ(result.value().type_group_index(), nullptr); @@ -387,7 +387,7 @@ TEST(FileSelectorPlugin, TestSaveWithFilter) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 1); + 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 @@ -459,7 +459,7 @@ TEST(FileSelectorPlugin, TestGetDirectorySimple) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 1); + ASSERT_EQ(paths.size(), 1); EXPECT_EQ(std::get(paths[0]), "C:\\Program Files"); EXPECT_EQ(result.value().type_group_index(), nullptr); } @@ -506,7 +506,7 @@ TEST(FileSelectorPlugin, TestGetDirectoryMultiple) { EXPECT_TRUE(shown); ASSERT_FALSE(result.has_error()); const EncodableList& paths = result.value().paths(); - EXPECT_EQ(paths.size(), 2); + ASSERT_EQ(paths.size(), 2); EXPECT_EQ(std::get(paths[0]), Utf8FromUtf16(fake_selected_dir_1.path())); EXPECT_EQ(std::get(paths[1]), From 411be21cd1613f4e5e5557476d7c8131e52247e7 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 16 Jun 2023 19:12:50 -0400 Subject: [PATCH 12/16] Add missed file --- .../lib/src/types/file_save_location.dart | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/file_selector/file_selector_platform_interface/lib/src/types/file_save_location.dart 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 000000000000..4670e33fc53d --- /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; +} From 9552cb9a53b1eea1dab4c432f1d2ec16943ebc5f Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 22 Jun 2023 10:26:52 -0400 Subject: [PATCH 13/16] Revert app-facing --- .../file_selector/lib/file_selector.dart | 50 +++---------------- 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index e1739eda0c8d..c75cb582335d 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,20 @@ 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( + // TODO(stuartmorgan): Update this to getSaveLocation in the next federated + // change PR. + // ignore: deprecated_member_use + 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. From 5415633d04877214d1018bc9fe3f4529c4abea60 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 22 Jun 2023 10:31:48 -0400 Subject: [PATCH 14/16] Update dependencies --- .../file_selector/file_selector_ios/example/pubspec.yaml | 7 +------ packages/file_selector/file_selector_ios/pubspec.yaml | 7 +------ .../file_selector/file_selector_linux/example/pubspec.yaml | 7 +------ packages/file_selector/file_selector_linux/pubspec.yaml | 7 +------ .../file_selector/file_selector_macos/example/pubspec.yaml | 7 +------ packages/file_selector/file_selector_macos/pubspec.yaml | 7 +------ .../file_selector/file_selector_web/example/pubspec.yaml | 7 +------ packages/file_selector/file_selector_web/pubspec.yaml | 7 +------ .../file_selector_windows/example/pubspec.yaml | 7 +------ packages/file_selector/file_selector_windows/pubspec.yaml | 7 +------ 10 files changed, 10 insertions(+), 60 deletions(-) diff --git a/packages/file_selector/file_selector_ios/example/pubspec.yaml b/packages/file_selector/file_selector_ios/example/pubspec.yaml index aace887472ba..3ab518a1a0ab 100644 --- a/packages/file_selector/file_selector_ios/example/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/example/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: .. - file_selector_platform_interface: ^2.2.0 + file_selector_platform_interface: ^2.6.0 flutter: sdk: flutter @@ -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 a3b3d04160fe..4bd9257c711c 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -17,7 +17,7 @@ flutter: pluginClass: FFSFileSelectorPlugin dependencies: - file_selector_platform_interface: ^2.3.0 + file_selector_platform_interface: ^2.6.0 flutter: sdk: flutter @@ -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/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index ed7db91f1bda..da7410f1b11b 100644 --- a/packages/file_selector/file_selector_linux/example/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: file_selector_linux: path: ../ - file_selector_platform_interface: ^2.4.0 + file_selector_platform_interface: ^2.6.0 flutter: sdk: flutter @@ -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/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index dd24a8474d8a..0c9909f5b449 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -18,15 +18,10 @@ flutter: dependencies: cross_file: ^0.3.1 - file_selector_platform_interface: ^2.4.0 + file_selector_platform_interface: ^2.6.0 flutter: sdk: flutter 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_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index 81e8801c07b9..baa27c568480 100644 --- a/packages/file_selector/file_selector_macos/example/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/example/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: .. - file_selector_platform_interface: ^2.4.0 + file_selector_platform_interface: ^2.6.0 flutter: sdk: flutter @@ -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/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index c56482c6e141..047c7caca4f3 100644 --- a/packages/file_selector/file_selector_macos/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/pubspec.yaml @@ -18,7 +18,7 @@ flutter: dependencies: cross_file: ^0.3.1 - file_selector_platform_interface: ^2.4.0 + file_selector_platform_interface: ^2.6.0 flutter: sdk: flutter @@ -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_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml index 9a7c108a0e49..fb3e71ed3f3a 100644 --- a/packages/file_selector/file_selector_web/example/pubspec.yaml +++ b/packages/file_selector/file_selector_web/example/pubspec.yaml @@ -6,7 +6,7 @@ environment: flutter: ">=3.3.0" dependencies: - file_selector_platform_interface: ^2.2.0 + file_selector_platform_interface: ^2.6.0 file_selector_web: path: ../ flutter: @@ -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/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 673c5a94c0c8..81ba11a9634a 100644 --- a/packages/file_selector/file_selector_web/pubspec.yaml +++ b/packages/file_selector/file_selector_web/pubspec.yaml @@ -17,7 +17,7 @@ flutter: fileName: file_selector_web.dart dependencies: - file_selector_platform_interface: ^2.3.0 + file_selector_platform_interface: ^2.6.0 flutter: sdk: flutter flutter_web_plugins: @@ -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/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml index 0492eca6921b..02a1475775ee 100644 --- a/packages/file_selector/file_selector_windows/example/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/example/pubspec.yaml @@ -8,7 +8,7 @@ environment: flutter: ">=3.3.0" dependencies: - file_selector_platform_interface: ^2.4.0 + file_selector_platform_interface: ^2.6.0 file_selector_windows: # When depending on this package from a real application you should use: # file_selector_windows: ^x.y.z @@ -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/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index 8b45d914c00c..4cd3cc521545 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -18,7 +18,7 @@ flutter: dependencies: cross_file: ^0.3.1 - file_selector_platform_interface: ^2.4.0 + file_selector_platform_interface: ^2.6.0 flutter: sdk: flutter @@ -28,8 +28,3 @@ dev_dependencies: 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}} From a05962bd6270dd39a0a555d5201a52c874ae5545 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 22 Jun 2023 13:11:33 -0400 Subject: [PATCH 15/16] Revert iOS, which had no real changes --- packages/file_selector/file_selector_ios/CHANGELOG.md | 4 ---- packages/file_selector/file_selector_ios/example/pubspec.yaml | 2 +- packages/file_selector/file_selector_ios/pubspec.yaml | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index 0c6d5658d31a..8a48d5b089db 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 3ab518a1a0ab..44642b6d9126 100644 --- a/packages/file_selector/file_selector_ios/example/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/example/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: .. - file_selector_platform_interface: ^2.6.0 + file_selector_platform_interface: ^2.2.0 flutter: sdk: flutter diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index 4bd9257c711c..dd3f7dfca532 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" @@ -17,7 +17,7 @@ flutter: pluginClass: FFSFileSelectorPlugin dependencies: - file_selector_platform_interface: ^2.6.0 + file_selector_platform_interface: ^2.3.0 flutter: sdk: flutter From 994b3f6ee2c18fd694bf24434a04b3ed361734b9 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 23 Jun 2023 11:02:49 -0400 Subject: [PATCH 16/16] Review feedback --- .../lib/file_selector_linux.dart | 16 ++++++++-------- .../lib/file_selector_macos.dart | 16 ++++++++-------- .../lib/file_selector_windows.dart | 16 ++++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) 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 5ae02ca3ade8..b06523b27d32 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 @@ -83,14 +83,14 @@ class FileSelectorLinux extends FileSelectorPlatform { String? suggestedName, String? confirmButtonText, }) async { - return (await getSaveLocation( - acceptedTypeGroups: acceptedTypeGroups, - options: SaveDialogOptions( - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText, - ))) - ?.path; + final FileSaveLocation? location = await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText, + )); + return location?.path; } @override 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 a26b0ff11a13..3489938a73a1 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,14 +60,14 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? suggestedName, String? confirmButtonText, }) async { - return (await getSaveLocation( - acceptedTypeGroups: acceptedTypeGroups, - options: SaveDialogOptions( - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText, - ))) - ?.path; + final FileSaveLocation? location = await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText, + )); + return location?.path; } @override 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 677811afb891..b96886bd0df6 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 @@ -56,14 +56,14 @@ class FileSelectorWindows extends FileSelectorPlatform { String? suggestedName, String? confirmButtonText, }) async { - return (await getSaveLocation( - acceptedTypeGroups: acceptedTypeGroups, - options: SaveDialogOptions( - initialDirectory: initialDirectory, - suggestedName: suggestedName, - confirmButtonText: confirmButtonText, - ))) - ?.path; + final FileSaveLocation? location = await getSaveLocation( + acceptedTypeGroups: acceptedTypeGroups, + options: SaveDialogOptions( + initialDirectory: initialDirectory, + suggestedName: suggestedName, + confirmButtonText: confirmButtonText, + )); + return location?.path; } @override