[file_selector] Implement canCreateDirectories on macos and linux (#10443)
Updates macos and linux `file_selector` platforms to implement the new `canCreateDirectories` parameter from platform interface
Until now, only macos and linux are able to override this parameter
This is the "platform implementations" step for #9965
Part of: flutter/flutter#141339
## Pre-Review Checklist
**Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.
[^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md
index face005..54d3a9e 100644
--- a/packages/file_selector/file_selector_linux/CHANGELOG.md
+++ b/packages/file_selector/file_selector_linux/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.9.4
+
+* Adds `getDirectoryPathWithOptions` and `getDirectoryPathsWithOptions` implementations.
+
## 0.9.3+3
* Updates to Pigeon 26.
diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml
index bf4a8ca..73248a2 100644
--- a/packages/file_selector/file_selector_linux/example/pubspec.yaml
+++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml
@@ -10,7 +10,7 @@
dependencies:
file_selector_linux:
path: ../
- file_selector_platform_interface: ^2.6.0
+ file_selector_platform_interface: ^2.7.0
flutter:
sdk: flutter
diff --git a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart
index ae81609..f80c9f6 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 @@
currentFolderPath: options.initialDirectory,
currentName: options.suggestedName,
acceptButtonLabel: options.confirmButtonText,
+ createFolders: options.canCreateDirectories,
),
);
return paths.isEmpty ? null : FileSaveLocation(paths.first);
@@ -104,11 +105,22 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
+ return getDirectoryPathWithOptions(
+ FileDialogOptions(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ ),
+ );
+ }
+
+ @override
+ Future<String?> getDirectoryPathWithOptions(FileDialogOptions options) async {
final List<String> paths = await _hostApi.showFileChooser(
PlatformFileChooserActionType.chooseDirectory,
PlatformFileChooserOptions(
- currentFolderPath: initialDirectory,
- acceptButtonLabel: confirmButtonText,
+ currentFolderPath: options.initialDirectory,
+ acceptButtonLabel: options.confirmButtonText,
+ createFolders: options.canCreateDirectories,
selectMultiple: false,
),
);
@@ -120,11 +132,24 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
+ return getDirectoryPathsWithOptions(
+ FileDialogOptions(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ ),
+ );
+ }
+
+ @override
+ Future<List<String>> 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 0de39fb..592db03 100644
--- a/packages/file_selector/file_selector_linux/lib/src/messages.g.dart
+++ b/packages/file_selector/file_selector_linux/lib/src/messages.g.dart
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Autogenerated from Pigeon (v26.1.0), do not edit directly.
+// Autogenerated from Pigeon (v26.1.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
@@ -97,6 +97,7 @@
this.currentName,
this.acceptButtonLabel,
this.selectMultiple,
+ this.createFolders,
});
List<PlatformTypeGroup>? allowedFileTypes;
@@ -112,6 +113,11 @@
/// Nullable because it does not apply to the "save" action.
bool? selectMultiple;
+ /// Whether to allow new folder creation.
+ ///
+ /// Nullable because it does not apply to the "open" action.
+ bool? createFolders;
+
List<Object?> _toList() {
return <Object?>[
allowedFileTypes,
@@ -119,6 +125,7 @@
currentName,
acceptButtonLabel,
selectMultiple,
+ createFolders,
];
}
@@ -135,6 +142,7 @@
currentName: result[2] as String?,
acceptButtonLabel: result[3] as String?,
selectMultiple: result[4] as bool?,
+ createFolders: result[5] as bool?,
);
}
diff --git a/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc b/packages/file_selector/file_selector_linux/linux/file_selector_plugin.cc
index e87f94d..bd67979 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 @@
}
}
+ 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 158f235..5788006 100644
--- a/packages/file_selector/file_selector_linux/linux/messages.g.cc
+++ b/packages/file_selector/file_selector_linux/linux/messages.g.cc
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Autogenerated from Pigeon (v26.1.0), do not edit directly.
+// Autogenerated from Pigeon (v26.1.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#include "messages.g.h"
@@ -84,6 +84,7 @@
gchar* current_name;
gchar* accept_button_label;
gboolean* select_multiple;
+ gboolean* create_folders;
};
G_DEFINE_TYPE(FfsPlatformFileChooserOptions, ffs_platform_file_chooser_options,
@@ -97,6 +98,7 @@
g_clear_pointer(&self->current_name, g_free);
g_clear_pointer(&self->accept_button_label, g_free);
g_clear_pointer(&self->select_multiple, g_free);
+ g_clear_pointer(&self->create_folders, g_free);
G_OBJECT_CLASS(ffs_platform_file_chooser_options_parent_class)
->dispose(object);
}
@@ -112,7 +114,7 @@
FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new(
FlValue* allowed_file_types, const gchar* current_folder_path,
const gchar* current_name, const gchar* accept_button_label,
- gboolean* select_multiple) {
+ gboolean* select_multiple, gboolean* create_folders) {
FfsPlatformFileChooserOptions* self = FFS_PLATFORM_FILE_CHOOSER_OPTIONS(
g_object_new(ffs_platform_file_chooser_options_get_type(), nullptr));
if (allowed_file_types != nullptr) {
@@ -141,6 +143,12 @@
} else {
self->select_multiple = nullptr;
}
+ if (create_folders != nullptr) {
+ self->create_folders = static_cast<gboolean*>(malloc(sizeof(gboolean)));
+ *self->create_folders = *create_folders;
+ } else {
+ self->create_folders = nullptr;
+ }
return self;
}
@@ -174,6 +182,12 @@
return self->select_multiple;
}
+gboolean* ffs_platform_file_chooser_options_get_create_folders(
+ FfsPlatformFileChooserOptions* self) {
+ g_return_val_if_fail(FFS_IS_PLATFORM_FILE_CHOOSER_OPTIONS(self), nullptr);
+ return self->create_folders;
+}
+
static FlValue* ffs_platform_file_chooser_options_to_list(
FfsPlatformFileChooserOptions* self) {
FlValue* values = fl_value_new_list();
@@ -194,6 +208,9 @@
fl_value_append_take(values, self->select_multiple != nullptr
? fl_value_new_bool(*self->select_multiple)
: fl_value_new_null());
+ fl_value_append_take(values, self->create_folders != nullptr
+ ? fl_value_new_bool(*self->create_folders)
+ : fl_value_new_null());
return values;
}
@@ -226,9 +243,16 @@
select_multiple_value = fl_value_get_bool(value4);
select_multiple = &select_multiple_value;
}
+ FlValue* value5 = fl_value_get_list_value(values, 5);
+ gboolean* create_folders = nullptr;
+ gboolean create_folders_value;
+ if (fl_value_get_type(value5) != FL_VALUE_TYPE_NULL) {
+ create_folders_value = fl_value_get_bool(value5);
+ create_folders = &create_folders_value;
+ }
return ffs_platform_file_chooser_options_new(
allowed_file_types, current_folder_path, current_name,
- accept_button_label, select_multiple);
+ accept_button_label, select_multiple, create_folders);
}
struct _FfsMessageCodec {
diff --git a/packages/file_selector/file_selector_linux/linux/messages.g.h b/packages/file_selector/file_selector_linux/linux/messages.g.h
index 8995bbc..3d5c8c6 100644
--- a/packages/file_selector/file_selector_linux/linux/messages.g.h
+++ b/packages/file_selector/file_selector_linux/linux/messages.g.h
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Autogenerated from Pigeon (v26.1.0), do not edit directly.
+// Autogenerated from Pigeon (v26.1.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#ifndef PIGEON_MESSAGES_G_H_
@@ -97,6 +97,7 @@
* current_name: field in this object.
* accept_button_label: field in this object.
* select_multiple: field in this object.
+ * create_folders: field in this object.
*
* Creates a new #PlatformFileChooserOptions object.
*
@@ -105,7 +106,7 @@
FfsPlatformFileChooserOptions* ffs_platform_file_chooser_options_new(
FlValue* allowed_file_types, const gchar* current_folder_path,
const gchar* current_name, const gchar* accept_button_label,
- gboolean* select_multiple);
+ gboolean* select_multiple, gboolean* create_folders);
/**
* ffs_platform_file_chooser_options_get_allowed_file_types
@@ -164,6 +165,19 @@
gboolean* ffs_platform_file_chooser_options_get_select_multiple(
FfsPlatformFileChooserOptions* object);
+/**
+ * ffs_platform_file_chooser_options_get_create_folders
+ * @object: a #FfsPlatformFileChooserOptions.
+ *
+ * Whether to allow new folder creation.
+ *
+ * Nullable because it does not apply to the "open" action.
+ *
+ * Returns: the field value.
+ */
+gboolean* ffs_platform_file_chooser_options_get_create_folders(
+ FfsPlatformFileChooserOptions* object);
+
G_DECLARE_FINAL_TYPE(FfsMessageCodec, ffs_message_codec, FFS, MESSAGE_CODEC,
FlStandardMessageCodec)
diff --git a/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc b/packages/file_selector/file_selector_linux/linux/test/file_selector_plugin_test.cc
index 841cc8f..863ba18 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 @@
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 @@
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 @@
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, 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, TestSaveWithArguments) {
g_autoptr(FfsPlatformFileChooserOptions) options =
ffs_platform_file_chooser_options_new(nullptr, "/tmp", "foo.txt", nullptr,
- nullptr);
+ nullptr, nullptr);
g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type(
nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_SAVE,
@@ -184,10 +184,48 @@
// doesn't in a test context, so that's not currently validated.
}
+TEST(FileSelectorPlugin, TestSaveWithCreateFoldersEnabled) {
+ gboolean create_folders = true;
+ g_autoptr(FfsPlatformFileChooserOptions) options =
+ ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr,
+ nullptr, &create_folders);
+
+ g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type(
+ nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_SAVE,
+ options);
+
+ ASSERT_NE(dialog, nullptr);
+ EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)),
+ GTK_FILE_CHOOSER_ACTION_SAVE);
+ EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)),
+ false);
+ EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)),
+ create_folders);
+}
+
+TEST(FileSelectorPlugin, TestSaveWithCreateFoldersDisabled) {
+ gboolean create_folders = false;
+ g_autoptr(FfsPlatformFileChooserOptions) options =
+ ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr,
+ nullptr, &create_folders);
+
+ g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type(
+ nullptr, FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_SAVE,
+ options);
+
+ ASSERT_NE(dialog, nullptr);
+ EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)),
+ GTK_FILE_CHOOSER_ACTION_SAVE);
+ EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)),
+ false);
+ EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)),
+ create_folders);
+}
+
TEST(FileSelectorPlugin, TestGetDirectory) {
g_autoptr(FfsPlatformFileChooserOptions) options =
ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr,
- nullptr);
+ nullptr, nullptr);
g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type(
nullptr,
@@ -205,7 +243,7 @@
gboolean select_multiple = true;
g_autoptr(FfsPlatformFileChooserOptions) options =
ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr,
- &select_multiple);
+ &select_multiple, nullptr);
g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type(
nullptr,
@@ -219,6 +257,42 @@
true);
}
+TEST(FileSelectorPlugin, TestGetDirectoryWithCreateFoldersEnabled) {
+ gboolean create_folders = true;
+ g_autoptr(FfsPlatformFileChooserOptions) options =
+ ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr,
+ nullptr, &create_folders);
+
+ g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type(
+ nullptr,
+ FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_CHOOSE_DIRECTORY,
+ options);
+
+ ASSERT_NE(dialog, nullptr);
+ EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+ EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)),
+ create_folders);
+}
+
+TEST(FileSelectorPlugin, TestGetDirectoryWithCreateFoldersDisabled) {
+ gboolean create_folders = false;
+ g_autoptr(FfsPlatformFileChooserOptions) options =
+ ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr,
+ nullptr, &create_folders);
+
+ g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type(
+ nullptr,
+ FILE_SELECTOR_LINUX_PLATFORM_FILE_CHOOSER_ACTION_TYPE_CHOOSE_DIRECTORY,
+ options);
+
+ ASSERT_NE(dialog, nullptr);
+ EXPECT_EQ(gtk_file_chooser_get_action(GTK_FILE_CHOOSER(dialog)),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+ EXPECT_EQ(gtk_file_chooser_get_create_folders(GTK_FILE_CHOOSER(dialog)),
+ create_folders);
+}
+
static gint mock_run_dialog_cancel(GtkNativeDialog* dialog) {
return GTK_RESPONSE_CANCEL;
}
@@ -226,7 +300,7 @@
TEST(FileSelectorPlugin, TestGetDirectoryCancel) {
g_autoptr(FfsPlatformFileChooserOptions) options =
ffs_platform_file_chooser_options_new(nullptr, nullptr, nullptr, nullptr,
- nullptr);
+ nullptr, nullptr);
g_autoptr(GtkFileChooserNative) dialog = create_dialog_of_type(
nullptr,
diff --git a/packages/file_selector/file_selector_linux/pigeons/messages.dart b/packages/file_selector/file_selector_linux/pigeons/messages.dart
index 658450c..e700c6a 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 @@
required this.currentName,
required this.acceptButtonLabel,
this.selectMultiple,
+ this.createFolders,
});
final List<PlatformTypeGroup>? allowedFileTypes;
@@ -50,6 +51,11 @@
///
/// Nullable because it does not apply to the "save" action.
final bool? selectMultiple;
+
+ /// Whether to allow new folder creation.
+ ///
+ /// Nullable because it does not apply to the "open" action.
+ final bool? createFolders;
}
@HostApi()
diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml
index e6c03ec..810d378 100644
--- a/packages/file_selector/file_selector_linux/pubspec.yaml
+++ b/packages/file_selector/file_selector_linux/pubspec.yaml
@@ -2,7 +2,7 @@
description: Liunx implementation of the file_selector plugin.
repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_linux
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
-version: 0.9.3+3
+version: 0.9.4
environment:
sdk: ^3.8.0
@@ -18,7 +18,7 @@
dependencies:
cross_file: ^0.3.1
- file_selector_platform_interface: ^2.6.0
+ file_selector_platform_interface: ^2.7.0
flutter:
sdk: flutter
diff --git a/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart b/packages/file_selector/file_selector_linux/test/file_selector_linux_test.dart
index d6ddf3a..4f6786b 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 @@
});
});
+ group('getDirectoryPathWithOptions', () {
+ test('passes the core flags correctly', () async {
+ const String path = '/foo/bar';
+ api.result = <String>[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 = <String>['/foo/bar', 'baz'];
@@ -415,6 +454,52 @@
expect(api.passedOptions?.selectMultiple, true);
});
});
+
+ group('getDirectoryPathsWithOptions', () {
+ test('passes the core flags correctly', () async {
+ api.result = <String>['/foo/bar', 'baz'];
+
+ expect(
+ await plugin.getDirectoryPathsWithOptions(const FileDialogOptions()),
+ api.result,
+ );
+
+ expect(api.passedType, PlatformFileChooserActionType.chooseDirectory);
+ expect(api.passedOptions?.selectMultiple, true);
+ });
+
+ test('passes initialDirectory correctly', () async {
+ const String path = '/example/directory';
+ await plugin.getDirectoryPathsWithOptions(
+ const FileDialogOptions(initialDirectory: path),
+ );
+
+ expect(api.passedOptions?.currentFolderPath, path);
+ });
+
+ test('passes confirmButtonText correctly', () async {
+ const String button = 'Select one or mode folders';
+ await plugin.getDirectoryPathsWithOptions(
+ const FileDialogOptions(confirmButtonText: button),
+ );
+
+ expect(api.passedOptions?.acceptButtonLabel, button);
+ });
+
+ test('passes canCreateDirectories flag correctly', () async {
+ await plugin.getDirectoryPathsWithOptions(
+ const FileDialogOptions(canCreateDirectories: true),
+ );
+
+ expect(api.passedOptions?.createFolders, true);
+ });
+
+ test('passes multiple flag correctly', () async {
+ await plugin.getDirectoryPathsWithOptions(const FileDialogOptions());
+
+ expect(api.passedOptions?.selectMultiple, true);
+ });
+ });
}
/// Fake implementation that stores arguments and provides a canned response.
diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md
index c43a073..fe540f3 100644
--- a/packages/file_selector/file_selector_macos/CHANGELOG.md
+++ b/packages/file_selector/file_selector_macos/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.9.5
+
+* Adds `getDirectoryPathWithOptions` and `getDirectoryPathsWithOptions` implementations.
+
## 0.9.4+6
* Updates to Pigeon 26.
diff --git a/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart b/packages/file_selector/file_selector_macos/example/lib/get_directory_page.dart
index fbc8d26..edbd299 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 @@
Future<void> _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 be1bdfd..c0b32ac 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 @@
Future<void> _getDirectoryPaths(BuildContext context) async {
const String confirmButtonText = 'Choose';
final List<String?> directoriesPaths = await FileSelectorPlatform.instance
- .getDirectoryPaths(confirmButtonText: confirmButtonText);
+ .getDirectoryPathsWithOptions(
+ const FileDialogOptions(
+ confirmButtonText: confirmButtonText,
+ canCreateDirectories: true,
+ ),
+ );
if (directoriesPaths.isEmpty) {
// Operation was canceled by the user.
return;
diff --git a/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj b/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj
index ce516fb..6797fa2 100644
--- a/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj
+++ b/packages/file_selector/file_selector_macos/example/macos/Runner.xcodeproj/project.pbxproj
@@ -232,6 +232,7 @@
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
+ D670A6A16AFB08D3E6D21B41 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -379,6 +380,23 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
+ D670A6A16AFB08D3E6D21B41 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
diff --git a/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift
index 9ef419c..b680653 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 @@
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 @@
}
}
+ 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 @@
// 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 @@
// 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 @@
wait(for: [called])
XCTAssertNotNil(panelController.openPanel)
}
+
+ func testGetDirectoryNewFolderVisible() throws {
+ let panelController = TestPanelController()
+ let plugin = FileSelectorPlugin(
+ viewProvider: TestViewProvider(),
+ panelController: panelController)
+
+ let returnPath = "/foo/bar"
+ panelController.openURLs = [URL(fileURLWithPath: returnPath)]
+
+ let called = XCTestExpectation()
+ let options = OpenPanelOptions(
+ allowsMultipleSelection: false,
+ canChooseDirectories: true,
+ canChooseFiles: false,
+ baseOptions: SavePanelOptions(canCreateDirectories: true))
+
+ plugin.displayOpenPanel(options: options) { result in
+ switch result {
+ case .success(let paths):
+ XCTAssertEqual(paths[0], returnPath)
+ case .failure(let error):
+ XCTFail("\(error)")
+ }
+ called.fulfill()
+ }
+
+ wait(for: [called])
+ XCTAssertNotNil(panelController.openPanel)
+ if let panel = panelController.openPanel {
+ XCTAssertTrue(panel.canCreateDirectories)
+ }
+ }
}
diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml
index 7925022..5c54c86 100644
--- a/packages/file_selector/file_selector_macos/example/pubspec.yaml
+++ b/packages/file_selector/file_selector_macos/example/pubspec.yaml
@@ -15,7 +15,7 @@
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ..
- file_selector_platform_interface: ^2.6.0
+ file_selector_platform_interface: ^2.7.0
flutter:
sdk: flutter
diff --git a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart
index 9b18cc8..dbcbd34 100644
--- a/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart
+++ b/packages/file_selector/file_selector_macos/lib/file_selector_macos.dart
@@ -91,6 +91,7 @@
directoryPath: options.initialDirectory,
nameFieldStringValue: options.suggestedName,
prompt: options.confirmButtonText,
+ canCreateDirectories: options.canCreateDirectories,
),
);
return path == null ? null : FileSaveLocation(path);
@@ -101,14 +102,25 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
+ return getDirectoryPathWithOptions(
+ FileDialogOptions(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ ),
+ );
+ }
+
+ @override
+ Future<String?> getDirectoryPathWithOptions(FileDialogOptions options) async {
final List<String?> paths = await _hostApi.displayOpenPanel(
OpenPanelOptions(
allowsMultipleSelection: false,
canChooseDirectories: true,
canChooseFiles: false,
baseOptions: SavePanelOptions(
- directoryPath: initialDirectory,
- prompt: confirmButtonText,
+ directoryPath: options.initialDirectory,
+ prompt: options.confirmButtonText,
+ canCreateDirectories: options.canCreateDirectories,
),
),
);
@@ -120,14 +132,27 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
+ return getDirectoryPathsWithOptions(
+ FileDialogOptions(
+ initialDirectory: initialDirectory,
+ confirmButtonText: confirmButtonText,
+ ),
+ );
+ }
+
+ @override
+ Future<List<String>> getDirectoryPathsWithOptions(
+ FileDialogOptions options,
+ ) async {
final List<String?> 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 4fac7a0..f91bc53 100644
--- a/packages/file_selector/file_selector_macos/lib/src/messages.g.dart
+++ b/packages/file_selector/file_selector_macos/lib/src/messages.g.dart
@@ -94,6 +94,7 @@
this.directoryPath,
this.nameFieldStringValue,
this.prompt,
+ this.canCreateDirectories,
});
AllowedTypes? allowedFileTypes;
@@ -104,12 +105,15 @@
String? prompt;
+ bool? canCreateDirectories;
+
List<Object?> _toList() {
return <Object?>[
allowedFileTypes,
directoryPath,
nameFieldStringValue,
prompt,
+ canCreateDirectories,
];
}
@@ -124,6 +128,7 @@
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 d35266d..9fbee44 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 @@
}
}
}
+
+ 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 aab8417..d655618 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 @@
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 @@
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 @@
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 8da0ef9..e65780f 100644
--- a/packages/file_selector/file_selector_macos/pigeons/messages.dart
+++ b/packages/file_selector/file_selector_macos/pigeons/messages.dart
@@ -35,11 +35,13 @@
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 82d97e2..ea8afde 100644
--- a/packages/file_selector/file_selector_macos/pubspec.yaml
+++ b/packages/file_selector/file_selector_macos/pubspec.yaml
@@ -2,7 +2,7 @@
description: macOS implementation of the file_selector plugin.
repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_macos
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
-version: 0.9.4+6
+version: 0.9.5
environment:
sdk: ^3.9.0
@@ -18,7 +18,7 @@
dependencies:
cross_file: ^0.3.1
- file_selector_platform_interface: ^2.6.0
+ file_selector_platform_interface: ^2.7.0
flutter:
sdk: flutter
diff --git a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart
index edd4c2e..1a8d3bf 100644
--- a/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart
+++ b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart
@@ -485,6 +485,62 @@
});
});
+ group('getDirectoryPathWithOptions', () {
+ test('works as expected with no arguments', () async {
+ api.result = <String>['foo'];
+
+ final String? path = await plugin.getDirectoryPathWithOptions(
+ const FileDialogOptions(),
+ );
+
+ expect(path, 'foo');
+ final OpenPanelOptions options = api.passedOpenPanelOptions!;
+ expect(options.allowsMultipleSelection, false);
+ expect(options.canChooseFiles, false);
+ expect(options.canChooseDirectories, true);
+ expect(options.baseOptions.allowedFileTypes, null);
+ expect(options.baseOptions.directoryPath, null);
+ expect(options.baseOptions.nameFieldStringValue, null);
+ expect(options.baseOptions.canCreateDirectories, null);
+ expect(options.baseOptions.prompt, null);
+ });
+
+ test('handles cancel', () async {
+ api.result = <String>[];
+
+ final String? path = await plugin.getDirectoryPath();
+
+ expect(path, null);
+ });
+
+ test('passes initialDirectory correctly', () async {
+ await plugin.getDirectoryPathWithOptions(
+ const FileDialogOptions(initialDirectory: '/example/directory'),
+ );
+
+ final OpenPanelOptions options = api.passedOpenPanelOptions!;
+ expect(options.baseOptions.directoryPath, '/example/directory');
+ });
+
+ test('passes confirmButtonText correctly', () async {
+ await plugin.getDirectoryPathWithOptions(
+ const FileDialogOptions(confirmButtonText: 'Open File'),
+ );
+
+ final OpenPanelOptions options = api.passedOpenPanelOptions!;
+ expect(options.baseOptions.prompt, 'Open File');
+ });
+
+ test('passes canCreateDirectories correctly', () async {
+ await plugin.getDirectoryPathWithOptions(
+ const FileDialogOptions(canCreateDirectories: false),
+ );
+
+ final OpenPanelOptions options = api.passedOpenPanelOptions!;
+ expect(options.baseOptions.canCreateDirectories, false);
+ });
+ });
+
group('getDirectoryPaths', () {
test('works as expected with no arguments', () async {
api.result = <String>[
@@ -532,6 +588,72 @@
expect(options.baseOptions.directoryPath, '/example/directory');
});
});
+
+ group('getDirectoryPathsWithOptions', () {
+ test('works as expected with no arguments', () async {
+ api.result = <String>[
+ 'firstDirectory',
+ 'secondDirectory',
+ 'thirdDirectory',
+ ];
+
+ final List<String> path = await plugin.getDirectoryPathsWithOptions(
+ const FileDialogOptions(),
+ );
+
+ expect(path, <String>[
+ 'firstDirectory',
+ 'secondDirectory',
+ 'thirdDirectory',
+ ]);
+ final OpenPanelOptions options = api.passedOpenPanelOptions!;
+ expect(options.allowsMultipleSelection, true);
+ expect(options.canChooseFiles, false);
+ expect(options.canChooseDirectories, true);
+ expect(options.baseOptions.allowedFileTypes, null);
+ expect(options.baseOptions.directoryPath, null);
+ expect(options.baseOptions.nameFieldStringValue, null);
+ expect(options.baseOptions.canCreateDirectories, null);
+ expect(options.baseOptions.prompt, null);
+ });
+
+ test('handles cancel', () async {
+ api.result = <String>[];
+
+ final List<String> paths = await plugin.getDirectoryPathsWithOptions(
+ const FileDialogOptions(),
+ );
+
+ expect(paths, <String>[]);
+ });
+
+ test('passes confirmButtonText correctly', () async {
+ await plugin.getDirectoryPathsWithOptions(
+ const FileDialogOptions(confirmButtonText: 'Select directories'),
+ );
+
+ final OpenPanelOptions options = api.passedOpenPanelOptions!;
+ expect(options.baseOptions.prompt, 'Select directories');
+ });
+
+ test('passes initialDirectory correctly', () async {
+ await plugin.getDirectoryPathsWithOptions(
+ const FileDialogOptions(initialDirectory: '/example/directory'),
+ );
+
+ final OpenPanelOptions options = api.passedOpenPanelOptions!;
+ expect(options.baseOptions.directoryPath, '/example/directory');
+ });
+
+ test('passes canCreateDirectories correctly', () async {
+ await plugin.getDirectoryPathsWithOptions(
+ const FileDialogOptions(canCreateDirectories: false),
+ );
+
+ final OpenPanelOptions options = api.passedOpenPanelOptions!;
+ expect(options.baseOptions.canCreateDirectories, false);
+ });
+ });
}
/// Fake implementation that stores arguments and provides a canned response.