diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index b9ed354daa1..8dfb56ca865 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 1.1.0 +* Adds `canCreateDirectories` param to `getDirectoryPath` and `getDirectoryPaths` to control the visibility of the New Folder button in file dialogs on supported platforms. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. * Updates README to indicate that Andoid SDK <21 is no longer supported. diff --git a/packages/file_selector/file_selector/example/macos/Podfile b/packages/file_selector/file_selector/example/macos/Podfile index ae77cc1d426..66f6172bbb3 100644 --- a/packages/file_selector/file_selector/example/macos/Podfile +++ b/packages/file_selector/file_selector/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/file_selector/file_selector/example/macos/Runner.xcodeproj/project.pbxproj b/packages/file_selector/file_selector/example/macos/Runner.xcodeproj/project.pbxproj index 7aa95e4ed28..36b6359c6e8 100644 --- a/packages/file_selector/file_selector/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/file_selector/file_selector/example/macos/Runner.xcodeproj/project.pbxproj @@ -186,6 +186,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + 43898F02A8C2312B24AEE41B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -299,6 +300,23 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 43898F02A8C2312B24AEE41B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; A778864BDDD7B12C41D66FBB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -395,7 +413,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -474,7 +492,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -521,7 +539,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/packages/file_selector/file_selector/example/pubspec.yaml b/packages/file_selector/file_selector/example/pubspec.yaml index bd8a1c0881b..34fb4e6b731 100644 --- a/packages/file_selector/file_selector/example/pubspec.yaml +++ b/packages/file_selector/file_selector/example/pubspec.yaml @@ -27,3 +27,7 @@ dev_dependencies: flutter: uses-material-design: true +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index 3220bac4ff3..7f262989c86 100644 --- a/packages/file_selector/file_selector/lib/file_selector.dart +++ b/packages/file_selector/file_selector/lib/file_selector.dart @@ -93,12 +93,17 @@ Future> openFiles({ /// [confirmButtonText] is the text in the confirmation button of the dialog. /// When not provided, the default OS label is used (for example, "Save"). /// +/// [canCreateDirectories] controls whether the user is allowed to create new +/// directories in the save dialog. When not provided, uses the platform default. +/// May not be supported on all platforms. +/// /// Returns `null` if the user cancels the operation. Future getSaveLocation({ List acceptedTypeGroups = const [], String? initialDirectory, String? suggestedName, String? confirmButtonText, + bool? canCreateDirectories, }) async { return FileSelectorPlatform.instance.getSaveLocation( acceptedTypeGroups: acceptedTypeGroups, @@ -106,6 +111,7 @@ Future getSaveLocation({ initialDirectory: initialDirectory, suggestedName: suggestedName, confirmButtonText: confirmButtonText, + canCreateDirectories: canCreateDirectories, ), ); } @@ -121,14 +127,22 @@ Future getSaveLocation({ /// [confirmButtonText] is the text in the confirmation button of the dialog. /// When not provided, the default OS label is used (for example, "Open"). /// +/// [canCreateDirectories] controls whether the user is allowed to create new +/// directories in the dialog. When not provided, uses the platform default. +/// May not be supported on all platforms. +/// /// Returns `null` if the user cancels the operation. Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, + bool? canCreateDirectories, }) async { - return FileSelectorPlatform.instance.getDirectoryPath( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, + return FileSelectorPlatform.instance.getDirectoryPathWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + canCreateDirectories: canCreateDirectories, + ), ); } @@ -144,13 +158,21 @@ Future getDirectoryPath({ /// [confirmButtonText] is the text in the confirmation button of the dialog. /// When not provided, the default OS label is used (for example, "Open"). /// +/// [canCreateDirectories] controls whether the user is allowed to create new +/// directories in the dialog. When not provided, uses the platform default. +/// May not be supported on all platforms. +/// /// Returns an empty array if the user cancels the operation. Future> getDirectoryPaths({ String? initialDirectory, String? confirmButtonText, + bool? canCreateDirectories, }) async { - return FileSelectorPlatform.instance.getDirectoryPaths( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, + return FileSelectorPlatform.instance.getDirectoryPathsWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + canCreateDirectories: canCreateDirectories, + ), ); } diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index 028209dd94f..a3ea6c9ec90 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: 1.0.3 +version: 1.1.0 environment: sdk: ^3.7.0 @@ -46,3 +46,7 @@ topics: - files - file-selection - file-selector +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector/test/file_selector_test.dart b/packages/file_selector/file_selector/test/file_selector_test.dart index 56bd7c3dfb3..445d2999c9f 100644 --- a/packages/file_selector/file_selector/test/file_selector_test.dart +++ b/packages/file_selector/file_selector/test/file_selector_test.dart @@ -12,6 +12,7 @@ void main() { const String initialDirectory = '/home/flutteruser'; const String confirmButtonText = 'Use this profile picture'; const String suggestedName = 'suggested_name'; + const List acceptedTypeGroups = [ XTypeGroup( label: 'documents', @@ -227,6 +228,18 @@ void main() { ); expect(location?.path, expectedSavePath); }); + + test('sets to disable the creation of new directories', () async { + const bool canCreateDirectories = false; + fakePlatformImplementation + ..setExpectations(canCreateDirectories: canCreateDirectories) + ..setPathsResponse([expectedSavePath]); + + final FileSaveLocation? location = await getSaveLocation( + canCreateDirectories: canCreateDirectories, + ); + expect(location?.path, expectedSavePath); + }); }); group('getDirectoryPath', () { @@ -278,6 +291,18 @@ void main() { ); expect(directoryPath, expectedDirectoryPath); }); + + test('sets to enable de creation of new directories', () async { + const bool canCreateDirectories = true; + fakePlatformImplementation + ..setExpectations(canCreateDirectories: canCreateDirectories) + ..setPathsResponse([expectedDirectoryPath]); + + final String? directoryPath = await getDirectoryPath( + canCreateDirectories: canCreateDirectories, + ); + expect(directoryPath, expectedDirectoryPath); + }); }); group('getDirectoryPaths', () { @@ -330,6 +355,17 @@ void main() { ); expect(directoryPaths, expectedDirectoryPaths); }); + test('sets to enable de creation of new directories', () async { + const bool canCreateDirectories = true; + fakePlatformImplementation + ..setExpectations(canCreateDirectories: canCreateDirectories) + ..setPathsResponse(expectedDirectoryPaths); + + final List directoryPaths = await getDirectoryPaths( + canCreateDirectories: canCreateDirectories, + ); + expect(directoryPaths, expectedDirectoryPaths); + }); }); } @@ -341,6 +377,7 @@ class FakeFileSelector extends Fake String? initialDirectory; String? confirmButtonText; String? suggestedName; + bool? canCreateDirectories; // Return values. List? files; List? paths; @@ -351,11 +388,13 @@ class FakeFileSelector extends Fake String? initialDirectory, String? suggestedName, String? confirmButtonText, + bool? canCreateDirectories, }) { this.acceptedTypeGroups = acceptedTypeGroups; this.initialDirectory = initialDirectory; this.suggestedName = suggestedName; this.confirmButtonText = confirmButtonText; + this.canCreateDirectories = canCreateDirectories; } // ignore: use_setters_to_change_properties @@ -436,9 +475,19 @@ class FakeFileSelector extends Fake Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, + bool canCreateDirectories = true, }) async { expect(initialDirectory, this.initialDirectory); expect(confirmButtonText, this.confirmButtonText); + expect(canCreateDirectories, this.canCreateDirectories); + return paths?[0]; + } + + @override + Future getDirectoryPathWithOptions(FileDialogOptions options) async { + expect(options.initialDirectory, initialDirectory); + expect(options.confirmButtonText, confirmButtonText); + expect(options.canCreateDirectories, canCreateDirectories); return paths?[0]; } @@ -446,9 +495,21 @@ class FakeFileSelector extends Fake Future> getDirectoryPaths({ String? initialDirectory, String? confirmButtonText, + bool canCreateDirectories = true, }) async { expect(initialDirectory, this.initialDirectory); expect(confirmButtonText, this.confirmButtonText); + expect(canCreateDirectories, this.canCreateDirectories); + return paths!; + } + + @override + Future> getDirectoryPathsWithOptions( + FileDialogOptions options, + ) async { + expect(options.initialDirectory, initialDirectory); + expect(options.confirmButtonText, confirmButtonText); + expect(options.canCreateDirectories, canCreateDirectories); return paths!; } } diff --git a/packages/file_selector/file_selector_android/CHANGELOG.md b/packages/file_selector/file_selector_android/CHANGELOG.md index 1401245d3c0..e5f67209022 100644 --- a/packages/file_selector/file_selector_android/CHANGELOG.md +++ b/packages/file_selector/file_selector_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.2 + +* Adds `getDirectoryPathWithOptions` implementation. + ## 0.5.1+16 * Bumps com.android.tools.build:gradle to 8.12.1. diff --git a/packages/file_selector/file_selector_android/example/pubspec.yaml b/packages/file_selector/file_selector_android/example/pubspec.yaml index 8b7074557c2..9f69522c0e2 100644 --- a/packages/file_selector/file_selector_android/example/pubspec.yaml +++ b/packages/file_selector/file_selector_android/example/pubspec.yaml @@ -29,3 +29,7 @@ dev_dependencies: flutter: uses-material-design: true +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector_android/lib/src/file_selector_android.dart b/packages/file_selector/file_selector_android/lib/src/file_selector_android.dart index 52206ebba17..8c7660a89de 100644 --- a/packages/file_selector/file_selector_android/lib/src/file_selector_android.dart +++ b/packages/file_selector/file_selector_android/lib/src/file_selector_android.dart @@ -53,7 +53,14 @@ class FileSelectorAndroid extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - return _api.getDirectoryPath(initialDirectory); + return getDirectoryPathWithOptions( + FileDialogOptions(initialDirectory: initialDirectory), + ); + } + + @override + Future getDirectoryPathWithOptions(FileDialogOptions options) async { + return _api.getDirectoryPath(options.initialDirectory); } XFile _xFileFromFileResponse(FileResponse file) { diff --git a/packages/file_selector/file_selector_android/pubspec.yaml b/packages/file_selector/file_selector_android/pubspec.yaml index 3ab5d809c24..6b6b3279ca1 100644 --- a/packages/file_selector/file_selector_android/pubspec.yaml +++ b/packages/file_selector/file_selector_android/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_android description: Android implementation of the file_selector package. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.1+16 +version: 0.5.2 environment: sdk: ^3.7.0 @@ -34,3 +34,7 @@ topics: - files - file-selection - file-selector +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index bcff55dae5a..775c9aac657 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.4 +* Adds `getDirectoryPathWithOptions` and `getDirectoryPathsWithOptions` implementations. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 0.9.3+2 diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index ae1fd0f3234..645df1c38a9 100644 --- a/packages/file_selector/file_selector_linux/example/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml @@ -20,3 +20,7 @@ dev_dependencies: flutter: uses-material-design: true +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart index 60aa454828d..83785767b13 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,6 +94,7 @@ class FileSelectorLinux extends FileSelectorPlatform { currentFolderPath: options.initialDirectory, currentName: options.suggestedName, acceptButtonLabel: options.confirmButtonText, + createFolders: options.canCreateDirectories, ), ); return paths.isEmpty ? null : FileSaveLocation(paths.first); @@ -104,11 +105,22 @@ class FileSelectorLinux extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { + return getDirectoryPathWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + ), + ); + } + + @override + Future getDirectoryPathWithOptions(FileDialogOptions options) async { final List paths = await _hostApi.showFileChooser( PlatformFileChooserActionType.chooseDirectory, PlatformFileChooserOptions( - currentFolderPath: initialDirectory, - acceptButtonLabel: confirmButtonText, + currentFolderPath: options.initialDirectory, + acceptButtonLabel: options.confirmButtonText, + createFolders: options.canCreateDirectories, selectMultiple: false, ), ); @@ -120,11 +132,24 @@ class FileSelectorLinux extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { + return getDirectoryPathsWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + ), + ); + } + + @override + Future> getDirectoryPathsWithOptions( + FileDialogOptions options, + ) async { return _hostApi.showFileChooser( PlatformFileChooserActionType.chooseDirectory, PlatformFileChooserOptions( - currentFolderPath: initialDirectory, - acceptButtonLabel: confirmButtonText, + currentFolderPath: options.initialDirectory, + acceptButtonLabel: options.confirmButtonText, + createFolders: options.canCreateDirectories, selectMultiple: true, ), ); diff --git a/packages/file_selector/file_selector_linux/lib/src/messages.g.dart b/packages/file_selector/file_selector_linux/lib/src/messages.g.dart index 6d5a9b84775..eba05390c38 100644 --- a/packages/file_selector/file_selector_linux/lib/src/messages.g.dart +++ b/packages/file_selector/file_selector_linux/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 (v22.6.2), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), 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, no_leading_underscores_for_local_identifiers @@ -59,6 +59,7 @@ class PlatformFileChooserOptions { this.currentName, this.acceptButtonLabel, this.selectMultiple, + this.createFolders, }); List? allowedFileTypes; @@ -74,6 +75,11 @@ class PlatformFileChooserOptions { /// Nullable because it does not apply to the "save" action. bool? selectMultiple; + /// Whether to allow new folders creation. + /// + /// Nullable because it does not apply to the "open" action. + bool? createFolders; + Object encode() { return [ allowedFileTypes, @@ -81,6 +87,7 @@ class PlatformFileChooserOptions { currentName, acceptButtonLabel, selectMultiple, + createFolders, ]; } @@ -93,6 +100,7 @@ class PlatformFileChooserOptions { currentName: result[2] as String?, acceptButtonLabel: result[3] as String?, selectMultiple: result[4] as bool?, + createFolders: result[5] as bool?, ); } } diff --git a/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc b/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc index e8095d589ea..88679b9944e 100644 --- a/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc +++ b/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc @@ -94,6 +94,13 @@ static GtkFileChooserNative* create_dialog( } } + const gboolean* create_folders = + ffs_platform_file_chooser_options_get_create_folders(options); + if (create_folders != nullptr) { + gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dialog), + *create_folders); + } + return GTK_FILE_CHOOSER_NATIVE(g_object_ref(dialog)); } diff --git a/packages/file_selector/file_selector_linux/linux/messages.g.cc b/packages/file_selector/file_selector_linux/linux/messages.g.cc index 6c6bfc77e90..c24ff23184c 100644 --- a/packages/file_selector/file_selector_linux/linux/messages.g.cc +++ b/packages/file_selector/file_selector_linux/linux/messages.g.cc @@ -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 (v22.6.2), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon #include "messages.g.h" @@ -84,6 +84,7 @@ struct _FfsPlatformFileChooserOptions { gchar* current_name; gchar* accept_button_label; gboolean* select_multiple; + gboolean* create_folders; }; G_DEFINE_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_options, @@ -97,6 +98,7 @@ static void ffs_platform_file_chooser_options_dispose(GObject* object) { g_clear_pointer(&self->current_name, g_free); g_clear_pointer(&self->accept_button_label, g_free); g_clear_pointer(&self->select_multiple, g_free); + g_clear_pointer(&self->create_folders, g_free); G_OBJECT_CLASS(ffs_platform_file_chooser_options_parent_class) ->dispose(object); } @@ -112,7 +114,7 @@ static void ffs_platform_file_chooser_options_class_init( FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new( FlValue* allowed_file_types, const gchar* current_folder_path, const gchar* current_name, const gchar* accept_button_label, - gboolean* select_multiple) { + gboolean* select_multiple, gboolean* create_folders) { FfsPlatformFileChooserOptions* self = FFS_PLATFORM_FILE_CHOOSER_OPTIONS( g_object_new(ffs_platform_file_chooser_options_get_type(), nullptr)); if (allowed_file_types != nullptr) { @@ -141,6 +143,12 @@ FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new( } else { self->select_multiple = nullptr; } + if (create_folders != nullptr) { + self->create_folders = static_cast(malloc(sizeof(gboolean))); + *self->create_folders = *create_folders; + } else { + self->create_folders = nullptr; + } return self; } @@ -174,6 +182,12 @@ gboolean* ffs_platform_file_chooser_options_get_select_multiple( return self->select_multiple; } +gboolean* ffs_platform_file_chooser_options_get_create_folders( + FfsPlatformFileChooserOptions* self) { + g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); + return self->create_folders; +} + static FlValue* ffs_platform_file_chooser_options_to_list( FfsPlatformFileChooserOptions* self) { FlValue* values = fl_value_new_list(); @@ -194,6 +208,9 @@ static FlValue* ffs_platform_file_chooser_options_to_list( fl_value_append_take(values, self->select_multiple != nullptr ? fl_value_new_bool(*self->select_multiple) : fl_value_new_null()); + fl_value_append_take(values, self->create_folders != nullptr + ? fl_value_new_bool(*self->create_folders) + : fl_value_new_null()); return values; } @@ -226,9 +243,16 @@ ffs_platform_file_chooser_options_new_from_list(FlValue* values) { select_multiple_value = fl_value_get_bool(value4); select_multiple = &select_multiple_value; } + FlValue* value5 = fl_value_get_list_value(values, 5); + gboolean* create_folders = nullptr; + gboolean create_folders_value; + if (fl_value_get_type(value5) != FL_VALUE_TYPE_NULL) { + create_folders_value = fl_value_get_bool(value5); + create_folders = &create_folders_value; + } return ffs_platform_file_chooser_options_new( allowed_file_types, current_folder_path, current_name, - accept_button_label, select_multiple); + accept_button_label, select_multiple, create_folders); } struct _FfsMessageCodec { diff --git a/packages/file_selector/file_selector_linux/linux/messages.g.h b/packages/file_selector/file_selector_linux/linux/messages.g.h index 50810d922a3..203f2a3b65d 100644 --- a/packages/file_selector/file_selector_linux/linux/messages.g.h +++ b/packages/file_selector/file_selector_linux/linux/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 (v22.6.2), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon #ifndef PIGEON_MESSAGES_G_H_ @@ -97,6 +97,7 @@ G_DECLARE_FINAL_TYPE(FfsPlatformFileChooserOptions, * current_name: field in this object. * accept_button_label: field in this object. * select_multiple: field in this object. + * create_folders: field in this object. * * Creates a new #PlatformFileChooserOptions object. * @@ -105,7 +106,7 @@ G_DECLARE_FINAL_TYPE(FfsPlatformFileChooserOptions, FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new( FlValue* allowed_file_types, const gchar* current_folder_path, const gchar* current_name, const gchar* accept_button_label, - gboolean* select_multiple); + gboolean* select_multiple, gboolean* create_folders); /** * ffs_platform_file_chooser_options_get_allowed_file_types @@ -164,6 +165,19 @@ const gchar* ffs_platform_file_chooser_options_get_accept_button_label( gboolean* ffs_platform_file_chooser_options_get_select_multiple( FfsPlatformFileChooserOptions* object); +/** + * ffs_platform_file_chooser_options_get_create_folders + * @object: a #FfsPlatformFileChooserOptions. + * + * Whether to allow new folders creation. + * + * Nullable because it does not apply to the "open" action. + * + * Returns: the field value. + */ +gboolean* ffs_platform_file_chooser_options_get_create_folders( + FfsPlatformFileChooserOptions* object); + G_DECLARE_FINAL_TYPE(FfsMessageCodec, ffs_message_codec, FFS, MESSAGE_CODEC, FlStandardMessageCodec) diff --git a/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc b/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc index 3a3c8459e46..2678208255c 100644 --- a/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc +++ b/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc @@ -28,7 +28,7 @@ static const int platform_type_group_object_id = 130; TEST(FileSelectorPlugin, TestOpenSimple) { g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - nullptr); + nullptr, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_OPEN, @@ -45,7 +45,7 @@ TEST(FileSelectorPlugin, TestOpenMultiple) { gboolean select_multiple = true; g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - &select_multiple); + &select_multiple, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_OPEN, @@ -105,7 +105,7 @@ TEST(FileSelectorPlugin, TestOpenWithFilter) { g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(type_groups, nullptr, nullptr, - nullptr, nullptr); + nullptr, nullptr, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_OPEN, @@ -149,7 +149,7 @@ TEST(FileSelectorPlugin, TestOpenWithFilter) { TEST(FileSelectorPlugin, TestSaveSimple) { g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - nullptr); + nullptr, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_SAVE, @@ -165,7 +165,7 @@ TEST(FileSelectorPlugin, TestSaveSimple) { TEST(FileSelectorPlugin, TestSaveWithArguments) { g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, "/tmp", "foo.txt", nullptr, - nullptr); + nullptr, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_SAVE, @@ -184,10 +184,48 @@ TEST(FileSelectorPlugin, TestSaveWithArguments) { // doesn't in a test context, so that's not currently validated. } +TEST(FileSelectorPlugin, TestSaveWithCreateFoldersEnabled) { + gboolean create_folders = true; + g_autoptr(FfsPlatformFileChooserOptions) options = + ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, + nullptr, &create_folders); + + g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( + nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_SAVE, + options); + + ASSERT_NE(dialog, nullptr); + EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), + GTK_FILE_CHOOSER_ACTION_SAVE); + EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), + false); + EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)), + create_folders); +} + +TEST(FileSelectorPlugin, TestSaveWithCreateFoldersDisabled) { + gboolean create_folders = false; + g_autoptr(FfsPlatformFileChooserOptions) options = + ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, + nullptr, &create_folders); + + g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( + nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_SAVE, + options); + + ASSERT_NE(dialog, nullptr); + EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), + GTK_FILE_CHOOSER_ACTION_SAVE); + EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), + false); + EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)), + create_folders); +} + TEST(FileSelectorPlugin, TestGetDirectory) { g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - nullptr); + nullptr, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, @@ -205,7 +243,7 @@ TEST(FileSelectorPlugin, TestGetMultipleDirectories) { gboolean select_multiple = true; g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - &select_multiple); + &select_multiple, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, @@ -219,6 +257,42 @@ TEST(FileSelectorPlugin, TestGetMultipleDirectories) { true); } +TEST(FileSelectorPlugin, TestGetDirectoryWithCreateFoldersEnabled) { + gboolean create_folders = true; + g_autoptr(FfsPlatformFileChooserOptions) options = + ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, + nullptr, &create_folders); + + g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( + nullptr, + FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_CHOOSE_DIRECTORY, + options); + + ASSERT_NE(dialog, nullptr); + EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)), + create_folders); +} + +TEST(FileSelectorPlugin, TestGetDirectoryWithCreateFoldersDisabled) { + gboolean create_folders = false; + g_autoptr(FfsPlatformFileChooserOptions) options = + ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, + nullptr, &create_folders); + + g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( + nullptr, + FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_CHOOSE_DIRECTORY, + options); + + ASSERT_NE(dialog, nullptr); + EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)), + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)), + create_folders); +} + static gint mock_run_dialog_cancel(GtkNativeDialog* dialog) { return GTK_RESPONSE_CANCEL; } @@ -226,7 +300,7 @@ static gint mock_run_dialog_cancel(GtkNativeDialog* dialog) { TEST(FileSelectorPlugin, TestGetDirectoryCancel) { g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - nullptr); + nullptr, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, diff --git a/packages/file_selector/file_selector_linux/pigeons/messages.dart b/packages/file_selector/file_selector_linux/pigeons/messages.dart index 3f393e88a6a..9419823b167 100644 --- a/packages/file_selector/file_selector_linux/pigeons/messages.dart +++ b/packages/file_selector/file_selector_linux/pigeons/messages.dart @@ -39,6 +39,7 @@ class PlatformFileChooserOptions { required this.currentName, required this.acceptButtonLabel, this.selectMultiple, + this.createFolders, }); final List? allowedFileTypes; @@ -50,6 +51,11 @@ class PlatformFileChooserOptions { /// /// Nullable because it does not apply to the "save" action. final bool? selectMultiple; + + /// Whether to allow new folders creation. + /// + /// Nullable because it does not apply to the "open" action. + final bool? createFolders; } @HostApi(dartHostTestHandler: 'TestFileSelectorApi') diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index e5450ed6da9..e110e0196c5 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.3+2 +version: 0.9.4 environment: sdk: ^3.7.0 @@ -31,3 +31,7 @@ topics: - files - file-selection - file-selector +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart index 973a8c61cbd..b8bbf9b4f66 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 @@ -385,6 +385,45 @@ void main() { }); }); + group('getDirectoryPathWithOptions', () { + test('passes the core flags correctly', () async { + const String path = '/foo/bar'; + api.result = [path]; + + expect( + await plugin.getDirectoryPathWithOptions(const FileDialogOptions()), + path, + ); + + expect(api.passedType, PlatformFileChooserActionType.chooseDirectory); + expect(api.passedOptions?.selectMultiple, false); + }); + + test('passes initialDirectory correctly', () async { + const String path = '/example/directory'; + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(initialDirectory: path), + ); + + expect(api.passedOptions?.currentFolderPath, path); + }); + + test('passes confirmButtonText correctly', () async { + const String button = 'Select Folder'; + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(confirmButtonText: button), + ); + expect(api.passedOptions?.acceptButtonLabel, button); + }); + + test('passes canCreateDirectories correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(canCreateDirectories: true), + ); + expect(api.passedOptions?.createFolders, true); + }); + }); + group('getDirectoryPaths', () { test('passes the core flags correctly', () async { api.result = ['/foo/bar', 'baz']; @@ -415,6 +454,52 @@ void main() { expect(api.passedOptions?.selectMultiple, true); }); }); + + group('getDirectoryPathsWithOptions', () { + test('passes the core flags correctly', () async { + api.result = ['/foo/bar', 'baz']; + + expect( + await plugin.getDirectoryPathsWithOptions(const FileDialogOptions()), + api.result, + ); + + expect(api.passedType, PlatformFileChooserActionType.chooseDirectory); + expect(api.passedOptions?.selectMultiple, true); + }); + + test('passes initialDirectory correctly', () async { + const String path = '/example/directory'; + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(initialDirectory: path), + ); + + expect(api.passedOptions?.currentFolderPath, path); + }); + + test('passes confirmButtonText correctly', () async { + const String button = 'Select one or mode folders'; + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(confirmButtonText: button), + ); + + expect(api.passedOptions?.acceptButtonLabel, button); + }); + + test('passes canCreateDirectories flag correctly', () async { + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(canCreateDirectories: true), + ); + + expect(api.passedOptions?.createFolders, true); + }); + + test('passes multiple flag correctly', () async { + await plugin.getDirectoryPathsWithOptions(const FileDialogOptions()); + + expect(api.passedOptions?.selectMultiple, true); + }); + }); } /// Fake implementation that stores arguments and provides a canned response. diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index 0404c64bbca..eb300d7fe3c 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.5 +* Adds `getDirectoryPathWithOptions` and `getDirectoryPathsWithOptions` implementations. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 0.9.4+4 diff --git a/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart b/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart index f7b607c33ff..afc7c3e73c3 100644 --- a/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart @@ -14,7 +14,12 @@ class GetDirectoryPage extends StatelessWidget { Future _getDirectoryPath(BuildContext context) async { const String confirmButtonText = 'Choose'; final String? directoryPath = await FileSelectorPlatform.instance - .getDirectoryPath(confirmButtonText: confirmButtonText); + .getDirectoryPathWithOptions( + const FileDialogOptions( + confirmButtonText: confirmButtonText, + canCreateDirectories: true, + ), + ); if (directoryPath == null) { // Operation was canceled by the user. return; diff --git a/packages/file_selector/file_selector_macos/example/lib/get_multiple_directories_page.dart b/packages/file_selector/file_selector_macos/example/lib/get_multiple_directories_page.dart index 1a236605e53..4b471d26e80 100644 --- a/packages/file_selector/file_selector_macos/example/lib/get_multiple_directories_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/get_multiple_directories_page.dart @@ -14,7 +14,12 @@ class GetMultipleDirectoriesPage extends StatelessWidget { Future _getDirectoryPaths(BuildContext context) async { const String confirmButtonText = 'Choose'; final List directoriesPaths = await FileSelectorPlatform.instance - .getDirectoryPaths(confirmButtonText: confirmButtonText); + .getDirectoryPathsWithOptions( + const FileDialogOptions( + confirmButtonText: confirmButtonText, + canCreateDirectories: true, + ), + ); if (directoriesPaths.isEmpty) { // Operation was canceled by the user. return; diff --git a/packages/file_selector/file_selector_macos/example/macos/Podfile b/packages/file_selector/file_selector_macos/example/macos/Podfile index ae77cc1d426..66f6172bbb3 100644 --- a/packages/file_selector/file_selector_macos/example/macos/Podfile +++ b/packages/file_selector/file_selector_macos/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj b/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj index f5dfb171d14..6797fa22411 100644 --- a/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj @@ -232,6 +232,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + D670A6A16AFB08D3E6D21B41 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -379,6 +380,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + D670A6A16AFB08D3E6D21B41 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -466,7 +484,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -599,7 +617,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -646,7 +664,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift index 12420d1ad46..26d1c4f9ac4 100644 --- a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift +++ b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift @@ -330,6 +330,10 @@ class ExampleTests: XCTestCase { wait(for: [called]) XCTAssertNotNil(panelController.savePanel) + if let panel = panelController.savePanel { + // By default, "New Folder" button is visible for Save dialogs + XCTAssertTrue(panel.canCreateDirectories) + } } func testSaveWithArguments() throws { @@ -365,6 +369,35 @@ class ExampleTests: XCTestCase { } } + func testSaveNewFolderHidden() throws { + let panelController = TestPanelController() + let plugin = FileSelectorPlugin( + viewProvider: TestViewProvider(), + panelController: panelController) + + let returnPath = "/foo/bar" + panelController.saveURL = URL(fileURLWithPath: returnPath) + + let called = XCTestExpectation() + let options = SavePanelOptions(canCreateDirectories: false) + + plugin.displaySavePanel(options: options) { result in + switch result { + case .success(let path): + XCTAssertEqual(path, returnPath) + case .failure(let error): + XCTFail("\(error)") + } + called.fulfill() + } + + wait(for: [called]) + XCTAssertNotNil(panelController.savePanel) + if let panel = panelController.savePanel { + XCTAssertFalse(panel.canCreateDirectories) + } + } + func testSaveCancel() throws { let panelController = TestPanelController() let plugin = FileSelectorPlugin( @@ -421,6 +454,8 @@ class ExampleTests: XCTestCase { // The Dart API only allows a single directory to be returned, so users shouldn't be allowed // to select multiple. XCTAssertFalse(panel.allowsMultipleSelection) + // By default, "New Folder" button is hidden for Choose Directory dialogs. + XCTAssertFalse(panel.canCreateDirectories) } } @@ -482,6 +517,8 @@ class ExampleTests: XCTestCase { // For consistency across platforms, file selection is disabled. XCTAssertFalse(panel.canChooseFiles) XCTAssertTrue(panel.allowsMultipleSelection) + // By default, "New Folder" button is hidden for Choose Directory dialogs. + XCTAssertFalse(panel.canCreateDirectories) } } @@ -510,4 +547,37 @@ class ExampleTests: XCTestCase { wait(for: [called]) XCTAssertNotNil(panelController.openPanel) } + + func testGetDirectoryNewFolderVisible() throws { + let panelController = TestPanelController() + let plugin = FileSelectorPlugin( + viewProvider: TestViewProvider(), + panelController: panelController) + + let returnPath = "/foo/bar" + panelController.openURLs = [URL(fileURLWithPath: returnPath)] + + let called = XCTestExpectation() + let options = OpenPanelOptions( + allowsMultipleSelection: false, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions(canCreateDirectories: true)) + + plugin.displayOpenPanel(options: options) { result in + switch result { + case .success(let paths): + XCTAssertEqual(paths[0], returnPath) + case .failure(let error): + XCTFail("\(error)") + } + called.fulfill() + } + + wait(for: [called]) + XCTAssertNotNil(panelController.openPanel) + if let panel = panelController.openPanel { + XCTAssertTrue(panel.canCreateDirectories) + } + } } diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index d27a6935bcf..1075e1d96fc 100644 --- a/packages/file_selector/file_selector_macos/example/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/example/pubspec.yaml @@ -25,3 +25,7 @@ dev_dependencies: flutter: uses-material-design: true +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart index 6f71f837f63..fd5d4e9a1d2 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 @@ -86,6 +86,7 @@ class FileSelectorMacOS extends FileSelectorPlatform { directoryPath: options.initialDirectory, nameFieldStringValue: options.suggestedName, prompt: options.confirmButtonText, + canCreateDirectories: options.canCreateDirectories, ), ); return path == null ? null : FileSaveLocation(path); @@ -96,14 +97,25 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { + return getDirectoryPathWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + ), + ); + } + + @override + Future getDirectoryPathWithOptions(FileDialogOptions options) async { final List paths = await _hostApi.displayOpenPanel( OpenPanelOptions( allowsMultipleSelection: false, canChooseDirectories: true, canChooseFiles: false, baseOptions: SavePanelOptions( - directoryPath: initialDirectory, - prompt: confirmButtonText, + directoryPath: options.initialDirectory, + prompt: options.confirmButtonText, + canCreateDirectories: options.canCreateDirectories, ), ), ); @@ -115,14 +127,27 @@ class FileSelectorMacOS extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { + return getDirectoryPathsWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + ), + ); + } + + @override + Future> getDirectoryPathsWithOptions( + FileDialogOptions options, + ) async { final List paths = await _hostApi.displayOpenPanel( OpenPanelOptions( allowsMultipleSelection: true, canChooseDirectories: true, canChooseFiles: false, baseOptions: SavePanelOptions( - directoryPath: initialDirectory, - prompt: confirmButtonText, + directoryPath: options.initialDirectory, + prompt: options.confirmButtonText, + canCreateDirectories: options.canCreateDirectories, ), ), ); diff --git a/packages/file_selector/file_selector_macos/lib/src/messages.g.dart b/packages/file_selector/file_selector_macos/lib/src/messages.g.dart index 2f0bb410d14..3c9234f14ec 100644 --- a/packages/file_selector/file_selector_macos/lib/src/messages.g.dart +++ b/packages/file_selector/file_selector_macos/lib/src/messages.g.dart @@ -108,6 +108,7 @@ class SavePanelOptions { this.directoryPath, this.nameFieldStringValue, this.prompt, + this.canCreateDirectories, }); AllowedTypes? allowedFileTypes; @@ -118,12 +119,15 @@ class SavePanelOptions { String? prompt; + bool? canCreateDirectories; + List _toList() { return [ allowedFileTypes, directoryPath, nameFieldStringValue, prompt, + canCreateDirectories, ]; } @@ -138,6 +142,7 @@ class SavePanelOptions { directoryPath: result[1] as String?, nameFieldStringValue: result[2] as String?, prompt: result[3] as String?, + canCreateDirectories: result[4] as bool?, ); } diff --git a/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/FileSelectorPlugin.swift b/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/FileSelectorPlugin.swift index d231aeb6fd5..0506bd6f68c 100644 --- a/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/FileSelectorPlugin.swift +++ b/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/FileSelectorPlugin.swift @@ -128,6 +128,10 @@ public class FileSelectorPlugin: NSObject, FlutterPlugin, FileSelectorApi { } } } + + if let canCreateDirectories = options.canCreateDirectories { + panel.canCreateDirectories = canCreateDirectories + } } /// Configures an NSOpenPanel based on channel method call arguments. diff --git a/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/messages.g.swift b/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/messages.g.swift index e5b6450f9aa..208821fc5c0 100644 --- a/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/messages.g.swift +++ b/packages/file_selector/file_selector_macos/macos/file_selector_macos/Sources/file_selector_macos/messages.g.swift @@ -175,6 +175,7 @@ struct SavePanelOptions: Hashable { var directoryPath: String? = nil var nameFieldStringValue: String? = nil var prompt: String? = nil + var canCreateDirectories: Bool? = nil // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SavePanelOptions? { @@ -182,12 +183,14 @@ struct SavePanelOptions: Hashable { let directoryPath: String? = nilOrValue(pigeonVar_list[1]) let nameFieldStringValue: String? = nilOrValue(pigeonVar_list[2]) let prompt: String? = nilOrValue(pigeonVar_list[3]) + let canCreateDirectories: Bool? = nilOrValue(pigeonVar_list[4]) return SavePanelOptions( allowedFileTypes: allowedFileTypes, directoryPath: directoryPath, nameFieldStringValue: nameFieldStringValue, - prompt: prompt + prompt: prompt, + canCreateDirectories: canCreateDirectories ) } func toList() -> [Any?] { @@ -196,6 +199,7 @@ struct SavePanelOptions: Hashable { directoryPath, nameFieldStringValue, prompt, + canCreateDirectories, ] } static func == (lhs: SavePanelOptions, rhs: SavePanelOptions) -> Bool { diff --git a/packages/file_selector/file_selector_macos/pigeons/messages.dart b/packages/file_selector/file_selector_macos/pigeons/messages.dart index 34268b5fc5d..6097af6c61a 100644 --- a/packages/file_selector/file_selector_macos/pigeons/messages.dart +++ b/packages/file_selector/file_selector_macos/pigeons/messages.dart @@ -36,11 +36,13 @@ class SavePanelOptions { this.directoryPath, this.nameFieldStringValue, this.prompt, + this.canCreateDirectories, }); final AllowedTypes? allowedFileTypes; final String? directoryPath; final String? nameFieldStringValue; final String? prompt; + final bool? canCreateDirectories; } /// Options for open panels. diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index 0890520cc13..9b98d4e0033 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.4+4 +version: 0.9.5 environment: sdk: ^3.7.0 @@ -33,3 +33,7 @@ topics: - files - file-selection - file-selector +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart index 1033ae5f3ae..a9e25e9ff41 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 @@ -566,6 +566,78 @@ void main() { }); }); + group('getDirectoryPathWithOptions', () { + test('works as expected with no arguments', () async { + when( + mockApi.displayOpenPanel(any), + ).thenAnswer((_) async => ['foo']); + + final String? path = await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(), + ); + + expect(path, 'foo'); + final VerificationResult result = verify( + mockApi.displayOpenPanel(captureAny), + ); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.allowsMultipleSelection, false); + expect(options.canChooseFiles, false); + expect(options.canChooseDirectories, true); + expect(options.baseOptions.allowedFileTypes, null); + expect(options.baseOptions.directoryPath, null); + expect(options.baseOptions.nameFieldStringValue, null); + expect(options.baseOptions.canCreateDirectories, null); + expect(options.baseOptions.prompt, null); + }); + + test('handles cancel', () async { + when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => []); + + final String? path = await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(), + ); + + expect(path, null); + }); + + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(initialDirectory: '/example/directory'), + ); + + final VerificationResult result = verify( + mockApi.displayOpenPanel(captureAny), + ); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.directoryPath, '/example/directory'); + }); + + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(confirmButtonText: 'Open File'), + ); + + final VerificationResult result = verify( + mockApi.displayOpenPanel(captureAny), + ); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.prompt, 'Open File'); + }); + + test('passes canCreateDirectories correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(canCreateDirectories: false), + ); + + final VerificationResult result = verify( + mockApi.displayOpenPanel(captureAny), + ); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.canCreateDirectories, false); + }); + }); + group('getDirectoryPaths', () { test('works as expected with no arguments', () async { when(mockApi.displayOpenPanel(any)).thenAnswer( @@ -624,4 +696,84 @@ void main() { expect(options.baseOptions.directoryPath, '/example/directory'); }); }); + + group('getDirectoryPathsWithOptions', () { + test('works as expected with no arguments', () async { + when(mockApi.displayOpenPanel(any)).thenAnswer( + (_) async => [ + 'firstDirectory', + 'secondDirectory', + 'thirdDirectory', + ], + ); + + final List path = await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(), + ); + + expect(path, [ + 'firstDirectory', + 'secondDirectory', + 'thirdDirectory', + ]); + final VerificationResult result = verify( + mockApi.displayOpenPanel(captureAny), + ); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.allowsMultipleSelection, true); + expect(options.canChooseFiles, false); + expect(options.canChooseDirectories, true); + expect(options.baseOptions.allowedFileTypes, null); + expect(options.baseOptions.directoryPath, null); + expect(options.baseOptions.nameFieldStringValue, null); + expect(options.baseOptions.canCreateDirectories, null); + expect(options.baseOptions.prompt, null); + }); + + test('handles cancel', () async { + when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => []); + + final List paths = await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(), + ); + + expect(paths, []); + }); + + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(confirmButtonText: 'Select directories'), + ); + + final VerificationResult result = verify( + mockApi.displayOpenPanel(captureAny), + ); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.prompt, 'Select directories'); + }); + + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(initialDirectory: '/example/directory'), + ); + + final VerificationResult result = verify( + mockApi.displayOpenPanel(captureAny), + ); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.directoryPath, '/example/directory'); + }); + + test('passes canCreateDirectories correctly', () async { + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(canCreateDirectories: false), + ); + + final VerificationResult result = verify( + mockApi.displayOpenPanel(captureAny), + ); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.canCreateDirectories, false); + }); + }); } diff --git a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md index 4274cd53af5..a4a18d538bf 100644 --- a/packages/file_selector/file_selector_platform_interface/CHANGELOG.md +++ b/packages/file_selector/file_selector_platform_interface/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.7.0 +* Adds `canCreateDirectories` parameter to `FileDialogOptions` to control the visibility of the New Folder button in file dialogs on supported platforms. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 2.6.2 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 7fe40c72f3a..2dd5f040b22 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 @@ -96,8 +96,7 @@ abstract class FileSelectorPlatform extends PlatformInterface { /// Opens a file dialog for loading directories and returns a directory path. /// /// 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 getDirectoryPathWithOptions instead') Future getDirectoryPath({ String? initialDirectory, String? confirmButtonText, @@ -105,16 +104,42 @@ abstract class FileSelectorPlatform extends PlatformInterface { throw UnimplementedError('getDirectoryPath() has not been implemented.'); } + /// Opens a file dialog for loading directories and returns a directory path. + /// + /// The `options` argument controls additional settings that can be passed to + /// file dialog. See [FileDialogOptions] for more details. + /// + /// Returns `null` if the user cancels the operation. + Future getDirectoryPathWithOptions(FileDialogOptions options) { + return getDirectoryPath( + initialDirectory: options.initialDirectory, + confirmButtonText: options.confirmButtonText, + ); + } + /// 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. + @Deprecated('Use getDirectoryPathsWithOptions instead') Future> getDirectoryPaths({ String? initialDirectory, String? confirmButtonText, }) { throw UnimplementedError('getDirectoryPaths() has not been implemented.'); } + + /// Opens a file dialog for loading directories and returns multiple directory + /// paths. + /// + /// The `options` argument controls additional settings that can be passed to + /// the file dialog. See [FileDialogOptions] for more details. + /// + /// Returns an empty list if the user cancels the operation. + Future> getDirectoryPathsWithOptions(FileDialogOptions options) { + return getDirectoryPaths( + initialDirectory: options.initialDirectory, + confirmButtonText: options.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 index f456b4ae157..530e97d49dd 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 @@ -8,13 +8,24 @@ import 'package:flutter/foundation.dart' show immutable; @immutable class FileDialogOptions { /// Creates a new options set with the given settings. - const FileDialogOptions({this.initialDirectory, this.confirmButtonText}); + const FileDialogOptions({ + this.initialDirectory, + this.confirmButtonText, + this.canCreateDirectories, + }); /// The initial directory the dialog should open with. final String? initialDirectory; /// The label for the button that confirms selection. final String? confirmButtonText; + + /// Whether the user is allowed to create new directories in the dialog. + /// + /// If null, the platform will decide the default value. + /// + /// May not be supported on all platforms. + final bool? canCreateDirectories; } /// Configuration options for a save dialog. @@ -24,6 +35,7 @@ class SaveDialogOptions extends FileDialogOptions { const SaveDialogOptions({ super.initialDirectory, super.confirmButtonText, + super.canCreateDirectories, this.suggestedName, }); diff --git a/packages/file_selector/file_selector_platform_interface/pubspec.yaml b/packages/file_selector/file_selector_platform_interface/pubspec.yaml index b52bcc0944b..d68d95826d0 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.6.2 +version: 2.7.0 environment: sdk: ^3.7.0 diff --git a/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart b/packages/file_selector/file_selector_platform_interface/test/file_selector_platform_interface_test.dart index 7592c851227..ea0e6c5e5e7 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 @@ -20,6 +20,28 @@ void main() { }); }); + group('getDirectoryPath', () { + test('Should throw unimplemented exception', () async { + final FileSelectorPlatform fileSelector = ExtendsFileSelectorPlatform(); + + await expectLater(() async { + return fileSelector.getDirectoryPath(); + }, throwsA(isA())); + }); + }); + + group('getDirectoryPathWithOptions', () { + test('Should throw unimplemented exception', () async { + final FileSelectorPlatform fileSelector = ExtendsFileSelectorPlatform(); + + await expectLater(() async { + return fileSelector.getDirectoryPathWithOptions( + const FileDialogOptions(), + ); + }, throwsA(isA())); + }); + }); + group('getDirectoryPaths', () { test('Should throw unimplemented exception', () async { final FileSelectorPlatform fileSelector = ExtendsFileSelectorPlatform(); @@ -30,6 +52,18 @@ void main() { }); }); + group('getDirectoryPathsWithOptions', () { + test('Should throw unimplemented exception', () async { + final FileSelectorPlatform fileSelector = ExtendsFileSelectorPlatform(); + + await expectLater(() async { + return fileSelector.getDirectoryPathsWithOptions( + const FileDialogOptions(), + ); + }, throwsA(isA())); + }); + }); + test('getSaveLocation falls back to getSavePath by default', () async { final FileSelectorPlatform fileSelector = OldFileSelectorPlatformImplementation(); diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 5323271b741..14486f4dace 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.5 +* Adds `getDirectoryPathWithOptions` implementation. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 0.9.4+2 diff --git a/packages/file_selector/file_selector_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml index 96fdf0fad6e..eb1dd780fee 100644 --- a/packages/file_selector/file_selector_web/example/pubspec.yaml +++ b/packages/file_selector/file_selector_web/example/pubspec.yaml @@ -18,3 +18,7 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector_web/lib/file_selector_web.dart b/packages/file_selector/file_selector_web/lib/file_selector_web.dart index 2b8a611e950..243c600ce65 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 @@ -76,6 +76,11 @@ class FileSelectorWeb extends FileSelectorPlatform { String? confirmButtonText, }) async => null; + @override + Future getDirectoryPathWithOptions( + FileDialogOptions options, + ) async => null; + Future> _openFiles({ List? acceptedTypeGroups, bool multiple = false, diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 52cd67b2c53..9f2f62dbede 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.4+2 +version: 0.9.5 environment: sdk: ^3.7.0 @@ -32,3 +32,7 @@ topics: - files - file-selection - file-selector +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../packages/file_selector/file_selector_platform_interface} diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index 8eaeeb2292f..c845b3c1972 100644 --- a/packages/file_selector/file_selector_windows/CHANGELOG.md +++ b/packages/file_selector/file_selector_windows/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.9.4 +* Adds `getDirectoryPathWithOptions` and `getDirectoryPathsWithOptions` implementations. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 0.9.3+4 diff --git a/packages/file_selector/file_selector_windows/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml index 7414ec65fdb..05648feccd0 100644 --- a/packages/file_selector/file_selector_windows/example/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/example/pubspec.yaml @@ -25,3 +25,7 @@ dev_dependencies: flutter: uses-material-design: true +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../../packages/file_selector/file_selector_platform_interface} 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 510663d01ae..de94a743c5f 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 @@ -94,10 +94,20 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { + return getDirectoryPathWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + ), + ); + } + + @override + Future getDirectoryPathWithOptions(FileDialogOptions options) async { final FileDialogResult result = await _hostApi.showOpenDialog( SelectionOptions(selectFolders: true, allowedTypes: []), - initialDirectory, - confirmButtonText, + options.initialDirectory, + options.confirmButtonText, ); return result.paths.isEmpty ? null : result.paths.first; } @@ -107,14 +117,26 @@ class FileSelectorWindows extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { + return getDirectoryPathsWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + ), + ); + } + + @override + Future> getDirectoryPathsWithOptions( + FileDialogOptions options, + ) async { final FileDialogResult result = await _hostApi.showOpenDialog( SelectionOptions( allowMultiple: true, selectFolders: true, allowedTypes: [], ), - initialDirectory, - confirmButtonText, + options.initialDirectory, + options.confirmButtonText, ); return result.paths.isEmpty ? [] : List.from(result.paths); } diff --git a/packages/file_selector/file_selector_windows/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index 48966187bfd..0630a8b4a3f 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_windows description: Windows implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_windows issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.3+4 +version: 0.9.4 environment: sdk: ^3.7.0 @@ -33,3 +33,7 @@ topics: - files - file-selection - file-selector +# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE. +# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins +dependency_overrides: + file_selector_platform_interface: {path: ../../../packages/file_selector/file_selector_platform_interface}