From efa1683d4f23d0c8819f71205353489625dc52be Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Fri, 5 Sep 2025 21:20:15 -0300 Subject: [PATCH 01/14] Add new file_selector param canCreateDirectories --- .../file_selector/example/pubspec.yaml | 4 + .../file_selector/lib/file_selector.dart | 28 +- .../file_selector/file_selector/pubspec.yaml | 4 + .../test/file_selector_test.dart | 5 + .../example/pubspec.yaml | 4 + .../lib/src/file_selector_android.dart | 9 +- .../file_selector_android/pubspec.yaml | 4 + .../file_selector_linux/example/pubspec.yaml | 4 + .../lib/file_selector_linux.dart | 31 +- .../lib/src/messages.g.dart | 73 ++-- .../linux/file_selector_plugin.cc | 7 + .../file_selector_linux/linux/messages.g.cc | 354 +++++++----------- .../file_selector_linux/linux/messages.g.h | 82 ++-- .../file_selector_linux/pigeons/messages.dart | 6 + .../file_selector_linux/pubspec.yaml | 4 + .../example/lib/get_directory_page.dart | 7 +- .../lib/get_multiple_directories_page.dart | 7 +- .../file_selector_macos/example/pubspec.yaml | 4 + .../lib/file_selector_macos.dart | 32 +- .../lib/src/messages.g.dart | 5 + .../FileSelectorPlugin.swift | 4 + .../file_selector_macos/messages.g.swift | 6 +- .../file_selector_macos/pigeons/messages.dart | 2 + .../file_selector_macos/pubspec.yaml | 4 + .../test/file_selector_macos_test.dart | 152 ++++++++ .../file_selector_web/example/pubspec.yaml | 4 + .../lib/file_selector_web.dart | 5 + .../file_selector_web/pubspec.yaml | 4 + .../example/pubspec.yaml | 4 + .../lib/file_selector_windows.dart | 30 +- .../file_selector_windows/pubspec.yaml | 4 + 31 files changed, 562 insertions(+), 331 deletions(-) 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..7b2586aeb91 100644 --- a/packages/file_selector/file_selector/lib/file_selector.dart +++ b/packages/file_selector/file_selector/lib/file_selector.dart @@ -121,14 +121,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 (if supported on the platform). +/// Currently only supported on Linux and macOS. +/// /// 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 +152,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 (if supported on the platform). +/// Currently only supported on Linux and macOS. +/// /// 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..806e60b11c3 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -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..aaa5647a947 100644 --- a/packages/file_selector/file_selector/test/file_selector_test.dart +++ b/packages/file_selector/file_selector/test/file_selector_test.dart @@ -341,6 +341,7 @@ class FakeFileSelector extends Fake String? initialDirectory; String? confirmButtonText; String? suggestedName; + bool canCreateDirectories = true; // Return values. List? files; List? paths; @@ -436,9 +437,11 @@ 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]; } @@ -446,9 +449,11 @@ 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!; } } 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..5da04fbd8c0 100644 --- a/packages/file_selector/file_selector_android/pubspec.yaml +++ b/packages/file_selector/file_selector_android/pubspec.yaml @@ -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/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..8f6c00fe719 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 @@ -104,11 +104,21 @@ 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, selectMultiple: false, ), ); @@ -120,11 +130,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..86315d1e3ab 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 @@ -19,7 +19,11 @@ PlatformException _createConnectionError(String channelName) { } /// A Pigeon representation of the GTK_FILE_CHOOSER_ACTION_* options. -enum PlatformFileChooserActionType { open, chooseDirectory, save } +enum PlatformFileChooserActionType { + open, + chooseDirectory, + save, +} /// A Pigeon representation of the Linux portion of an `XTypeGroup`. class PlatformTypeGroup { @@ -36,7 +40,11 @@ class PlatformTypeGroup { List mimeTypes; Object encode() { - return [label, extensions, mimeTypes]; + return [ + label, + extensions, + mimeTypes, + ]; } static PlatformTypeGroup decode(Object result) { @@ -59,6 +67,7 @@ class PlatformFileChooserOptions { this.currentName, this.acceptButtonLabel, this.selectMultiple, + this.createFolders, }); List? allowedFileTypes; @@ -74,6 +83,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,22 +95,24 @@ class PlatformFileChooserOptions { currentName, acceptButtonLabel, selectMultiple, + createFolders, ]; } static PlatformFileChooserOptions decode(Object result) { result as List; return PlatformFileChooserOptions( - allowedFileTypes: - (result[0] as List?)?.cast(), + allowedFileTypes: (result[0] as List?)?.cast(), currentFolderPath: result[1] as String?, currentName: result[2] as String?, acceptButtonLabel: result[3] as String?, selectMultiple: result[4] as bool?, + createFolders: result[5] as bool?, ); } } + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -104,13 +120,13 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is PlatformFileChooserActionType) { + } else if (value is PlatformFileChooserActionType) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is PlatformTypeGroup) { + } else if (value is PlatformTypeGroup) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is PlatformFileChooserOptions) { + } else if (value is PlatformFileChooserOptions) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else { @@ -121,14 +137,12 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; - return value == null - ? null - : PlatformFileChooserActionType.values[value]; - case 130: + return value == null ? null : PlatformFileChooserActionType.values[value]; + case 130: return PlatformTypeGroup.decode(readValue(buffer)!); - case 131: + case 131: return PlatformFileChooserOptions.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -140,12 +154,9 @@ class FileSelectorApi { /// Constructor for [FileSelectorApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - FileSelectorApi({ - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + FileSelectorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -156,21 +167,15 @@ class FileSelectorApi { /// list of selected paths. /// /// An empty list corresponds to a cancelled selection. - Future> showFileChooser( - PlatformFileChooserActionType type, - PlatformFileChooserOptions options, - ) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + Future> showFileChooser(PlatformFileChooserActionType type, PlatformFileChooserOptions options) async { + final String pigeonVar_channelName = 'dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([type, options]) - as List?; + await pigeonVar_channel.send([type, options]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { 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..ae98b994d5f 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" @@ -24,18 +24,15 @@ static void ffs_platform_type_group_dispose(GObject* object) { G_OBJECT_CLASS(ffs_platform_type_group_parent_class)->dispose(object); } -static void ffs_platform_type_group_init(FfsPlatformTypeGroup* self) {} +static void ffs_platform_type_group_init(FfsPlatformTypeGroup* self) { +} -static void ffs_platform_type_group_class_init( - FfsPlatformTypeGroupClass* klass) { +static void ffs_platform_type_group_class_init(FfsPlatformTypeGroupClass* klass) { G_OBJECT_CLASS(klass)->dispose = ffs_platform_type_group_dispose; } -FfsPlatformTypeGroup* ffs_platform_type_group_new(const gchar* label, - FlValue* extensions, - FlValue* mime_types) { - FfsPlatformTypeGroup* self = FFS_PLATFORM_TYPE_GROUP( - g_object_new(ffs_platform_type_group_get_type(), nullptr)); +FfsPlatformTypeGroup* ffs_platform_type_group_new(const gchar* label, FlValue* extensions, FlValue* mime_types) { + FfsPlatformTypeGroup* self = FFS_PLATFORM_TYPE_GROUP(g_object_new(ffs_platform_type_group_get_type(), nullptr)); self->label = g_strdup(label); self->extensions = fl_value_ref(extensions); self->mime_types = fl_value_ref(mime_types); @@ -65,8 +62,7 @@ static FlValue* ffs_platform_type_group_to_list(FfsPlatformTypeGroup* self) { return values; } -static FfsPlatformTypeGroup* ffs_platform_type_group_new_from_list( - FlValue* values) { +static FfsPlatformTypeGroup* ffs_platform_type_group_new_from_list(FlValue* values) { FlValue* value0 = fl_value_get_list_value(values, 0); const gchar* label = fl_value_get_string(value0); FlValue* value1 = fl_value_get_list_value(values, 1); @@ -84,121 +80,114 @@ struct _FfsPlatformFileChooserOptions { gchar* current_name; gchar* accept_button_label; gboolean* select_multiple; + gboolean* create_folders; }; -G_DEFINE_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_options, - G_TYPE_OBJECT) +G_DEFINE_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_options, G_TYPE_OBJECT) static void ffs_platform_file_chooser_options_dispose(GObject* object) { - FfsPlatformFileChooserOptions* self = - FFS_PLATFORM_FILE_CHOOSER_OPTIONS(object); + FfsPlatformFileChooserOptions* self = FFS_PLATFORM_FILE_CHOOSER_OPTIONS(object); g_clear_pointer(&self->allowed_file_types, fl_value_unref); g_clear_pointer(&self->current_folder_path, g_free); 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_OBJECT_CLASS(ffs_platform_file_chooser_options_parent_class) - ->dispose(object); + g_clear_pointer(&self->create_folders, g_free); + G_OBJECT_CLASS(ffs_platform_file_chooser_options_parent_class)->dispose(object); } -static void ffs_platform_file_chooser_options_init( - FfsPlatformFileChooserOptions* self) {} +static void ffs_platform_file_chooser_options_init(FfsPlatformFileChooserOptions* self) { +} -static void ffs_platform_file_chooser_options_class_init( - FfsPlatformFileChooserOptionsClass* klass) { +static void ffs_platform_file_chooser_options_class_init(FfsPlatformFileChooserOptionsClass* klass) { G_OBJECT_CLASS(klass)->dispose = ffs_platform_file_chooser_options_dispose; } -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) { - FfsPlatformFileChooserOptions* self = FFS_PLATFORM_FILE_CHOOSER_OPTIONS( - g_object_new(ffs_platform_file_chooser_options_get_type(), nullptr)); +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* 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) { self->allowed_file_types = fl_value_ref(allowed_file_types); - } else { + } + else { self->allowed_file_types = nullptr; } if (current_folder_path != nullptr) { self->current_folder_path = g_strdup(current_folder_path); - } else { + } + else { self->current_folder_path = nullptr; } if (current_name != nullptr) { self->current_name = g_strdup(current_name); - } else { + } + else { self->current_name = nullptr; } if (accept_button_label != nullptr) { self->accept_button_label = g_strdup(accept_button_label); - } else { + } + else { self->accept_button_label = nullptr; } if (select_multiple != nullptr) { self->select_multiple = static_cast(malloc(sizeof(gboolean))); *self->select_multiple = *select_multiple; - } else { + } + 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; } -FlValue* ffs_platform_file_chooser_options_get_allowed_file_types( - FfsPlatformFileChooserOptions* self) { +FlValue* ffs_platform_file_chooser_options_get_allowed_file_types(FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->allowed_file_types; } -const gchar* ffs_platform_file_chooser_options_get_current_folder_path( - FfsPlatformFileChooserOptions* self) { +const gchar* ffs_platform_file_chooser_options_get_current_folder_path(FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->current_folder_path; } -const gchar* ffs_platform_file_chooser_options_get_current_name( - FfsPlatformFileChooserOptions* self) { +const gchar* ffs_platform_file_chooser_options_get_current_name(FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->current_name; } -const gchar* ffs_platform_file_chooser_options_get_accept_button_label( - FfsPlatformFileChooserOptions* self) { +const gchar* ffs_platform_file_chooser_options_get_accept_button_label(FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->accept_button_label; } -gboolean* ffs_platform_file_chooser_options_get_select_multiple( - FfsPlatformFileChooserOptions* self) { +gboolean* ffs_platform_file_chooser_options_get_select_multiple(FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->select_multiple; } -static FlValue* ffs_platform_file_chooser_options_to_list( - FfsPlatformFileChooserOptions* self) { +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(); - fl_value_append_take(values, self->allowed_file_types != nullptr - ? fl_value_ref(self->allowed_file_types) - : fl_value_new_null()); - fl_value_append_take(values, - self->current_folder_path != nullptr - ? fl_value_new_string(self->current_folder_path) - : fl_value_new_null()); - fl_value_append_take(values, self->current_name != nullptr - ? fl_value_new_string(self->current_name) - : fl_value_new_null()); - fl_value_append_take(values, - self->accept_button_label != nullptr - ? fl_value_new_string(self->accept_button_label) - : fl_value_new_null()); - 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->allowed_file_types != nullptr ? fl_value_ref(self->allowed_file_types) : fl_value_new_null()); + fl_value_append_take(values, self->current_folder_path != nullptr ? fl_value_new_string(self->current_folder_path) : fl_value_new_null()); + fl_value_append_take(values, self->current_name != nullptr ? fl_value_new_string(self->current_name) : fl_value_new_null()); + fl_value_append_take(values, self->accept_button_label != nullptr ? fl_value_new_string(self->accept_button_label) : fl_value_new_null()); + 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; } -static FfsPlatformFileChooserOptions* -ffs_platform_file_chooser_options_new_from_list(FlValue* values) { +static FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new_from_list(FlValue* values) { FlValue* value0 = fl_value_get_list_value(values, 0); FlValue* allowed_file_types = nullptr; if (fl_value_get_type(value0) != FL_VALUE_TYPE_NULL) { @@ -226,152 +215,115 @@ ffs_platform_file_chooser_options_new_from_list(FlValue* values) { select_multiple_value = fl_value_get_bool(value4); select_multiple = &select_multiple_value; } - return ffs_platform_file_chooser_options_new( - allowed_file_types, current_folder_path, current_name, - accept_button_label, select_multiple); + 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, create_folders); } struct _FfsMessageCodec { FlStandardMessageCodec parent_instance; + }; -G_DEFINE_TYPE(FfsMessageCodec, ffs_message_codec, - fl_standard_message_codec_get_type()) +G_DEFINE_TYPE(FfsMessageCodec, ffs_message_codec, fl_standard_message_codec_get_type()) -static gboolean ffs_message_codec_write_ffs_platform_file_chooser_action_type( - FlStandardMessageCodec* codec, GByteArray* buffer, FlValue* value, - GError** error) { +static gboolean ffs_message_codec_write_ffs_platform_file_chooser_action_type(FlStandardMessageCodec* codec, GByteArray* buffer, FlValue* value, GError** error) { uint8_t type = 129; g_byte_array_append(buffer, &type, sizeof(uint8_t)); return fl_standard_message_codec_write_value(codec, buffer, value, error); } -static gboolean ffs_message_codec_write_ffs_platform_type_group( - FlStandardMessageCodec* codec, GByteArray* buffer, - FfsPlatformTypeGroup* value, GError** error) { +static gboolean ffs_message_codec_write_ffs_platform_type_group(FlStandardMessageCodec* codec, GByteArray* buffer, FfsPlatformTypeGroup* value, GError** error) { uint8_t type = 130; g_byte_array_append(buffer, &type, sizeof(uint8_t)); g_autoptr(FlValue) values = ffs_platform_type_group_to_list(value); return fl_standard_message_codec_write_value(codec, buffer, values, error); } -static gboolean ffs_message_codec_write_ffs_platform_file_chooser_options( - FlStandardMessageCodec* codec, GByteArray* buffer, - FfsPlatformFileChooserOptions* value, GError** error) { +static gboolean ffs_message_codec_write_ffs_platform_file_chooser_options(FlStandardMessageCodec* codec, GByteArray* buffer, FfsPlatformFileChooserOptions* value, GError** error) { uint8_t type = 131; g_byte_array_append(buffer, &type, sizeof(uint8_t)); g_autoptr(FlValue) values = ffs_platform_file_chooser_options_to_list(value); return fl_standard_message_codec_write_value(codec, buffer, values, error); } -static gboolean ffs_message_codec_write_value(FlStandardMessageCodec* codec, - GByteArray* buffer, - FlValue* value, GError** error) { +static gboolean ffs_message_codec_write_value(FlStandardMessageCodec* codec, GByteArray* buffer, FlValue* value, GError** error) { if (fl_value_get_type(value) == FL_VALUE_TYPE_CUSTOM) { switch (fl_value_get_custom_type(value)) { case 129: - return ffs_message_codec_write_ffs_platform_file_chooser_action_type( - codec, buffer, - reinterpret_cast( - const_cast(fl_value_get_custom_value(value))), - error); + return ffs_message_codec_write_ffs_platform_file_chooser_action_type(codec, buffer, reinterpret_cast(const_cast(fl_value_get_custom_value(value))), error); case 130: - return ffs_message_codec_write_ffs_platform_type_group( - codec, buffer, - FFS_PLATFORM_TYPE_GROUP(fl_value_get_custom_value_object(value)), - error); + return ffs_message_codec_write_ffs_platform_type_group(codec, buffer, FFS_PLATFORM_TYPE_GROUP(fl_value_get_custom_value_object(value)), error); case 131: - return ffs_message_codec_write_ffs_platform_file_chooser_options( - codec, buffer, - FFS_PLATFORM_FILE_CHOOSER_OPTIONS( - fl_value_get_custom_value_object(value)), - error); + return ffs_message_codec_write_ffs_platform_file_chooser_options(codec, buffer, FFS_PLATFORM_FILE_CHOOSER_OPTIONS(fl_value_get_custom_value_object(value)), error); } } - return FL_STANDARD_MESSAGE_CODEC_CLASS(ffs_message_codec_parent_class) - ->write_value(codec, buffer, value, error); + return FL_STANDARD_MESSAGE_CODEC_CLASS(ffs_message_codec_parent_class)->write_value(codec, buffer, value, error); } -static FlValue* ffs_message_codec_read_ffs_platform_file_chooser_action_type( - FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, - GError** error) { - return fl_value_new_custom( - 129, fl_standard_message_codec_read_value(codec, buffer, offset, error), - (GDestroyNotify)fl_value_unref); +static FlValue* ffs_message_codec_read_ffs_platform_file_chooser_action_type(FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, GError** error) { + return fl_value_new_custom(129, fl_standard_message_codec_read_value(codec, buffer, offset, error), (GDestroyNotify)fl_value_unref); } -static FlValue* ffs_message_codec_read_ffs_platform_type_group( - FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, - GError** error) { - g_autoptr(FlValue) values = - fl_standard_message_codec_read_value(codec, buffer, offset, error); +static FlValue* ffs_message_codec_read_ffs_platform_type_group(FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, GError** error) { + g_autoptr(FlValue) values = fl_standard_message_codec_read_value(codec, buffer, offset, error); if (values == nullptr) { return nullptr; } - g_autoptr(FfsPlatformTypeGroup) value = - ffs_platform_type_group_new_from_list(values); + g_autoptr(FfsPlatformTypeGroup) value = ffs_platform_type_group_new_from_list(values); if (value == nullptr) { - g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, - "Invalid data received for MessageData"); + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, "Invalid data received for MessageData"); return nullptr; } return fl_value_new_custom_object(130, G_OBJECT(value)); } -static FlValue* ffs_message_codec_read_ffs_platform_file_chooser_options( - FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, - GError** error) { - g_autoptr(FlValue) values = - fl_standard_message_codec_read_value(codec, buffer, offset, error); +static FlValue* ffs_message_codec_read_ffs_platform_file_chooser_options(FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, GError** error) { + g_autoptr(FlValue) values = fl_standard_message_codec_read_value(codec, buffer, offset, error); if (values == nullptr) { return nullptr; } - g_autoptr(FfsPlatformFileChooserOptions) value = - ffs_platform_file_chooser_options_new_from_list(values); + g_autoptr(FfsPlatformFileChooserOptions) value = ffs_platform_file_chooser_options_new_from_list(values); if (value == nullptr) { - g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, - "Invalid data received for MessageData"); + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, "Invalid data received for MessageData"); return nullptr; } return fl_value_new_custom_object(131, G_OBJECT(value)); } -static FlValue* ffs_message_codec_read_value_of_type( - FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, int type, - GError** error) { +static FlValue* ffs_message_codec_read_value_of_type(FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, int type, GError** error) { switch (type) { case 129: - return ffs_message_codec_read_ffs_platform_file_chooser_action_type( - codec, buffer, offset, error); + return ffs_message_codec_read_ffs_platform_file_chooser_action_type(codec, buffer, offset, error); case 130: - return ffs_message_codec_read_ffs_platform_type_group(codec, buffer, - offset, error); + return ffs_message_codec_read_ffs_platform_type_group(codec, buffer, offset, error); case 131: - return ffs_message_codec_read_ffs_platform_file_chooser_options( - codec, buffer, offset, error); + return ffs_message_codec_read_ffs_platform_file_chooser_options(codec, buffer, offset, error); default: - return FL_STANDARD_MESSAGE_CODEC_CLASS(ffs_message_codec_parent_class) - ->read_value_of_type(codec, buffer, offset, type, error); + return FL_STANDARD_MESSAGE_CODEC_CLASS(ffs_message_codec_parent_class)->read_value_of_type(codec, buffer, offset, type, error); } } -static void ffs_message_codec_init(FfsMessageCodec* self) {} +static void ffs_message_codec_init(FfsMessageCodec* self) { +} static void ffs_message_codec_class_init(FfsMessageCodecClass* klass) { - FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->write_value = - ffs_message_codec_write_value; - FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->read_value_of_type = - ffs_message_codec_read_value_of_type; + FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->write_value = ffs_message_codec_write_value; + FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->read_value_of_type = ffs_message_codec_read_value_of_type; } static FfsMessageCodec* ffs_message_codec_new() { - FfsMessageCodec* self = - FFS_MESSAGE_CODEC(g_object_new(ffs_message_codec_get_type(), nullptr)); + FfsMessageCodec* self = FFS_MESSAGE_CODEC(g_object_new(ffs_message_codec_get_type(), nullptr)); return self; } @@ -381,52 +333,34 @@ struct _FfsFileSelectorApiShowFileChooserResponse { FlValue* value; }; -G_DEFINE_TYPE(FfsFileSelectorApiShowFileChooserResponse, - ffs_file_selector_api_show_file_chooser_response, G_TYPE_OBJECT) +G_DEFINE_TYPE(FfsFileSelectorApiShowFileChooserResponse, ffs_file_selector_api_show_file_chooser_response, G_TYPE_OBJECT) -static void ffs_file_selector_api_show_file_chooser_response_dispose( - GObject* object) { - FfsFileSelectorApiShowFileChooserResponse* self = - FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(object); +static void ffs_file_selector_api_show_file_chooser_response_dispose(GObject* object) { + FfsFileSelectorApiShowFileChooserResponse* self = FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(object); g_clear_pointer(&self->value, fl_value_unref); - G_OBJECT_CLASS(ffs_file_selector_api_show_file_chooser_response_parent_class) - ->dispose(object); + G_OBJECT_CLASS(ffs_file_selector_api_show_file_chooser_response_parent_class)->dispose(object); } -static void ffs_file_selector_api_show_file_chooser_response_init( - FfsFileSelectorApiShowFileChooserResponse* self) {} +static void ffs_file_selector_api_show_file_chooser_response_init(FfsFileSelectorApiShowFileChooserResponse* self) { +} -static void ffs_file_selector_api_show_file_chooser_response_class_init( - FfsFileSelectorApiShowFileChooserResponseClass* klass) { - G_OBJECT_CLASS(klass)->dispose = - ffs_file_selector_api_show_file_chooser_response_dispose; +static void ffs_file_selector_api_show_file_chooser_response_class_init(FfsFileSelectorApiShowFileChooserResponseClass* klass) { + G_OBJECT_CLASS(klass)->dispose = ffs_file_selector_api_show_file_chooser_response_dispose; } -FfsFileSelectorApiShowFileChooserResponse* -ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value) { - FfsFileSelectorApiShowFileChooserResponse* self = - FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(g_object_new( - ffs_file_selector_api_show_file_chooser_response_get_type(), - nullptr)); +FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value) { + FfsFileSelectorApiShowFileChooserResponse* self = FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(g_object_new(ffs_file_selector_api_show_file_chooser_response_get_type(), nullptr)); self->value = fl_value_new_list(); fl_value_append_take(self->value, fl_value_ref(return_value)); return self; } -FfsFileSelectorApiShowFileChooserResponse* -ffs_file_selector_api_show_file_chooser_response_new_error(const gchar* code, - const gchar* message, - FlValue* details) { - FfsFileSelectorApiShowFileChooserResponse* self = - FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(g_object_new( - ffs_file_selector_api_show_file_chooser_response_get_type(), - nullptr)); +FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_chooser_response_new_error(const gchar* code, const gchar* message, FlValue* details) { + FfsFileSelectorApiShowFileChooserResponse* self = FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(g_object_new(ffs_file_selector_api_show_file_chooser_response_get_type(), nullptr)); self->value = fl_value_new_list(); fl_value_append_take(self->value, fl_value_new_string(code)); - fl_value_append_take(self->value, - fl_value_new_string(message != nullptr ? message : "")); - fl_value_append_take(self->value, details != nullptr ? fl_value_ref(details) - : fl_value_new_null()); + fl_value_append_take(self->value, fl_value_new_string(message != nullptr ? message : "")); + fl_value_append_take(self->value, details != nullptr ? fl_value_ref(details) : fl_value_new_null()); return self; } @@ -449,26 +383,22 @@ static void ffs_file_selector_api_dispose(GObject* object) { G_OBJECT_CLASS(ffs_file_selector_api_parent_class)->dispose(object); } -static void ffs_file_selector_api_init(FfsFileSelectorApi* self) {} +static void ffs_file_selector_api_init(FfsFileSelectorApi* self) { +} static void ffs_file_selector_api_class_init(FfsFileSelectorApiClass* klass) { G_OBJECT_CLASS(klass)->dispose = ffs_file_selector_api_dispose; } -static FfsFileSelectorApi* ffs_file_selector_api_new( - const FfsFileSelectorApiVTable* vtable, gpointer user_data, - GDestroyNotify user_data_free_func) { - FfsFileSelectorApi* self = FFS_FILE_SELECTOR_API( - g_object_new(ffs_file_selector_api_get_type(), nullptr)); +static FfsFileSelectorApi* ffs_file_selector_api_new(const FfsFileSelectorApiVTable* vtable, gpointer user_data, GDestroyNotify user_data_free_func) { + FfsFileSelectorApi* self = FFS_FILE_SELECTOR_API(g_object_new(ffs_file_selector_api_get_type(), nullptr)); self->vtable = vtable; self->user_data = user_data; self->user_data_free_func = user_data_free_func; return self; } -static void ffs_file_selector_api_show_file_chooser_cb( - FlBasicMessageChannel* channel, FlValue* message_, - FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) { +static void ffs_file_selector_api_show_file_chooser_cb(FlBasicMessageChannel* channel, FlValue* message_, FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) { FfsFileSelectorApi* self = FFS_FILE_SELECTOR_API(user_data); if (self->vtable == nullptr || self->vtable->show_file_chooser == nullptr) { @@ -476,64 +406,36 @@ static void ffs_file_selector_api_show_file_chooser_cb( } FlValue* value0 = fl_value_get_list_value(message_, 0); - FfsPlatformFileChooserActionType type = - static_cast( - fl_value_get_int(reinterpret_cast( - const_cast(fl_value_get_custom_value(value0))))); + FfsPlatformFileChooserActionType type = static_cast(fl_value_get_int(reinterpret_cast(const_cast(fl_value_get_custom_value(value0))))); FlValue* value1 = fl_value_get_list_value(message_, 1); - FfsPlatformFileChooserOptions* options = FFS_PLATFORM_FILE_CHOOSER_OPTIONS( - fl_value_get_custom_value_object(value1)); - g_autoptr(FfsFileSelectorApiShowFileChooserResponse) response = - self->vtable->show_file_chooser(type, options, self->user_data); + FfsPlatformFileChooserOptions* options = FFS_PLATFORM_FILE_CHOOSER_OPTIONS(fl_value_get_custom_value_object(value1)); + g_autoptr(FfsFileSelectorApiShowFileChooserResponse) response = self->vtable->show_file_chooser(type, options, self->user_data); if (response == nullptr) { - g_warning("No response returned to %s.%s", "FileSelectorApi", - "showFileChooser"); + g_warning("No response returned to %s.%s", "FileSelectorApi", "showFileChooser"); return; } g_autoptr(GError) error = NULL; - if (!fl_basic_message_channel_respond(channel, response_handle, - response->value, &error)) { - g_warning("Failed to send response to %s.%s: %s", "FileSelectorApi", - "showFileChooser", error->message); + if (!fl_basic_message_channel_respond(channel, response_handle, response->value, &error)) { + g_warning("Failed to send response to %s.%s: %s", "FileSelectorApi", "showFileChooser", error->message); } } -void ffs_file_selector_api_set_method_handlers( - FlBinaryMessenger* messenger, const gchar* suffix, - const FfsFileSelectorApiVTable* vtable, gpointer user_data, - GDestroyNotify user_data_free_func) { - g_autofree gchar* dot_suffix = - suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); - g_autoptr(FfsFileSelectorApi) api_data = - ffs_file_selector_api_new(vtable, user_data, user_data_free_func); +void ffs_file_selector_api_set_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix, const FfsFileSelectorApiVTable* vtable, gpointer user_data, GDestroyNotify user_data_free_func) { + g_autofree gchar* dot_suffix = suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); + g_autoptr(FfsFileSelectorApi) api_data = ffs_file_selector_api_new(vtable, user_data, user_data_free_func); g_autoptr(FfsMessageCodec) codec = ffs_message_codec_new(); - g_autofree gchar* show_file_chooser_channel_name = g_strdup_printf( - "dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser%" - "s", - dot_suffix); - g_autoptr(FlBasicMessageChannel) show_file_chooser_channel = - fl_basic_message_channel_new(messenger, show_file_chooser_channel_name, - FL_MESSAGE_CODEC(codec)); - fl_basic_message_channel_set_message_handler( - show_file_chooser_channel, ffs_file_selector_api_show_file_chooser_cb, - g_object_ref(api_data), g_object_unref); -} - -void ffs_file_selector_api_clear_method_handlers(FlBinaryMessenger* messenger, - const gchar* suffix) { - g_autofree gchar* dot_suffix = - suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); + g_autofree gchar* show_file_chooser_channel_name = g_strdup_printf("dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser%s", dot_suffix); + g_autoptr(FlBasicMessageChannel) show_file_chooser_channel = fl_basic_message_channel_new(messenger, show_file_chooser_channel_name, FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler(show_file_chooser_channel, ffs_file_selector_api_show_file_chooser_cb, g_object_ref(api_data), g_object_unref); +} + +void ffs_file_selector_api_clear_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix) { + g_autofree gchar* dot_suffix = suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); g_autoptr(FfsMessageCodec) codec = ffs_message_codec_new(); - g_autofree gchar* show_file_chooser_channel_name = g_strdup_printf( - "dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser%" - "s", - dot_suffix); - g_autoptr(FlBasicMessageChannel) show_file_chooser_channel = - fl_basic_message_channel_new(messenger, show_file_chooser_channel_name, - FL_MESSAGE_CODEC(codec)); - fl_basic_message_channel_set_message_handler(show_file_chooser_channel, - nullptr, nullptr, nullptr); + g_autofree gchar* show_file_chooser_channel_name = g_strdup_printf("dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser%s", dot_suffix); + g_autoptr(FlBasicMessageChannel) show_file_chooser_channel = fl_basic_message_channel_new(messenger, show_file_chooser_channel_name, FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler(show_file_chooser_channel, nullptr, nullptr, nullptr); } 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..c0c9001e0d2 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_ @@ -31,8 +31,7 @@ typedef enum { * A Pigeon representation of the Linux portion of an `XTypeGroup`. */ -G_DECLARE_FINAL_TYPE(FfsPlatformTypeGroup, ffs_platform_type_group, FFS, - PLATFORM_TYPE_GROUP, GObject) +G_DECLARE_FINAL_TYPE(FfsPlatformTypeGroup, ffs_platform_type_group, FFS, PLATFORM_TYPE_GROUP, GObject) /** * ffs_platform_type_group_new: @@ -44,9 +43,7 @@ G_DECLARE_FINAL_TYPE(FfsPlatformTypeGroup, ffs_platform_type_group, FFS, * * Returns: a new #FfsPlatformTypeGroup */ -FfsPlatformTypeGroup* ffs_platform_type_group_new(const gchar* label, - FlValue* extensions, - FlValue* mime_types); +FfsPlatformTypeGroup* ffs_platform_type_group_new(const gchar* label, FlValue* extensions, FlValue* mime_types); /** * ffs_platform_type_group_get_label @@ -86,9 +83,7 @@ FlValue* ffs_platform_type_group_get_mime_types(FfsPlatformTypeGroup* object); * These correspond to gtk_file_chooser_set_* options. */ -G_DECLARE_FINAL_TYPE(FfsPlatformFileChooserOptions, - ffs_platform_file_chooser_options, FFS, - PLATFORM_FILE_CHOOSER_OPTIONS, GObject) +G_DECLARE_FINAL_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_options, FFS, PLATFORM_FILE_CHOOSER_OPTIONS, GObject) /** * ffs_platform_file_chooser_options_new: @@ -97,15 +92,13 @@ 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. * * Returns: a new #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); +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* create_folders); /** * ffs_platform_file_chooser_options_get_allowed_file_types @@ -115,8 +108,7 @@ FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new( * * Returns: the field value. */ -FlValue* ffs_platform_file_chooser_options_get_allowed_file_types( - FfsPlatformFileChooserOptions* object); +FlValue* ffs_platform_file_chooser_options_get_allowed_file_types(FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_current_folder_path @@ -126,8 +118,7 @@ FlValue* ffs_platform_file_chooser_options_get_allowed_file_types( * * Returns: the field value. */ -const gchar* ffs_platform_file_chooser_options_get_current_folder_path( - FfsPlatformFileChooserOptions* object); +const gchar* ffs_platform_file_chooser_options_get_current_folder_path(FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_current_name @@ -137,8 +128,7 @@ const gchar* ffs_platform_file_chooser_options_get_current_folder_path( * * Returns: the field value. */ -const gchar* ffs_platform_file_chooser_options_get_current_name( - FfsPlatformFileChooserOptions* object); +const gchar* ffs_platform_file_chooser_options_get_current_name(FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_accept_button_label @@ -148,8 +138,7 @@ const gchar* ffs_platform_file_chooser_options_get_current_name( * * Returns: the field value. */ -const gchar* ffs_platform_file_chooser_options_get_accept_button_label( - FfsPlatformFileChooserOptions* object); +const gchar* ffs_platform_file_chooser_options_get_accept_button_label(FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_select_multiple @@ -161,18 +150,25 @@ const gchar* ffs_platform_file_chooser_options_get_accept_button_label( * * Returns: the field value. */ -gboolean* ffs_platform_file_chooser_options_get_select_multiple( - FfsPlatformFileChooserOptions* object); +gboolean* ffs_platform_file_chooser_options_get_select_multiple(FfsPlatformFileChooserOptions* object); -G_DECLARE_FINAL_TYPE(FfsMessageCodec, ffs_message_codec, FFS, MESSAGE_CODEC, - FlStandardMessageCodec) +/** + * 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) -G_DECLARE_FINAL_TYPE(FfsFileSelectorApi, ffs_file_selector_api, FFS, - FILE_SELECTOR_API, GObject) +G_DECLARE_FINAL_TYPE(FfsFileSelectorApi, ffs_file_selector_api, FFS, FILE_SELECTOR_API, GObject) -G_DECLARE_FINAL_TYPE(FfsFileSelectorApiShowFileChooserResponse, - ffs_file_selector_api_show_file_chooser_response, FFS, - FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE, GObject) +G_DECLARE_FINAL_TYPE(FfsFileSelectorApiShowFileChooserResponse, ffs_file_selector_api_show_file_chooser_response, FFS, FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE, GObject) /** * ffs_file_selector_api_show_file_chooser_response_new: @@ -181,8 +177,7 @@ G_DECLARE_FINAL_TYPE(FfsFileSelectorApiShowFileChooserResponse, * * Returns: a new #FfsFileSelectorApiShowFileChooserResponse */ -FfsFileSelectorApiShowFileChooserResponse* -ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value); +FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value); /** * ffs_file_selector_api_show_file_chooser_response_new_error: @@ -194,21 +189,15 @@ ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value); * * Returns: a new #FfsFileSelectorApiShowFileChooserResponse */ -FfsFileSelectorApiShowFileChooserResponse* -ffs_file_selector_api_show_file_chooser_response_new_error(const gchar* code, - const gchar* message, - FlValue* details); +FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_chooser_response_new_error(const gchar* code, const gchar* message, FlValue* details); /** * FfsFileSelectorApiVTable: * - * Table of functions exposed by FileSelectorApi to be implemented by the API - * provider. + * Table of functions exposed by FileSelectorApi to be implemented by the API provider. */ typedef struct { - FfsFileSelectorApiShowFileChooserResponse* (*show_file_chooser)( - FfsPlatformFileChooserActionType type, - FfsPlatformFileChooserOptions* options, gpointer user_data); + FfsFileSelectorApiShowFileChooserResponse* (*show_file_chooser)(FfsPlatformFileChooserActionType type, FfsPlatformFileChooserOptions* options, gpointer user_data); } FfsFileSelectorApiVTable; /** @@ -218,15 +207,11 @@ typedef struct { * @suffix: (allow-none): a suffix to add to the API or %NULL for none. * @vtable: implementations of the methods in this API. * @user_data: (closure): user data to pass to the functions in @vtable. - * @user_data_free_func: (allow-none): a function which gets called to free - * @user_data, or %NULL. + * @user_data_free_func: (allow-none): a function which gets called to free @user_data, or %NULL. * * Connects the method handlers in the FileSelectorApi API. */ -void ffs_file_selector_api_set_method_handlers( - FlBinaryMessenger* messenger, const gchar* suffix, - const FfsFileSelectorApiVTable* vtable, gpointer user_data, - GDestroyNotify user_data_free_func); +void ffs_file_selector_api_set_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix, const FfsFileSelectorApiVTable* vtable, gpointer user_data, GDestroyNotify user_data_free_func); /** * ffs_file_selector_api_clear_method_handlers: @@ -236,8 +221,7 @@ void ffs_file_selector_api_set_method_handlers( * * Clears the method handlers in the FileSelectorApi API. */ -void ffs_file_selector_api_clear_method_handlers(FlBinaryMessenger* messenger, - const gchar* suffix); +void ffs_file_selector_api_clear_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix); G_END_DECLS 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..efa9a2469ce 100644 --- a/packages/file_selector/file_selector_linux/pubspec.yaml +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -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_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/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..9b56de32276 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 @@ -96,14 +96,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 +126,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..deba47775ab 100644 --- a/packages/file_selector/file_selector_macos/pubspec.yaml +++ b/packages/file_selector/file_selector_macos/pubspec.yaml @@ -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..1833026bc29 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, true); + 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, true); + 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_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..5d34debaa87 100644 --- a/packages/file_selector/file_selector_web/pubspec.yaml +++ b/packages/file_selector/file_selector_web/pubspec.yaml @@ -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/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..a4e9fbd891c 100644 --- a/packages/file_selector/file_selector_windows/pubspec.yaml +++ b/packages/file_selector/file_selector_windows/pubspec.yaml @@ -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} From 543c641066abee33f758eecd50ab0cf671f9e35a Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Fri, 5 Sep 2025 21:39:22 -0300 Subject: [PATCH 02/14] Bump version --- packages/file_selector/file_selector/CHANGELOG.md | 3 ++- packages/file_selector/file_selector/pubspec.yaml | 2 +- packages/file_selector/file_selector_android/CHANGELOG.md | 4 ++++ packages/file_selector/file_selector_android/pubspec.yaml | 2 +- packages/file_selector/file_selector_ios/CHANGELOG.md | 3 ++- packages/file_selector/file_selector_ios/pubspec.yaml | 2 +- packages/file_selector/file_selector_linux/CHANGELOG.md | 3 ++- packages/file_selector/file_selector_linux/pubspec.yaml | 2 +- packages/file_selector/file_selector_macos/CHANGELOG.md | 3 ++- packages/file_selector/file_selector_macos/pubspec.yaml | 2 +- packages/file_selector/file_selector_web/CHANGELOG.md | 3 ++- packages/file_selector/file_selector_web/pubspec.yaml | 2 +- packages/file_selector/file_selector_windows/CHANGELOG.md | 3 ++- packages/file_selector/file_selector_windows/pubspec.yaml | 2 +- 14 files changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index b9ed354daa1..e33a119908f 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` 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. * Updates README to indicate that Andoid SDK <21 is no longer supported. diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index 806e60b11c3..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 diff --git a/packages/file_selector/file_selector_android/CHANGELOG.md b/packages/file_selector/file_selector_android/CHANGELOG.md index 1401245d3c0..2995528f0e6 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 `canCreateDirectories` parameter to `FileDialogOptions` to control the visibility of the New Folder button in file dialogs on supported platforms. + ## 0.5.1+16 * Bumps com.android.tools.build:gradle to 8.12.1. diff --git a/packages/file_selector/file_selector_android/pubspec.yaml b/packages/file_selector/file_selector_android/pubspec.yaml index 5da04fbd8c0..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 diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index b34eedbd891..feb3d0bba6c 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 0.5.4 +* 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. ## 0.5.3+2 diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index e3d711e277d..a9cefd452e5 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_ios description: iOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.3+2 +version: 0.5.4 environment: sdk: ^3.7.0 diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index bcff55dae5a..a528639d2e0 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 `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. ## 0.9.3+2 diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml index efa9a2469ce..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 diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index 0404c64bbca..f7394fefaba 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 `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. ## 0.9.4+4 diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index deba47775ab..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 diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 5323271b741..9b00a9d72d8 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 `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. ## 0.9.4+2 diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml index 5d34debaa87..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 diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index 8eaeeb2292f..7d697b34bb4 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 `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. ## 0.9.3+4 diff --git a/packages/file_selector/file_selector_windows/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml index a4e9fbd891c..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 From d0915c9d1e85c4b37a8b4f313c4dfde037d2eded Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Fri, 5 Sep 2025 21:53:12 -0300 Subject: [PATCH 03/14] Apply format --- .../lib/src/messages.g.dart | 63 ++-- .../file_selector_linux/linux/messages.g.cc | 340 ++++++++++++------ .../file_selector_linux/linux/messages.g.h | 70 ++-- 3 files changed, 314 insertions(+), 159 deletions(-) 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 86315d1e3ab..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 @@ -19,11 +19,7 @@ PlatformException _createConnectionError(String channelName) { } /// A Pigeon representation of the GTK_FILE_CHOOSER_ACTION_* options. -enum PlatformFileChooserActionType { - open, - chooseDirectory, - save, -} +enum PlatformFileChooserActionType { open, chooseDirectory, save } /// A Pigeon representation of the Linux portion of an `XTypeGroup`. class PlatformTypeGroup { @@ -40,11 +36,7 @@ class PlatformTypeGroup { List mimeTypes; Object encode() { - return [ - label, - extensions, - mimeTypes, - ]; + return [label, extensions, mimeTypes]; } static PlatformTypeGroup decode(Object result) { @@ -102,7 +94,8 @@ class PlatformFileChooserOptions { static PlatformFileChooserOptions decode(Object result) { result as List; return PlatformFileChooserOptions( - allowedFileTypes: (result[0] as List?)?.cast(), + allowedFileTypes: + (result[0] as List?)?.cast(), currentFolderPath: result[1] as String?, currentName: result[2] as String?, acceptButtonLabel: result[3] as String?, @@ -112,7 +105,6 @@ class PlatformFileChooserOptions { } } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -120,13 +112,13 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is PlatformFileChooserActionType) { + } else if (value is PlatformFileChooserActionType) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is PlatformTypeGroup) { + } else if (value is PlatformTypeGroup) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is PlatformFileChooserOptions) { + } else if (value is PlatformFileChooserOptions) { buffer.putUint8(131); writeValue(buffer, value.encode()); } else { @@ -137,12 +129,14 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformFileChooserActionType.values[value]; - case 130: + return value == null + ? null + : PlatformFileChooserActionType.values[value]; + case 130: return PlatformTypeGroup.decode(readValue(buffer)!); - case 131: + case 131: return PlatformFileChooserOptions.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -154,9 +148,12 @@ class FileSelectorApi { /// Constructor for [FileSelectorApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - FileSelectorApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) - : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + FileSelectorApi({ + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -167,15 +164,21 @@ class FileSelectorApi { /// list of selected paths. /// /// An empty list corresponds to a cancelled selection. - Future> showFileChooser(PlatformFileChooserActionType type, PlatformFileChooserOptions options) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); + Future> showFileChooser( + PlatformFileChooserActionType type, + PlatformFileChooserOptions options, + ) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([type, options]) as List?; + await pigeonVar_channel.send([type, options]) + as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { 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 ae98b994d5f..c24ff23184c 100644 --- a/packages/file_selector/file_selector_linux/linux/messages.g.cc +++ b/packages/file_selector/file_selector_linux/linux/messages.g.cc @@ -24,15 +24,18 @@ static void ffs_platform_type_group_dispose(GObject* object) { G_OBJECT_CLASS(ffs_platform_type_group_parent_class)->dispose(object); } -static void ffs_platform_type_group_init(FfsPlatformTypeGroup* self) { -} +static void ffs_platform_type_group_init(FfsPlatformTypeGroup* self) {} -static void ffs_platform_type_group_class_init(FfsPlatformTypeGroupClass* klass) { +static void ffs_platform_type_group_class_init( + FfsPlatformTypeGroupClass* klass) { G_OBJECT_CLASS(klass)->dispose = ffs_platform_type_group_dispose; } -FfsPlatformTypeGroup* ffs_platform_type_group_new(const gchar* label, FlValue* extensions, FlValue* mime_types) { - FfsPlatformTypeGroup* self = FFS_PLATFORM_TYPE_GROUP(g_object_new(ffs_platform_type_group_get_type(), nullptr)); +FfsPlatformTypeGroup* ffs_platform_type_group_new(const gchar* label, + FlValue* extensions, + FlValue* mime_types) { + FfsPlatformTypeGroup* self = FFS_PLATFORM_TYPE_GROUP( + g_object_new(ffs_platform_type_group_get_type(), nullptr)); self->label = g_strdup(label); self->extensions = fl_value_ref(extensions); self->mime_types = fl_value_ref(mime_types); @@ -62,7 +65,8 @@ static FlValue* ffs_platform_type_group_to_list(FfsPlatformTypeGroup* self) { return values; } -static FfsPlatformTypeGroup* ffs_platform_type_group_new_from_list(FlValue* values) { +static FfsPlatformTypeGroup* ffs_platform_type_group_new_from_list( + FlValue* values) { FlValue* value0 = fl_value_get_list_value(values, 0); const gchar* label = fl_value_get_string(value0); FlValue* value1 = fl_value_get_list_value(values, 1); @@ -83,111 +87,135 @@ struct _FfsPlatformFileChooserOptions { gboolean* create_folders; }; -G_DEFINE_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_options, G_TYPE_OBJECT) +G_DEFINE_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_options, + G_TYPE_OBJECT) static void ffs_platform_file_chooser_options_dispose(GObject* object) { - FfsPlatformFileChooserOptions* self = FFS_PLATFORM_FILE_CHOOSER_OPTIONS(object); + FfsPlatformFileChooserOptions* self = + FFS_PLATFORM_FILE_CHOOSER_OPTIONS(object); g_clear_pointer(&self->allowed_file_types, fl_value_unref); g_clear_pointer(&self->current_folder_path, g_free); 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); + G_OBJECT_CLASS(ffs_platform_file_chooser_options_parent_class) + ->dispose(object); } -static void ffs_platform_file_chooser_options_init(FfsPlatformFileChooserOptions* self) { -} +static void ffs_platform_file_chooser_options_init( + FfsPlatformFileChooserOptions* self) {} -static void ffs_platform_file_chooser_options_class_init(FfsPlatformFileChooserOptionsClass* klass) { +static void ffs_platform_file_chooser_options_class_init( + FfsPlatformFileChooserOptionsClass* klass) { G_OBJECT_CLASS(klass)->dispose = ffs_platform_file_chooser_options_dispose; } -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* create_folders) { - FfsPlatformFileChooserOptions* self = FFS_PLATFORM_FILE_CHOOSER_OPTIONS(g_object_new(ffs_platform_file_chooser_options_get_type(), nullptr)); +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* 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) { self->allowed_file_types = fl_value_ref(allowed_file_types); - } - else { + } else { self->allowed_file_types = nullptr; } if (current_folder_path != nullptr) { self->current_folder_path = g_strdup(current_folder_path); - } - else { + } else { self->current_folder_path = nullptr; } if (current_name != nullptr) { self->current_name = g_strdup(current_name); - } - else { + } else { self->current_name = nullptr; } if (accept_button_label != nullptr) { self->accept_button_label = g_strdup(accept_button_label); - } - else { + } else { self->accept_button_label = nullptr; } if (select_multiple != nullptr) { self->select_multiple = static_cast(malloc(sizeof(gboolean))); *self->select_multiple = *select_multiple; - } - else { + } else { self->select_multiple = nullptr; } if (create_folders != nullptr) { self->create_folders = static_cast(malloc(sizeof(gboolean))); *self->create_folders = *create_folders; - } - else { + } else { self->create_folders = nullptr; } return self; } -FlValue* ffs_platform_file_chooser_options_get_allowed_file_types(FfsPlatformFileChooserOptions* self) { +FlValue* ffs_platform_file_chooser_options_get_allowed_file_types( + FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->allowed_file_types; } -const gchar* ffs_platform_file_chooser_options_get_current_folder_path(FfsPlatformFileChooserOptions* self) { +const gchar* ffs_platform_file_chooser_options_get_current_folder_path( + FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->current_folder_path; } -const gchar* ffs_platform_file_chooser_options_get_current_name(FfsPlatformFileChooserOptions* self) { +const gchar* ffs_platform_file_chooser_options_get_current_name( + FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->current_name; } -const gchar* ffs_platform_file_chooser_options_get_accept_button_label(FfsPlatformFileChooserOptions* self) { +const gchar* ffs_platform_file_chooser_options_get_accept_button_label( + FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->accept_button_label; } -gboolean* ffs_platform_file_chooser_options_get_select_multiple(FfsPlatformFileChooserOptions* self) { +gboolean* ffs_platform_file_chooser_options_get_select_multiple( + FfsPlatformFileChooserOptions* self) { g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr); return self->select_multiple; } -gboolean* ffs_platform_file_chooser_options_get_create_folders(FfsPlatformFileChooserOptions* self) { +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) { +static FlValue* ffs_platform_file_chooser_options_to_list( + FfsPlatformFileChooserOptions* self) { FlValue* values = fl_value_new_list(); - fl_value_append_take(values, self->allowed_file_types != nullptr ? fl_value_ref(self->allowed_file_types) : fl_value_new_null()); - fl_value_append_take(values, self->current_folder_path != nullptr ? fl_value_new_string(self->current_folder_path) : fl_value_new_null()); - fl_value_append_take(values, self->current_name != nullptr ? fl_value_new_string(self->current_name) : fl_value_new_null()); - fl_value_append_take(values, self->accept_button_label != nullptr ? fl_value_new_string(self->accept_button_label) : fl_value_new_null()); - 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()); + fl_value_append_take(values, self->allowed_file_types != nullptr + ? fl_value_ref(self->allowed_file_types) + : fl_value_new_null()); + fl_value_append_take(values, + self->current_folder_path != nullptr + ? fl_value_new_string(self->current_folder_path) + : fl_value_new_null()); + fl_value_append_take(values, self->current_name != nullptr + ? fl_value_new_string(self->current_name) + : fl_value_new_null()); + fl_value_append_take(values, + self->accept_button_label != nullptr + ? fl_value_new_string(self->accept_button_label) + : fl_value_new_null()); + 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; } -static FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new_from_list(FlValue* values) { +static FfsPlatformFileChooserOptions* +ffs_platform_file_chooser_options_new_from_list(FlValue* values) { FlValue* value0 = fl_value_get_list_value(values, 0); FlValue* allowed_file_types = nullptr; if (fl_value_get_type(value0) != FL_VALUE_TYPE_NULL) { @@ -222,108 +250,152 @@ static FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new_from 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, create_folders); + return ffs_platform_file_chooser_options_new( + allowed_file_types, current_folder_path, current_name, + accept_button_label, select_multiple, create_folders); } struct _FfsMessageCodec { FlStandardMessageCodec parent_instance; - }; -G_DEFINE_TYPE(FfsMessageCodec, ffs_message_codec, fl_standard_message_codec_get_type()) +G_DEFINE_TYPE(FfsMessageCodec, ffs_message_codec, + fl_standard_message_codec_get_type()) -static gboolean ffs_message_codec_write_ffs_platform_file_chooser_action_type(FlStandardMessageCodec* codec, GByteArray* buffer, FlValue* value, GError** error) { +static gboolean ffs_message_codec_write_ffs_platform_file_chooser_action_type( + FlStandardMessageCodec* codec, GByteArray* buffer, FlValue* value, + GError** error) { uint8_t type = 129; g_byte_array_append(buffer, &type, sizeof(uint8_t)); return fl_standard_message_codec_write_value(codec, buffer, value, error); } -static gboolean ffs_message_codec_write_ffs_platform_type_group(FlStandardMessageCodec* codec, GByteArray* buffer, FfsPlatformTypeGroup* value, GError** error) { +static gboolean ffs_message_codec_write_ffs_platform_type_group( + FlStandardMessageCodec* codec, GByteArray* buffer, + FfsPlatformTypeGroup* value, GError** error) { uint8_t type = 130; g_byte_array_append(buffer, &type, sizeof(uint8_t)); g_autoptr(FlValue) values = ffs_platform_type_group_to_list(value); return fl_standard_message_codec_write_value(codec, buffer, values, error); } -static gboolean ffs_message_codec_write_ffs_platform_file_chooser_options(FlStandardMessageCodec* codec, GByteArray* buffer, FfsPlatformFileChooserOptions* value, GError** error) { +static gboolean ffs_message_codec_write_ffs_platform_file_chooser_options( + FlStandardMessageCodec* codec, GByteArray* buffer, + FfsPlatformFileChooserOptions* value, GError** error) { uint8_t type = 131; g_byte_array_append(buffer, &type, sizeof(uint8_t)); g_autoptr(FlValue) values = ffs_platform_file_chooser_options_to_list(value); return fl_standard_message_codec_write_value(codec, buffer, values, error); } -static gboolean ffs_message_codec_write_value(FlStandardMessageCodec* codec, GByteArray* buffer, FlValue* value, GError** error) { +static gboolean ffs_message_codec_write_value(FlStandardMessageCodec* codec, + GByteArray* buffer, + FlValue* value, GError** error) { if (fl_value_get_type(value) == FL_VALUE_TYPE_CUSTOM) { switch (fl_value_get_custom_type(value)) { case 129: - return ffs_message_codec_write_ffs_platform_file_chooser_action_type(codec, buffer, reinterpret_cast(const_cast(fl_value_get_custom_value(value))), error); + return ffs_message_codec_write_ffs_platform_file_chooser_action_type( + codec, buffer, + reinterpret_cast( + const_cast(fl_value_get_custom_value(value))), + error); case 130: - return ffs_message_codec_write_ffs_platform_type_group(codec, buffer, FFS_PLATFORM_TYPE_GROUP(fl_value_get_custom_value_object(value)), error); + return ffs_message_codec_write_ffs_platform_type_group( + codec, buffer, + FFS_PLATFORM_TYPE_GROUP(fl_value_get_custom_value_object(value)), + error); case 131: - return ffs_message_codec_write_ffs_platform_file_chooser_options(codec, buffer, FFS_PLATFORM_FILE_CHOOSER_OPTIONS(fl_value_get_custom_value_object(value)), error); + return ffs_message_codec_write_ffs_platform_file_chooser_options( + codec, buffer, + FFS_PLATFORM_FILE_CHOOSER_OPTIONS( + fl_value_get_custom_value_object(value)), + error); } } - return FL_STANDARD_MESSAGE_CODEC_CLASS(ffs_message_codec_parent_class)->write_value(codec, buffer, value, error); + return FL_STANDARD_MESSAGE_CODEC_CLASS(ffs_message_codec_parent_class) + ->write_value(codec, buffer, value, error); } -static FlValue* ffs_message_codec_read_ffs_platform_file_chooser_action_type(FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, GError** error) { - return fl_value_new_custom(129, fl_standard_message_codec_read_value(codec, buffer, offset, error), (GDestroyNotify)fl_value_unref); +static FlValue* ffs_message_codec_read_ffs_platform_file_chooser_action_type( + FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, + GError** error) { + return fl_value_new_custom( + 129, fl_standard_message_codec_read_value(codec, buffer, offset, error), + (GDestroyNotify)fl_value_unref); } -static FlValue* ffs_message_codec_read_ffs_platform_type_group(FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, GError** error) { - g_autoptr(FlValue) values = fl_standard_message_codec_read_value(codec, buffer, offset, error); +static FlValue* ffs_message_codec_read_ffs_platform_type_group( + FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, + GError** error) { + g_autoptr(FlValue) values = + fl_standard_message_codec_read_value(codec, buffer, offset, error); if (values == nullptr) { return nullptr; } - g_autoptr(FfsPlatformTypeGroup) value = ffs_platform_type_group_new_from_list(values); + g_autoptr(FfsPlatformTypeGroup) value = + ffs_platform_type_group_new_from_list(values); if (value == nullptr) { - g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, "Invalid data received for MessageData"); + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Invalid data received for MessageData"); return nullptr; } return fl_value_new_custom_object(130, G_OBJECT(value)); } -static FlValue* ffs_message_codec_read_ffs_platform_file_chooser_options(FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, GError** error) { - g_autoptr(FlValue) values = fl_standard_message_codec_read_value(codec, buffer, offset, error); +static FlValue* ffs_message_codec_read_ffs_platform_file_chooser_options( + FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, + GError** error) { + g_autoptr(FlValue) values = + fl_standard_message_codec_read_value(codec, buffer, offset, error); if (values == nullptr) { return nullptr; } - g_autoptr(FfsPlatformFileChooserOptions) value = ffs_platform_file_chooser_options_new_from_list(values); + g_autoptr(FfsPlatformFileChooserOptions) value = + ffs_platform_file_chooser_options_new_from_list(values); if (value == nullptr) { - g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, "Invalid data received for MessageData"); + g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED, + "Invalid data received for MessageData"); return nullptr; } return fl_value_new_custom_object(131, G_OBJECT(value)); } -static FlValue* ffs_message_codec_read_value_of_type(FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, int type, GError** error) { +static FlValue* ffs_message_codec_read_value_of_type( + FlStandardMessageCodec* codec, GBytes* buffer, size_t* offset, int type, + GError** error) { switch (type) { case 129: - return ffs_message_codec_read_ffs_platform_file_chooser_action_type(codec, buffer, offset, error); + return ffs_message_codec_read_ffs_platform_file_chooser_action_type( + codec, buffer, offset, error); case 130: - return ffs_message_codec_read_ffs_platform_type_group(codec, buffer, offset, error); + return ffs_message_codec_read_ffs_platform_type_group(codec, buffer, + offset, error); case 131: - return ffs_message_codec_read_ffs_platform_file_chooser_options(codec, buffer, offset, error); + return ffs_message_codec_read_ffs_platform_file_chooser_options( + codec, buffer, offset, error); default: - return FL_STANDARD_MESSAGE_CODEC_CLASS(ffs_message_codec_parent_class)->read_value_of_type(codec, buffer, offset, type, error); + return FL_STANDARD_MESSAGE_CODEC_CLASS(ffs_message_codec_parent_class) + ->read_value_of_type(codec, buffer, offset, type, error); } } -static void ffs_message_codec_init(FfsMessageCodec* self) { -} +static void ffs_message_codec_init(FfsMessageCodec* self) {} static void ffs_message_codec_class_init(FfsMessageCodecClass* klass) { - FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->write_value = ffs_message_codec_write_value; - FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->read_value_of_type = ffs_message_codec_read_value_of_type; + FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->write_value = + ffs_message_codec_write_value; + FL_STANDARD_MESSAGE_CODEC_CLASS(klass)->read_value_of_type = + ffs_message_codec_read_value_of_type; } static FfsMessageCodec* ffs_message_codec_new() { - FfsMessageCodec* self = FFS_MESSAGE_CODEC(g_object_new(ffs_message_codec_get_type(), nullptr)); + FfsMessageCodec* self = + FFS_MESSAGE_CODEC(g_object_new(ffs_message_codec_get_type(), nullptr)); return self; } @@ -333,34 +405,52 @@ struct _FfsFileSelectorApiShowFileChooserResponse { FlValue* value; }; -G_DEFINE_TYPE(FfsFileSelectorApiShowFileChooserResponse, ffs_file_selector_api_show_file_chooser_response, G_TYPE_OBJECT) +G_DEFINE_TYPE(FfsFileSelectorApiShowFileChooserResponse, + ffs_file_selector_api_show_file_chooser_response, G_TYPE_OBJECT) -static void ffs_file_selector_api_show_file_chooser_response_dispose(GObject* object) { - FfsFileSelectorApiShowFileChooserResponse* self = FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(object); +static void ffs_file_selector_api_show_file_chooser_response_dispose( + GObject* object) { + FfsFileSelectorApiShowFileChooserResponse* self = + FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(object); g_clear_pointer(&self->value, fl_value_unref); - G_OBJECT_CLASS(ffs_file_selector_api_show_file_chooser_response_parent_class)->dispose(object); + G_OBJECT_CLASS(ffs_file_selector_api_show_file_chooser_response_parent_class) + ->dispose(object); } -static void ffs_file_selector_api_show_file_chooser_response_init(FfsFileSelectorApiShowFileChooserResponse* self) { -} +static void ffs_file_selector_api_show_file_chooser_response_init( + FfsFileSelectorApiShowFileChooserResponse* self) {} -static void ffs_file_selector_api_show_file_chooser_response_class_init(FfsFileSelectorApiShowFileChooserResponseClass* klass) { - G_OBJECT_CLASS(klass)->dispose = ffs_file_selector_api_show_file_chooser_response_dispose; +static void ffs_file_selector_api_show_file_chooser_response_class_init( + FfsFileSelectorApiShowFileChooserResponseClass* klass) { + G_OBJECT_CLASS(klass)->dispose = + ffs_file_selector_api_show_file_chooser_response_dispose; } -FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value) { - FfsFileSelectorApiShowFileChooserResponse* self = FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(g_object_new(ffs_file_selector_api_show_file_chooser_response_get_type(), nullptr)); +FfsFileSelectorApiShowFileChooserResponse* +ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value) { + FfsFileSelectorApiShowFileChooserResponse* self = + FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(g_object_new( + ffs_file_selector_api_show_file_chooser_response_get_type(), + nullptr)); self->value = fl_value_new_list(); fl_value_append_take(self->value, fl_value_ref(return_value)); return self; } -FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_chooser_response_new_error(const gchar* code, const gchar* message, FlValue* details) { - FfsFileSelectorApiShowFileChooserResponse* self = FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(g_object_new(ffs_file_selector_api_show_file_chooser_response_get_type(), nullptr)); +FfsFileSelectorApiShowFileChooserResponse* +ffs_file_selector_api_show_file_chooser_response_new_error(const gchar* code, + const gchar* message, + FlValue* details) { + FfsFileSelectorApiShowFileChooserResponse* self = + FFS_FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE(g_object_new( + ffs_file_selector_api_show_file_chooser_response_get_type(), + nullptr)); self->value = fl_value_new_list(); fl_value_append_take(self->value, fl_value_new_string(code)); - fl_value_append_take(self->value, fl_value_new_string(message != nullptr ? message : "")); - fl_value_append_take(self->value, details != nullptr ? fl_value_ref(details) : fl_value_new_null()); + fl_value_append_take(self->value, + fl_value_new_string(message != nullptr ? message : "")); + fl_value_append_take(self->value, details != nullptr ? fl_value_ref(details) + : fl_value_new_null()); return self; } @@ -383,22 +473,26 @@ static void ffs_file_selector_api_dispose(GObject* object) { G_OBJECT_CLASS(ffs_file_selector_api_parent_class)->dispose(object); } -static void ffs_file_selector_api_init(FfsFileSelectorApi* self) { -} +static void ffs_file_selector_api_init(FfsFileSelectorApi* self) {} static void ffs_file_selector_api_class_init(FfsFileSelectorApiClass* klass) { G_OBJECT_CLASS(klass)->dispose = ffs_file_selector_api_dispose; } -static FfsFileSelectorApi* ffs_file_selector_api_new(const FfsFileSelectorApiVTable* vtable, gpointer user_data, GDestroyNotify user_data_free_func) { - FfsFileSelectorApi* self = FFS_FILE_SELECTOR_API(g_object_new(ffs_file_selector_api_get_type(), nullptr)); +static FfsFileSelectorApi* ffs_file_selector_api_new( + const FfsFileSelectorApiVTable* vtable, gpointer user_data, + GDestroyNotify user_data_free_func) { + FfsFileSelectorApi* self = FFS_FILE_SELECTOR_API( + g_object_new(ffs_file_selector_api_get_type(), nullptr)); self->vtable = vtable; self->user_data = user_data; self->user_data_free_func = user_data_free_func; return self; } -static void ffs_file_selector_api_show_file_chooser_cb(FlBasicMessageChannel* channel, FlValue* message_, FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) { +static void ffs_file_selector_api_show_file_chooser_cb( + FlBasicMessageChannel* channel, FlValue* message_, + FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) { FfsFileSelectorApi* self = FFS_FILE_SELECTOR_API(user_data); if (self->vtable == nullptr || self->vtable->show_file_chooser == nullptr) { @@ -406,36 +500,64 @@ static void ffs_file_selector_api_show_file_chooser_cb(FlBasicMessageChannel* ch } FlValue* value0 = fl_value_get_list_value(message_, 0); - FfsPlatformFileChooserActionType type = static_cast(fl_value_get_int(reinterpret_cast(const_cast(fl_value_get_custom_value(value0))))); + FfsPlatformFileChooserActionType type = + static_cast( + fl_value_get_int(reinterpret_cast( + const_cast(fl_value_get_custom_value(value0))))); FlValue* value1 = fl_value_get_list_value(message_, 1); - FfsPlatformFileChooserOptions* options = FFS_PLATFORM_FILE_CHOOSER_OPTIONS(fl_value_get_custom_value_object(value1)); - g_autoptr(FfsFileSelectorApiShowFileChooserResponse) response = self->vtable->show_file_chooser(type, options, self->user_data); + FfsPlatformFileChooserOptions* options = FFS_PLATFORM_FILE_CHOOSER_OPTIONS( + fl_value_get_custom_value_object(value1)); + g_autoptr(FfsFileSelectorApiShowFileChooserResponse) response = + self->vtable->show_file_chooser(type, options, self->user_data); if (response == nullptr) { - g_warning("No response returned to %s.%s", "FileSelectorApi", "showFileChooser"); + g_warning("No response returned to %s.%s", "FileSelectorApi", + "showFileChooser"); return; } g_autoptr(GError) error = NULL; - if (!fl_basic_message_channel_respond(channel, response_handle, response->value, &error)) { - g_warning("Failed to send response to %s.%s: %s", "FileSelectorApi", "showFileChooser", error->message); + if (!fl_basic_message_channel_respond(channel, response_handle, + response->value, &error)) { + g_warning("Failed to send response to %s.%s: %s", "FileSelectorApi", + "showFileChooser", error->message); } } -void ffs_file_selector_api_set_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix, const FfsFileSelectorApiVTable* vtable, gpointer user_data, GDestroyNotify user_data_free_func) { - g_autofree gchar* dot_suffix = suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); - g_autoptr(FfsFileSelectorApi) api_data = ffs_file_selector_api_new(vtable, user_data, user_data_free_func); +void ffs_file_selector_api_set_method_handlers( + FlBinaryMessenger* messenger, const gchar* suffix, + const FfsFileSelectorApiVTable* vtable, gpointer user_data, + GDestroyNotify user_data_free_func) { + g_autofree gchar* dot_suffix = + suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); + g_autoptr(FfsFileSelectorApi) api_data = + ffs_file_selector_api_new(vtable, user_data, user_data_free_func); g_autoptr(FfsMessageCodec) codec = ffs_message_codec_new(); - g_autofree gchar* show_file_chooser_channel_name = g_strdup_printf("dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser%s", dot_suffix); - g_autoptr(FlBasicMessageChannel) show_file_chooser_channel = fl_basic_message_channel_new(messenger, show_file_chooser_channel_name, FL_MESSAGE_CODEC(codec)); - fl_basic_message_channel_set_message_handler(show_file_chooser_channel, ffs_file_selector_api_show_file_chooser_cb, g_object_ref(api_data), g_object_unref); -} - -void ffs_file_selector_api_clear_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix) { - g_autofree gchar* dot_suffix = suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); + g_autofree gchar* show_file_chooser_channel_name = g_strdup_printf( + "dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser%" + "s", + dot_suffix); + g_autoptr(FlBasicMessageChannel) show_file_chooser_channel = + fl_basic_message_channel_new(messenger, show_file_chooser_channel_name, + FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler( + show_file_chooser_channel, ffs_file_selector_api_show_file_chooser_cb, + g_object_ref(api_data), g_object_unref); +} + +void ffs_file_selector_api_clear_method_handlers(FlBinaryMessenger* messenger, + const gchar* suffix) { + g_autofree gchar* dot_suffix = + suffix != nullptr ? g_strdup_printf(".%s", suffix) : g_strdup(""); g_autoptr(FfsMessageCodec) codec = ffs_message_codec_new(); - g_autofree gchar* show_file_chooser_channel_name = g_strdup_printf("dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser%s", dot_suffix); - g_autoptr(FlBasicMessageChannel) show_file_chooser_channel = fl_basic_message_channel_new(messenger, show_file_chooser_channel_name, FL_MESSAGE_CODEC(codec)); - fl_basic_message_channel_set_message_handler(show_file_chooser_channel, nullptr, nullptr, nullptr); + g_autofree gchar* show_file_chooser_channel_name = g_strdup_printf( + "dev.flutter.pigeon.file_selector_linux.FileSelectorApi.showFileChooser%" + "s", + dot_suffix); + g_autoptr(FlBasicMessageChannel) show_file_chooser_channel = + fl_basic_message_channel_new(messenger, show_file_chooser_channel_name, + FL_MESSAGE_CODEC(codec)); + fl_basic_message_channel_set_message_handler(show_file_chooser_channel, + nullptr, nullptr, nullptr); } 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 c0c9001e0d2..203f2a3b65d 100644 --- a/packages/file_selector/file_selector_linux/linux/messages.g.h +++ b/packages/file_selector/file_selector_linux/linux/messages.g.h @@ -31,7 +31,8 @@ typedef enum { * A Pigeon representation of the Linux portion of an `XTypeGroup`. */ -G_DECLARE_FINAL_TYPE(FfsPlatformTypeGroup, ffs_platform_type_group, FFS, PLATFORM_TYPE_GROUP, GObject) +G_DECLARE_FINAL_TYPE(FfsPlatformTypeGroup, ffs_platform_type_group, FFS, + PLATFORM_TYPE_GROUP, GObject) /** * ffs_platform_type_group_new: @@ -43,7 +44,9 @@ G_DECLARE_FINAL_TYPE(FfsPlatformTypeGroup, ffs_platform_type_group, FFS, PLATFOR * * Returns: a new #FfsPlatformTypeGroup */ -FfsPlatformTypeGroup* ffs_platform_type_group_new(const gchar* label, FlValue* extensions, FlValue* mime_types); +FfsPlatformTypeGroup* ffs_platform_type_group_new(const gchar* label, + FlValue* extensions, + FlValue* mime_types); /** * ffs_platform_type_group_get_label @@ -83,7 +86,9 @@ FlValue* ffs_platform_type_group_get_mime_types(FfsPlatformTypeGroup* object); * These correspond to gtk_file_chooser_set_* options. */ -G_DECLARE_FINAL_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_options, FFS, PLATFORM_FILE_CHOOSER_OPTIONS, GObject) +G_DECLARE_FINAL_TYPE(FfsPlatformFileChooserOptions, + ffs_platform_file_chooser_options, FFS, + PLATFORM_FILE_CHOOSER_OPTIONS, GObject) /** * ffs_platform_file_chooser_options_new: @@ -98,7 +103,10 @@ G_DECLARE_FINAL_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_op * * Returns: a new #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* create_folders); +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* create_folders); /** * ffs_platform_file_chooser_options_get_allowed_file_types @@ -108,7 +116,8 @@ FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new(FlValue* al * * Returns: the field value. */ -FlValue* ffs_platform_file_chooser_options_get_allowed_file_types(FfsPlatformFileChooserOptions* object); +FlValue* ffs_platform_file_chooser_options_get_allowed_file_types( + FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_current_folder_path @@ -118,7 +127,8 @@ FlValue* ffs_platform_file_chooser_options_get_allowed_file_types(FfsPlatformFil * * Returns: the field value. */ -const gchar* ffs_platform_file_chooser_options_get_current_folder_path(FfsPlatformFileChooserOptions* object); +const gchar* ffs_platform_file_chooser_options_get_current_folder_path( + FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_current_name @@ -128,7 +138,8 @@ const gchar* ffs_platform_file_chooser_options_get_current_folder_path(FfsPlatfo * * Returns: the field value. */ -const gchar* ffs_platform_file_chooser_options_get_current_name(FfsPlatformFileChooserOptions* object); +const gchar* ffs_platform_file_chooser_options_get_current_name( + FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_accept_button_label @@ -138,7 +149,8 @@ const gchar* ffs_platform_file_chooser_options_get_current_name(FfsPlatformFileC * * Returns: the field value. */ -const gchar* ffs_platform_file_chooser_options_get_accept_button_label(FfsPlatformFileChooserOptions* object); +const gchar* ffs_platform_file_chooser_options_get_accept_button_label( + FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_select_multiple @@ -150,7 +162,8 @@ const gchar* ffs_platform_file_chooser_options_get_accept_button_label(FfsPlatfo * * Returns: the field value. */ -gboolean* ffs_platform_file_chooser_options_get_select_multiple(FfsPlatformFileChooserOptions* object); +gboolean* ffs_platform_file_chooser_options_get_select_multiple( + FfsPlatformFileChooserOptions* object); /** * ffs_platform_file_chooser_options_get_create_folders @@ -162,13 +175,18 @@ gboolean* ffs_platform_file_chooser_options_get_select_multiple(FfsPlatformFileC * * Returns: the field value. */ -gboolean* ffs_platform_file_chooser_options_get_create_folders(FfsPlatformFileChooserOptions* object); +gboolean* ffs_platform_file_chooser_options_get_create_folders( + FfsPlatformFileChooserOptions* object); -G_DECLARE_FINAL_TYPE(FfsMessageCodec, ffs_message_codec, FFS, MESSAGE_CODEC, FlStandardMessageCodec) +G_DECLARE_FINAL_TYPE(FfsMessageCodec, ffs_message_codec, FFS, MESSAGE_CODEC, + FlStandardMessageCodec) -G_DECLARE_FINAL_TYPE(FfsFileSelectorApi, ffs_file_selector_api, FFS, FILE_SELECTOR_API, GObject) +G_DECLARE_FINAL_TYPE(FfsFileSelectorApi, ffs_file_selector_api, FFS, + FILE_SELECTOR_API, GObject) -G_DECLARE_FINAL_TYPE(FfsFileSelectorApiShowFileChooserResponse, ffs_file_selector_api_show_file_chooser_response, FFS, FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE, GObject) +G_DECLARE_FINAL_TYPE(FfsFileSelectorApiShowFileChooserResponse, + ffs_file_selector_api_show_file_chooser_response, FFS, + FILE_SELECTOR_API_SHOW_FILE_CHOOSER_RESPONSE, GObject) /** * ffs_file_selector_api_show_file_chooser_response_new: @@ -177,7 +195,8 @@ G_DECLARE_FINAL_TYPE(FfsFileSelectorApiShowFileChooserResponse, ffs_file_selecto * * Returns: a new #FfsFileSelectorApiShowFileChooserResponse */ -FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value); +FfsFileSelectorApiShowFileChooserResponse* +ffs_file_selector_api_show_file_chooser_response_new(FlValue* return_value); /** * ffs_file_selector_api_show_file_chooser_response_new_error: @@ -189,15 +208,21 @@ FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_choos * * Returns: a new #FfsFileSelectorApiShowFileChooserResponse */ -FfsFileSelectorApiShowFileChooserResponse* ffs_file_selector_api_show_file_chooser_response_new_error(const gchar* code, const gchar* message, FlValue* details); +FfsFileSelectorApiShowFileChooserResponse* +ffs_file_selector_api_show_file_chooser_response_new_error(const gchar* code, + const gchar* message, + FlValue* details); /** * FfsFileSelectorApiVTable: * - * Table of functions exposed by FileSelectorApi to be implemented by the API provider. + * Table of functions exposed by FileSelectorApi to be implemented by the API + * provider. */ typedef struct { - FfsFileSelectorApiShowFileChooserResponse* (*show_file_chooser)(FfsPlatformFileChooserActionType type, FfsPlatformFileChooserOptions* options, gpointer user_data); + FfsFileSelectorApiShowFileChooserResponse* (*show_file_chooser)( + FfsPlatformFileChooserActionType type, + FfsPlatformFileChooserOptions* options, gpointer user_data); } FfsFileSelectorApiVTable; /** @@ -207,11 +232,15 @@ typedef struct { * @suffix: (allow-none): a suffix to add to the API or %NULL for none. * @vtable: implementations of the methods in this API. * @user_data: (closure): user data to pass to the functions in @vtable. - * @user_data_free_func: (allow-none): a function which gets called to free @user_data, or %NULL. + * @user_data_free_func: (allow-none): a function which gets called to free + * @user_data, or %NULL. * * Connects the method handlers in the FileSelectorApi API. */ -void ffs_file_selector_api_set_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix, const FfsFileSelectorApiVTable* vtable, gpointer user_data, GDestroyNotify user_data_free_func); +void ffs_file_selector_api_set_method_handlers( + FlBinaryMessenger* messenger, const gchar* suffix, + const FfsFileSelectorApiVTable* vtable, gpointer user_data, + GDestroyNotify user_data_free_func); /** * ffs_file_selector_api_clear_method_handlers: @@ -221,7 +250,8 @@ void ffs_file_selector_api_set_method_handlers(FlBinaryMessenger* messenger, con * * Clears the method handlers in the FileSelectorApi API. */ -void ffs_file_selector_api_clear_method_handlers(FlBinaryMessenger* messenger, const gchar* suffix); +void ffs_file_selector_api_clear_method_handlers(FlBinaryMessenger* messenger, + const gchar* suffix); G_END_DECLS From 01638975456da318e78bc54f9b7034fa4ad6caed Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Fri, 5 Sep 2025 23:20:48 -0300 Subject: [PATCH 04/14] Fix tests --- .../test/file_selector_test.dart | 45 ++++++++++++++++++- .../test/file_selector_macos_test.dart | 4 +- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/packages/file_selector/file_selector/test/file_selector_test.dart b/packages/file_selector/file_selector/test/file_selector_test.dart index aaa5647a947..0ee16626995 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,8 @@ void main() { const String initialDirectory = '/home/flutteruser'; const String confirmButtonText = 'Use this profile picture'; const String suggestedName = 'suggested_name'; + const bool canCreateDirectories = true; + const List acceptedTypeGroups = [ XTypeGroup( label: 'documents', @@ -278,6 +280,17 @@ void main() { ); expect(directoryPath, expectedDirectoryPath); }); + + test('sets the canCreateDirectories parameter', () async { + fakePlatformImplementation + ..setExpectations(canCreateDirectories: canCreateDirectories) + ..setPathsResponse([expectedDirectoryPath]); + + final String? directoryPath = await getDirectoryPath( + canCreateDirectories: canCreateDirectories, + ); + expect(directoryPath, expectedDirectoryPath); + }); }); group('getDirectoryPaths', () { @@ -330,6 +343,16 @@ void main() { ); expect(directoryPaths, expectedDirectoryPaths); }); + test('sets the canCreateDirectories parameter', () async { + fakePlatformImplementation + ..setExpectations(canCreateDirectories: canCreateDirectories) + ..setPathsResponse(expectedDirectoryPaths); + + final List directoryPaths = await getDirectoryPaths( + canCreateDirectories: true, + ); + expect(directoryPaths, expectedDirectoryPaths); + }); }); } @@ -341,7 +364,7 @@ class FakeFileSelector extends Fake String? initialDirectory; String? confirmButtonText; String? suggestedName; - bool canCreateDirectories = true; + bool? canCreateDirectories; // Return values. List? files; List? paths; @@ -352,11 +375,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 @@ -445,6 +470,14 @@ class FakeFileSelector extends Fake return paths?[0]; } + @override + Future getDirectoryPathWithOptions(FileDialogOptions options) async { + expect(options.initialDirectory, initialDirectory); + expect(options.confirmButtonText, confirmButtonText); + expect(options.canCreateDirectories, canCreateDirectories); + return paths?[0]; + } + @override Future> getDirectoryPaths({ String? initialDirectory, @@ -456,4 +489,14 @@ class FakeFileSelector extends Fake 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_macos/test/file_selector_macos_test.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart index 1833026bc29..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 @@ -587,7 +587,7 @@ void main() { expect(options.baseOptions.allowedFileTypes, null); expect(options.baseOptions.directoryPath, null); expect(options.baseOptions.nameFieldStringValue, null); - expect(options.baseOptions.canCreateDirectories, true); + expect(options.baseOptions.canCreateDirectories, null); expect(options.baseOptions.prompt, null); }); @@ -726,7 +726,7 @@ void main() { expect(options.baseOptions.allowedFileTypes, null); expect(options.baseOptions.directoryPath, null); expect(options.baseOptions.nameFieldStringValue, null); - expect(options.baseOptions.canCreateDirectories, true); + expect(options.baseOptions.canCreateDirectories, null); expect(options.baseOptions.prompt, null); }); From 8dd0e5d6b61656cc2695727b8ee5b1653bc145bf Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Fri, 5 Sep 2025 23:34:07 -0300 Subject: [PATCH 05/14] Implement platform interface --- .../CHANGELOG.md | 3 +- .../method_channel_file_selector.dart | 39 +++++-- .../file_selector_interface.dart | 31 ++++- .../lib/src/types/file_dialog_options.dart | 11 +- .../pubspec.yaml | 2 +- .../method_channel_file_selector_test.dart | 108 +++++++++++++++++- 6 files changed, 176 insertions(+), 18 deletions(-) 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/method_channel/method_channel_file_selector.dart b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart index 8d787f4fa3b..ef2874a14ca 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart @@ -78,10 +78,22 @@ class MethodChannelFileSelector extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - return _channel.invokeMethod('getDirectoryPath', { - 'initialDirectory': initialDirectory, - 'confirmButtonText': confirmButtonText, - }); + return getDirectoryPathWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + ), + ); + } + + @override + Future getDirectoryPathWithOptions(FileDialogOptions options) async { + return _channel + .invokeMethod('getDirectoryPathWithOptions', { + 'initialDirectory': options.initialDirectory, + 'confirmButtonText': options.confirmButtonText, + 'canCreateDirectories': options.canCreateDirectories, + }); } @override @@ -89,11 +101,24 @@ class MethodChannelFileSelector extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { + return getDirectoryPathsWithOptions( + FileDialogOptions( + initialDirectory: initialDirectory, + confirmButtonText: confirmButtonText, + ), + ); + } + + @override + Future> getDirectoryPathsWithOptions( + FileDialogOptions options, + ) async { final List? pathList = await _channel.invokeListMethod( - 'getDirectoryPaths', + 'getDirectoryPathsWithOptions', { - 'initialDirectory': initialDirectory, - 'confirmButtonText': confirmButtonText, + 'initialDirectory': options.initialDirectory, + 'confirmButtonText': options.confirmButtonText, + 'canCreateDirectories': options.canCreateDirectories, }, ); return pathList ?? []; 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..35ee7f36c0a 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,40 @@ 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) { + throw UnimplementedError( + 'getDirectoryPathWithOptions() has not been implemented.', + ); + } + /// 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) { + throw UnimplementedError( + 'getDirectoryPathsWithOptions() has not been implemented.', + ); + } } 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..0ff8df2d1aa 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,21 @@ 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 supported on the platform). + /// Currently only supported on Linux and macOS. + final bool? canCreateDirectories; } /// Configuration options for a save dialog. @@ -24,6 +32,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/method_channel_file_selector_test.dart b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart index cbacbbf648f..d35e7f3ec72 100644 --- a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart @@ -226,10 +226,11 @@ void main() { expectMethodCall( log, - 'getDirectoryPath', + 'getDirectoryPathWithOptions', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, + 'canCreateDirectories': null, }, ); }); @@ -238,10 +239,58 @@ void main() { expectMethodCall( log, - 'getDirectoryPath', + 'getDirectoryPathWithOptions', arguments: { 'initialDirectory': null, 'confirmButtonText': 'Select Folder', + 'canCreateDirectories': null, + }, + ); + }); + }); + group('#getDirectoryPathWithOptions', () { + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(initialDirectory: '/example/directory'), + ); + + expectMethodCall( + log, + 'getDirectoryPathWithOptions', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + 'canCreateDirectories': null, + }, + ); + }); + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(confirmButtonText: 'Select Folder'), + ); + + expectMethodCall( + log, + 'getDirectoryPathWithOptions', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Select Folder', + 'canCreateDirectories': null, + }, + ); + }); + test('passes canCreateDirectories correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(canCreateDirectories: true), + ); + + expectMethodCall( + log, + 'getDirectoryPathWithOptions', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': null, + 'canCreateDirectories': true, }, ); }); @@ -252,10 +301,11 @@ void main() { expectMethodCall( log, - 'getDirectoryPaths', + 'getDirectoryPathsWithOptions', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, + 'canCreateDirectories': null, }, ); }); @@ -266,10 +316,60 @@ void main() { expectMethodCall( log, - 'getDirectoryPaths', + 'getDirectoryPathsWithOptions', arguments: { 'initialDirectory': null, 'confirmButtonText': 'Select one or more Folders', + 'canCreateDirectories': null, + }, + ); + }); + }); + group('#getDirectoryPathsWithOptions', () { + test('passes initialDirectory correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(initialDirectory: '/example/directory'), + ); + + expectMethodCall( + log, + 'getDirectoryPathWithOptions', + arguments: { + 'initialDirectory': '/example/directory', + 'confirmButtonText': null, + 'canCreateDirectories': null, + }, + ); + }); + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions( + confirmButtonText: 'Select one or more Folders', + ), + ); + + expectMethodCall( + log, + 'getDirectoryPathWithOptions', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': 'Select one or more Folders', + 'canCreateDirectories': null, + }, + ); + }); + test('passes canCreateDirectories correctly', () async { + await plugin.getDirectoryPathWithOptions( + const FileDialogOptions(canCreateDirectories: true), + ); + + expectMethodCall( + log, + 'getDirectoryPathWithOptions', + arguments: { + 'initialDirectory': null, + 'confirmButtonText': null, + 'canCreateDirectories': true, }, ); }); From 4c95589a2833d7d766bca22388bcdf210f91d0c9 Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Sat, 6 Sep 2025 01:15:18 -0300 Subject: [PATCH 06/14] fix: pass the correct parameter to hostapi --- .../file_selector_linux/lib/file_selector_linux.dart | 1 + 1 file changed, 1 insertion(+) 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 8f6c00fe719..08c2ee2e269 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 @@ -119,6 +119,7 @@ class FileSelectorLinux extends FileSelectorPlatform { PlatformFileChooserOptions( currentFolderPath: options.initialDirectory, acceptButtonLabel: options.confirmButtonText, + createFolders: options.canCreateDirectories, selectMultiple: false, ), ); From 763605922a051679a6df741793c89054fcea3f46 Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Sat, 6 Sep 2025 01:20:44 -0300 Subject: [PATCH 07/14] tests: fix linux tests --- .../linux/test/file_selector_plugin_test.cc | 22 +++-- .../test/file_selector_linux_test.dart | 85 +++++++++++++++++++ 2 files changed, 99 insertions(+), 8 deletions(-) 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..bc3c9e3ebfe 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, @@ -185,9 +185,10 @@ TEST(FileSelectorPlugin, TestSaveWithArguments) { } TEST(FileSelectorPlugin, TestGetDirectory) { + gboolean create_folders = true; g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - nullptr); + nullptr, &create_folders); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, @@ -199,13 +200,16 @@ TEST(FileSelectorPlugin, TestGetDirectory) { GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); 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)), + true); } TEST(FileSelectorPlugin, TestGetMultipleDirectories) { gboolean select_multiple = true; + gboolean create_folders = true; g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - &select_multiple); + &select_multiple, &create_folders); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, @@ -217,6 +221,8 @@ TEST(FileSelectorPlugin, TestGetMultipleDirectories) { GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), true); + EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)), + true); } static gint mock_run_dialog_cancel(GtkNativeDialog* dialog) { @@ -226,7 +232,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/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. From fcc7f87b0a0b87c7263c24c17f0bbb0248cf2762 Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Wed, 10 Sep 2025 18:06:03 -0300 Subject: [PATCH 08/14] fix: changelog and docs --- packages/file_selector/file_selector/CHANGELOG.md | 2 +- .../file_selector/file_selector/lib/file_selector.dart | 8 ++++---- .../file_selector/file_selector_android/CHANGELOG.md | 2 +- packages/file_selector/file_selector_ios/CHANGELOG.md | 3 +-- packages/file_selector/file_selector_ios/pubspec.yaml | 2 +- .../file_selector/file_selector_linux/CHANGELOG.md | 2 +- .../file_selector/file_selector_macos/CHANGELOG.md | 2 +- .../platform_interface/file_selector_interface.dart | 10 ++++++---- .../lib/src/types/file_dialog_options.dart | 7 +++++-- packages/file_selector/file_selector_web/CHANGELOG.md | 2 +- 10 files changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index e33a119908f..8dfb56ca865 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,6 +1,6 @@ ## 1.1.0 -* Adds `canCreateDirectories` parameter to `FileDialogOptions` to control the visibility of the New Folder button in file dialogs on supported platforms. +* 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/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index 7b2586aeb91..73ab1548563 100644 --- a/packages/file_selector/file_selector/lib/file_selector.dart +++ b/packages/file_selector/file_selector/lib/file_selector.dart @@ -122,8 +122,8 @@ Future getSaveLocation({ /// 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 (if supported on the platform). -/// Currently only supported on Linux and macOS. +/// 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({ @@ -153,8 +153,8 @@ Future getDirectoryPath({ /// 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 (if supported on the platform). -/// Currently only supported on Linux and macOS. +/// 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({ diff --git a/packages/file_selector/file_selector_android/CHANGELOG.md b/packages/file_selector/file_selector_android/CHANGELOG.md index 2995528f0e6..e5f67209022 100644 --- a/packages/file_selector/file_selector_android/CHANGELOG.md +++ b/packages/file_selector/file_selector_android/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.5.2 -* Adds `canCreateDirectories` parameter to `FileDialogOptions` to control the visibility of the New Folder button in file dialogs on supported platforms. +* Adds `getDirectoryPathWithOptions` implementation. ## 0.5.1+16 diff --git a/packages/file_selector/file_selector_ios/CHANGELOG.md b/packages/file_selector/file_selector_ios/CHANGELOG.md index feb3d0bba6c..b34eedbd891 100644 --- a/packages/file_selector/file_selector_ios/CHANGELOG.md +++ b/packages/file_selector/file_selector_ios/CHANGELOG.md @@ -1,6 +1,5 @@ -## 0.5.4 +## NEXT -* 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. ## 0.5.3+2 diff --git a/packages/file_selector/file_selector_ios/pubspec.yaml b/packages/file_selector/file_selector_ios/pubspec.yaml index a9cefd452e5..e3d711e277d 100644 --- a/packages/file_selector/file_selector_ios/pubspec.yaml +++ b/packages/file_selector/file_selector_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: file_selector_ios description: iOS implementation of the file_selector plugin. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.5.4 +version: 0.5.3+2 environment: sdk: ^3.7.0 diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md index a528639d2e0..775c9aac657 100644 --- a/packages/file_selector/file_selector_linux/CHANGELOG.md +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.9.4 -* Adds `canCreateDirectories` parameter to `FileDialogOptions` to control the visibility of the New Folder button in file dialogs on supported platforms. +* 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_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index f7394fefaba..eb300d7fe3c 100644 --- a/packages/file_selector/file_selector_macos/CHANGELOG.md +++ b/packages/file_selector/file_selector_macos/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.9.5 -* Adds `canCreateDirectories` parameter to `FileDialogOptions` to control the visibility of the New Folder button in file dialogs on supported platforms. +* 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_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 35ee7f36c0a..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 @@ -111,8 +111,9 @@ abstract class FileSelectorPlatform extends PlatformInterface { /// /// Returns `null` if the user cancels the operation. Future getDirectoryPathWithOptions(FileDialogOptions options) { - throw UnimplementedError( - 'getDirectoryPathWithOptions() has not been implemented.', + return getDirectoryPath( + initialDirectory: options.initialDirectory, + confirmButtonText: options.confirmButtonText, ); } @@ -136,8 +137,9 @@ abstract class FileSelectorPlatform extends PlatformInterface { /// /// Returns an empty list if the user cancels the operation. Future> getDirectoryPathsWithOptions(FileDialogOptions options) { - throw UnimplementedError( - 'getDirectoryPathsWithOptions() has not been implemented.', + 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 0ff8df2d1aa..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 @@ -20,8 +20,11 @@ class FileDialogOptions { /// The label for the button that confirms selection. final String? confirmButtonText; - /// Whether the user is allowed to create new directories in the dialog (if supported on the platform). - /// Currently only supported on Linux and macOS. + /// 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; } diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md index 9b00a9d72d8..14486f4dace 100644 --- a/packages/file_selector/file_selector_web/CHANGELOG.md +++ b/packages/file_selector/file_selector_web/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.9.5 -* Adds `canCreateDirectories` parameter to `FileDialogOptions` to control the visibility of the New Folder button in file dialogs on supported platforms. +* Adds `getDirectoryPathWithOptions` implementation. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 0.9.4+2 From a536f18a076db2ce8a8ee3feea293d1cdfa5945c Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Wed, 10 Sep 2025 18:06:41 -0300 Subject: [PATCH 09/14] fix: undo changes on method channel --- .../method_channel_file_selector.dart | 39 ++----- .../method_channel_file_selector_test.dart | 108 +----------------- 2 files changed, 11 insertions(+), 136 deletions(-) diff --git a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart index ef2874a14ca..8d787f4fa3b 100644 --- a/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart +++ b/packages/file_selector/file_selector_platform_interface/lib/src/method_channel/method_channel_file_selector.dart @@ -78,22 +78,10 @@ class MethodChannelFileSelector extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - return getDirectoryPathWithOptions( - FileDialogOptions( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, - ), - ); - } - - @override - Future getDirectoryPathWithOptions(FileDialogOptions options) async { - return _channel - .invokeMethod('getDirectoryPathWithOptions', { - 'initialDirectory': options.initialDirectory, - 'confirmButtonText': options.confirmButtonText, - 'canCreateDirectories': options.canCreateDirectories, - }); + return _channel.invokeMethod('getDirectoryPath', { + 'initialDirectory': initialDirectory, + 'confirmButtonText': confirmButtonText, + }); } @override @@ -101,24 +89,11 @@ class MethodChannelFileSelector extends FileSelectorPlatform { String? initialDirectory, String? confirmButtonText, }) async { - return getDirectoryPathsWithOptions( - FileDialogOptions( - initialDirectory: initialDirectory, - confirmButtonText: confirmButtonText, - ), - ); - } - - @override - Future> getDirectoryPathsWithOptions( - FileDialogOptions options, - ) async { final List? pathList = await _channel.invokeListMethod( - 'getDirectoryPathsWithOptions', + 'getDirectoryPaths', { - 'initialDirectory': options.initialDirectory, - 'confirmButtonText': options.confirmButtonText, - 'canCreateDirectories': options.canCreateDirectories, + 'initialDirectory': initialDirectory, + 'confirmButtonText': confirmButtonText, }, ); return pathList ?? []; diff --git a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart index d35e7f3ec72..cbacbbf648f 100644 --- a/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart +++ b/packages/file_selector/file_selector_platform_interface/test/method_channel_file_selector_test.dart @@ -226,11 +226,10 @@ void main() { expectMethodCall( log, - 'getDirectoryPathWithOptions', + 'getDirectoryPath', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, - 'canCreateDirectories': null, }, ); }); @@ -239,58 +238,10 @@ void main() { expectMethodCall( log, - 'getDirectoryPathWithOptions', + 'getDirectoryPath', arguments: { 'initialDirectory': null, 'confirmButtonText': 'Select Folder', - 'canCreateDirectories': null, - }, - ); - }); - }); - group('#getDirectoryPathWithOptions', () { - test('passes initialDirectory correctly', () async { - await plugin.getDirectoryPathWithOptions( - const FileDialogOptions(initialDirectory: '/example/directory'), - ); - - expectMethodCall( - log, - 'getDirectoryPathWithOptions', - arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - 'canCreateDirectories': null, - }, - ); - }); - test('passes confirmButtonText correctly', () async { - await plugin.getDirectoryPathWithOptions( - const FileDialogOptions(confirmButtonText: 'Select Folder'), - ); - - expectMethodCall( - log, - 'getDirectoryPathWithOptions', - arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Select Folder', - 'canCreateDirectories': null, - }, - ); - }); - test('passes canCreateDirectories correctly', () async { - await plugin.getDirectoryPathWithOptions( - const FileDialogOptions(canCreateDirectories: true), - ); - - expectMethodCall( - log, - 'getDirectoryPathWithOptions', - arguments: { - 'initialDirectory': null, - 'confirmButtonText': null, - 'canCreateDirectories': true, }, ); }); @@ -301,11 +252,10 @@ void main() { expectMethodCall( log, - 'getDirectoryPathsWithOptions', + 'getDirectoryPaths', arguments: { 'initialDirectory': '/example/directory', 'confirmButtonText': null, - 'canCreateDirectories': null, }, ); }); @@ -316,60 +266,10 @@ void main() { expectMethodCall( log, - 'getDirectoryPathsWithOptions', + 'getDirectoryPaths', arguments: { 'initialDirectory': null, 'confirmButtonText': 'Select one or more Folders', - 'canCreateDirectories': null, - }, - ); - }); - }); - group('#getDirectoryPathsWithOptions', () { - test('passes initialDirectory correctly', () async { - await plugin.getDirectoryPathWithOptions( - const FileDialogOptions(initialDirectory: '/example/directory'), - ); - - expectMethodCall( - log, - 'getDirectoryPathWithOptions', - arguments: { - 'initialDirectory': '/example/directory', - 'confirmButtonText': null, - 'canCreateDirectories': null, - }, - ); - }); - test('passes confirmButtonText correctly', () async { - await plugin.getDirectoryPathWithOptions( - const FileDialogOptions( - confirmButtonText: 'Select one or more Folders', - ), - ); - - expectMethodCall( - log, - 'getDirectoryPathWithOptions', - arguments: { - 'initialDirectory': null, - 'confirmButtonText': 'Select one or more Folders', - 'canCreateDirectories': null, - }, - ); - }); - test('passes canCreateDirectories correctly', () async { - await plugin.getDirectoryPathWithOptions( - const FileDialogOptions(canCreateDirectories: true), - ); - - expectMethodCall( - log, - 'getDirectoryPathWithOptions', - arguments: { - 'initialDirectory': null, - 'confirmButtonText': null, - 'canCreateDirectories': true, }, ); }); From e91108cbefccb70c85742b223da2b371e07c1cac Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Wed, 10 Sep 2025 23:20:36 -0300 Subject: [PATCH 10/14] fix: tests --- .../test/file_selector_test.dart | 5 +- .../linux/test/file_selector_plugin_test.cc | 80 +++++++++++++++++-- ...file_selector_platform_interface_test.dart | 34 ++++++++ 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/packages/file_selector/file_selector/test/file_selector_test.dart b/packages/file_selector/file_selector/test/file_selector_test.dart index 0ee16626995..7a6afdcb82c 100644 --- a/packages/file_selector/file_selector/test/file_selector_test.dart +++ b/packages/file_selector/file_selector/test/file_selector_test.dart @@ -12,7 +12,6 @@ void main() { const String initialDirectory = '/home/flutteruser'; const String confirmButtonText = 'Use this profile picture'; const String suggestedName = 'suggested_name'; - const bool canCreateDirectories = true; const List acceptedTypeGroups = [ XTypeGroup( @@ -282,6 +281,7 @@ void main() { }); test('sets the canCreateDirectories parameter', () async { + const bool canCreateDirectories = true; fakePlatformImplementation ..setExpectations(canCreateDirectories: canCreateDirectories) ..setPathsResponse([expectedDirectoryPath]); @@ -344,12 +344,13 @@ void main() { expect(directoryPaths, expectedDirectoryPaths); }); test('sets the canCreateDirectories parameter', () async { + const bool canCreateDirectories = true; fakePlatformImplementation ..setExpectations(canCreateDirectories: canCreateDirectories) ..setPathsResponse(expectedDirectoryPaths); final List directoryPaths = await getDirectoryPaths( - canCreateDirectories: true, + canCreateDirectories: canCreateDirectories, ); expect(directoryPaths, expectedDirectoryPaths); }); 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 bc3c9e3ebfe..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 @@ -184,12 +184,49 @@ TEST(FileSelectorPlugin, TestSaveWithArguments) { // doesn't in a test context, so that's not currently validated. } -TEST(FileSelectorPlugin, TestGetDirectory) { +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); + g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_CHOOSE_DIRECTORY, @@ -200,16 +237,13 @@ TEST(FileSelectorPlugin, TestGetDirectory) { GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); 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)), - true); } TEST(FileSelectorPlugin, TestGetMultipleDirectories) { gboolean select_multiple = true; - gboolean create_folders = true; g_autoptr(FfsPlatformFileChooserOptions) options = ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr, - &select_multiple, &create_folders); + &select_multiple, nullptr); g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type( nullptr, @@ -221,8 +255,42 @@ TEST(FileSelectorPlugin, TestGetMultipleDirectories) { GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)), 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)), - true); + 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) { 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(); From 9754ff227431684ac14346f91c53bca467294c5f Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Wed, 10 Sep 2025 23:21:41 -0300 Subject: [PATCH 11/14] adds createDirectories param to getSaveLocation --- .../file_selector_linux/lib/file_selector_linux.dart | 1 + .../file_selector_macos/lib/file_selector_macos.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart index 08c2ee2e269..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); 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 9b56de32276..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); From c3d09bb08ce9fc59a4df6bff5c592e617b10137d Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Wed, 10 Sep 2025 23:33:12 -0300 Subject: [PATCH 12/14] Fix file_selector_windows changelog --- packages/file_selector/file_selector_windows/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md index 7d697b34bb4..c845b3c1972 100644 --- a/packages/file_selector/file_selector_windows/CHANGELOG.md +++ b/packages/file_selector/file_selector_windows/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.9.4 -* Adds `canCreateDirectories` parameter to `FileDialogOptions` to control the visibility of the New Folder button in file dialogs on supported platforms. +* Adds `getDirectoryPathWithOptions` and `getDirectoryPathsWithOptions` implementations. * Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 0.9.3+4 From b40b3afa4f20ffd423c046eaadbc95c0d26b1f7e Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Thu, 11 Sep 2025 00:30:08 -0300 Subject: [PATCH 13/14] update runnerTests --- .../file_selector/example/macos/Podfile | 2 +- .../macos/Runner.xcodeproj/project.pbxproj | 24 ++++++- .../file_selector_macos/example/macos/Podfile | 2 +- .../macos/Runner.xcodeproj/project.pbxproj | 24 ++++++- .../macos/RunnerTests/RunnerTests.swift | 70 +++++++++++++++++++ 5 files changed, 114 insertions(+), 8 deletions(-) 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_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) + } + } } From cb4e65c3d0d34a0cd05834b319a78a1abe148fea Mon Sep 17 00:00:00 2001 From: Daniel Ferreira Date: Thu, 11 Sep 2025 01:24:22 -0300 Subject: [PATCH 14/14] add parameter to getSaveLocation --- .../file_selector/lib/file_selector.dart | 6 ++++++ .../file_selector/test/file_selector_test.dart | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/file_selector/file_selector/lib/file_selector.dart b/packages/file_selector/file_selector/lib/file_selector.dart index 73ab1548563..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, ), ); } 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 7a6afdcb82c..445d2999c9f 100644 --- a/packages/file_selector/file_selector/test/file_selector_test.dart +++ b/packages/file_selector/file_selector/test/file_selector_test.dart @@ -228,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', () { @@ -280,7 +292,7 @@ void main() { expect(directoryPath, expectedDirectoryPath); }); - test('sets the canCreateDirectories parameter', () async { + test('sets to enable de creation of new directories', () async { const bool canCreateDirectories = true; fakePlatformImplementation ..setExpectations(canCreateDirectories: canCreateDirectories) @@ -343,7 +355,7 @@ void main() { ); expect(directoryPaths, expectedDirectoryPaths); }); - test('sets the canCreateDirectories parameter', () async { + test('sets to enable de creation of new directories', () async { const bool canCreateDirectories = true; fakePlatformImplementation ..setExpectations(canCreateDirectories: canCreateDirectories)