diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index face0052b23..54d3a9e69e1 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.4 + +* Adds `getDirectoryPathWithOptions` and `getDirectoryPathsWithOptions` implementations. + ## 0.9.3+3 * Updates to Pigeon 26. diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml index bf4a8ca7eb4..73248a2704a 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.6.0 + file_selector_platform_interface: ^2.7.0 flutter: sdk: flutter 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 ae816093fea..f80c9f60d3b 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 0de39fbb36b..592db0366e3 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 // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.1.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -97,6 +97,7 @@ class PlatformFileChooserOptions { this.currentName, this.acceptButtonLabel, this.selectMultiple, + this.createFolders, }); List? allowedFileTypes; @@ -112,6 +113,11 @@ class PlatformFileChooserOptions { /// Nullable because it does not apply to the "save" action. bool? selectMultiple; + /// Whether to allow new folder creation. + /// + /// Nullable because it does not apply to the "open" action. + bool? createFolders; + List _toList() { return [ allowedFileTypes, @@ -119,6 +125,7 @@ class PlatformFileChooserOptions { currentName, acceptButtonLabel, selectMultiple, + createFolders, ]; } @@ -135,6 +142,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 e87f94d8bad..bd67979272b 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 158f23527aa..5788006d77d 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 // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.1.1), 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 8995bbc37f6..3d5c8c65806 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 // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v26.1.0), do not edit directly. +// Autogenerated from Pigeon (v26.1.1), 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 folder 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 841cc8f7f54..863ba187cf5 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 658450c2477..e700c6ae5bf 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 folder creation. + /// + /// Nullable because it does not apply to the "open" action. + final bool? createFolders; } @HostApi() diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index e6c03ec9818..810d3789e6c 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+3 +version: 0.9.4 environment: sdk: ^3.8.0 @@ -18,7 +18,7 @@ flutter: dependencies: cross_file: ^0.3.1 - file_selector_platform_interface: ^2.6.0 + file_selector_platform_interface: ^2.7.0 flutter: sdk: flutter 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 d6ddf3abf37..4f6786b5cfb 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 c43a073bf11..fe540f37b94 100644 --- a/packages/file_selector/file_selector_macos/CHANGELOG.md +++ b/packages/file_selector/file_selector_macos/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.5 + +* Adds `getDirectoryPathWithOptions` and `getDirectoryPathsWithOptions` implementations. + ## 0.9.4+6 * Updates to Pigeon 26. 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 fbc8d2673d9..edbd299fda2 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 be1bdfd40a0..c0b32ace41d 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/Runner.xcodeproj/project.pbxproj b/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj index ce516fb3512..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 */ 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 9ef419c9aaa..b68065387a0 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 79250222c3c..5c54c86f4af 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.6.0 + file_selector_platform_interface: ^2.7.0 flutter: sdk: flutter 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 9b18cc88ac3..dbcbd345fc2 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 @@ -91,6 +91,7 @@ class FileSelectorMacOS extends FileSelectorPlatform { directoryPath: options.initialDirectory, nameFieldStringValue: options.suggestedName, prompt: options.confirmButtonText, + canCreateDirectories: options.canCreateDirectories, ), ); return path == null ? null : FileSaveLocation(path); @@ -101,14 +102,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, ), ), ); @@ -120,14 +132,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 4fac7a015ac..f91bc53fee3 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 @@ -94,6 +94,7 @@ class SavePanelOptions { this.directoryPath, this.nameFieldStringValue, this.prompt, + this.canCreateDirectories, }); AllowedTypes? allowedFileTypes; @@ -104,12 +105,15 @@ class SavePanelOptions { String? prompt; + bool? canCreateDirectories; + List _toList() { return [ allowedFileTypes, directoryPath, nameFieldStringValue, prompt, + canCreateDirectories, ]; } @@ -124,6 +128,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 d35266df096..9fbee4489df 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 aab841779fd..d655618b4a9 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 8da0ef9fd26..e65780f0c20 100644 --- a/packages/file_selector/file_selector_macos/pigeons/messages.dart +++ b/packages/file_selector/file_selector_macos/pigeons/messages.dart @@ -35,11 +35,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 82d97e26a25..ea8afde8724 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+6 +version: 0.9.5 environment: sdk: ^3.9.0 @@ -18,7 +18,7 @@ flutter: dependencies: cross_file: ^0.3.1 - file_selector_platform_interface: ^2.6.0 + file_selector_platform_interface: ^2.7.0 flutter: sdk: flutter 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 edd4c2e2d7f..1a8d3bfcc11 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 @@ -485,6 +485,62 @@ void main() { }); }); + group('getDirectoryPathWithOptions', () { + test('works as expected with no arguments', () async { + api.result = ['foo']; + + final String? path = await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(), + ); + + expect(path, 'foo'); + final OpenPanelOptions options = api.passedOpenPanelOptions!; + 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 { + api.result = []; + + final String? path = await plugin.getDirectoryPath(); + + expect(path, null); + }); + + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(initialDirectory: '/example/directory'), + ); + + final OpenPanelOptions options = api.passedOpenPanelOptions!; + expect(options.baseOptions.directoryPath, '/example/directory'); + }); + + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(confirmButtonText: 'Open File'), + ); + + final OpenPanelOptions options = api.passedOpenPanelOptions!; + expect(options.baseOptions.prompt, 'Open File'); + }); + + test('passes canCreateDirectories correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(canCreateDirectories: false), + ); + + final OpenPanelOptions options = api.passedOpenPanelOptions!; + expect(options.baseOptions.canCreateDirectories, false); + }); + }); + group('getDirectoryPaths', () { test('works as expected with no arguments', () async { api.result = [ @@ -532,6 +588,72 @@ void main() { expect(options.baseOptions.directoryPath, '/example/directory'); }); }); + + group('getDirectoryPathsWithOptions', () { + test('works as expected with no arguments', () async { + api.result = [ + 'firstDirectory', + 'secondDirectory', + 'thirdDirectory', + ]; + + final List path = await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(), + ); + + expect(path, [ + 'firstDirectory', + 'secondDirectory', + 'thirdDirectory', + ]); + final OpenPanelOptions options = api.passedOpenPanelOptions!; + 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 { + api.result = []; + + final List paths = await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(), + ); + + expect(paths, []); + }); + + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(confirmButtonText: 'Select directories'), + ); + + final OpenPanelOptions options = api.passedOpenPanelOptions!; + expect(options.baseOptions.prompt, 'Select directories'); + }); + + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(initialDirectory: '/example/directory'), + ); + + final OpenPanelOptions options = api.passedOpenPanelOptions!; + expect(options.baseOptions.directoryPath, '/example/directory'); + }); + + test('passes canCreateDirectories correctly', () async { + await plugin.getDirectoryPathsWithOptions( + const FileDialogOptions(canCreateDirectories: false), + ); + + final OpenPanelOptions options = api.passedOpenPanelOptions!; + expect(options.baseOptions.canCreateDirectories, false); + }); + }); } /// Fake implementation that stores arguments and provides a canned response.