[file_selector] add getDirectoryPaths implementation on Linux (#6573)
* Add getDirectoriesPaths method to the file_selector_platform_interface
Add getDirectoriesPaths to method channel.
Increment version to 2.3.0
apply feedback
extract assertion method
* add getDirectoryPaths Linux implementation
* apply rebase
* update version to 0.9.1
Co-authored-by: eugerossetto <eugenio.rossetto@southworks.com>
diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md
index a1f57b5..70ce307 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.1
+
+* Adds `getDirectoryPaths` implementation.
+
## 0.9.0+1
* Changes XTypeGroup initialization from final to const.
diff --git a/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart b/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart
new file mode 100644
index 0000000..66ab29c
--- /dev/null
+++ b/packages/file_selector/file_selector_linux/example/lib/get_multiple_directories_page.dart
@@ -0,0 +1,84 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
+import 'package:flutter/material.dart';
+
+/// Screen that allows the user to select one or more directories using `getDirectoryPaths`,
+/// then displays the selected directories in a dialog.
+class GetMultipleDirectoriesPage extends StatelessWidget {
+ /// Default Constructor
+ const GetMultipleDirectoriesPage({Key? key}) : super(key: key);
+
+ Future<void> _getDirectoryPaths(BuildContext context) async {
+ const String confirmButtonText = 'Choose';
+ final List<String> directoryPaths =
+ await FileSelectorPlatform.instance.getDirectoryPaths(
+ confirmButtonText: confirmButtonText,
+ );
+ if (directoryPaths.isEmpty) {
+ // Operation was canceled by the user.
+ return;
+ }
+ await showDialog<void>(
+ context: context,
+ builder: (BuildContext context) => TextDisplay(directoryPaths.join('\n')),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Select multiple directories'),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ // TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724
+ // ignore: deprecated_member_use
+ primary: Colors.blue,
+ // ignore: deprecated_member_use
+ onPrimary: Colors.white,
+ ),
+ child: const Text(
+ 'Press to ask user to choose multiple directories'),
+ onPressed: () => _getDirectoryPaths(context),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+/// Widget that displays a text file in a dialog.
+class TextDisplay extends StatelessWidget {
+ /// Creates a `TextDisplay`.
+ const TextDisplay(this.directoriesPaths, {Key? key}) : super(key: key);
+
+ /// The path selected in the dialog.
+ final String directoriesPaths;
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: const Text('Selected Directories'),
+ content: Scrollbar(
+ child: SingleChildScrollView(
+ child: Text(directoriesPaths),
+ ),
+ ),
+ actions: <Widget>[
+ TextButton(
+ child: const Text('Close'),
+ onPressed: () => Navigator.pop(context),
+ ),
+ ],
+ );
+ }
+}
diff --git a/packages/file_selector/file_selector_linux/example/lib/home_page.dart b/packages/file_selector/file_selector_linux/example/lib/home_page.dart
index a4b2ae1..80e1633 100644
--- a/packages/file_selector/file_selector_linux/example/lib/home_page.dart
+++ b/packages/file_selector/file_selector_linux/example/lib/home_page.dart
@@ -55,6 +55,13 @@
child: const Text('Open a get directory dialog'),
onPressed: () => Navigator.pushNamed(context, '/directory'),
),
+ const SizedBox(height: 10),
+ ElevatedButton(
+ style: style,
+ child: const Text('Open a get directories dialog'),
+ onPressed: () =>
+ Navigator.pushNamed(context, '/multi-directories'),
+ ),
],
),
),
diff --git a/packages/file_selector/file_selector_linux/example/lib/main.dart b/packages/file_selector/file_selector_linux/example/lib/main.dart
index 3e44710..b8f0476 100644
--- a/packages/file_selector/file_selector_linux/example/lib/main.dart
+++ b/packages/file_selector/file_selector_linux/example/lib/main.dart
@@ -5,6 +5,7 @@
import 'package:flutter/material.dart';
import 'get_directory_page.dart';
+import 'get_multiple_directories_page.dart';
import 'home_page.dart';
import 'open_image_page.dart';
import 'open_multiple_images_page.dart';
@@ -36,6 +37,8 @@
'/open/text': (BuildContext context) => const OpenTextPage(),
'/save/text': (BuildContext context) => SaveTextPage(),
'/directory': (BuildContext context) => const GetDirectoryPage(),
+ '/multi-directories': (BuildContext context) =>
+ const GetMultipleDirectoriesPage()
},
);
}
diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml
index 51bdb28..912a082 100644
--- a/packages/file_selector/file_selector_linux/example/pubspec.yaml
+++ b/packages/file_selector/file_selector_linux/example/pubspec.yaml
@@ -1,6 +1,6 @@
name: file_selector_linux_example
description: Local testbed for Linux file_selector implementation.
-publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+publish_to: 'none'
version: 1.0.0+1
environment:
@@ -9,7 +9,7 @@
dependencies:
file_selector_linux:
path: ../
- file_selector_platform_interface: ^2.2.0
+ file_selector_platform_interface: ^2.4.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 430b41c..b8e3df6 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
@@ -102,13 +102,26 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
- return _channel.invokeMethod<String>(
- _getDirectoryPathMethod,
- <String, dynamic>{
- _initialDirectoryKey: initialDirectory,
- _confirmButtonTextKey: confirmButtonText,
- },
- );
+ final List<String>? path = await _channel
+ .invokeListMethod<String>(_getDirectoryPathMethod, <String, dynamic>{
+ _initialDirectoryKey: initialDirectory,
+ _confirmButtonTextKey: confirmButtonText,
+ });
+ return path?.first;
+ }
+
+ @override
+ Future<List<String>> getDirectoryPaths({
+ String? initialDirectory,
+ String? confirmButtonText,
+ }) async {
+ final List<String>? pathList = await _channel
+ .invokeListMethod<String>(_getDirectoryPathMethod, <String, dynamic>{
+ _initialDirectoryKey: initialDirectory,
+ _confirmButtonTextKey: confirmButtonText,
+ _multipleKey: true,
+ });
+ return pathList ?? <String>[];
}
}
diff --git a/packages/file_selector/file_selector_linux/linux/.gitignore b/packages/file_selector/file_selector_linux/linux/.gitignore
new file mode 100644
index 0000000..83fee18
--- /dev/null
+++ b/packages/file_selector/file_selector_linux/linux/.gitignore
@@ -0,0 +1,2 @@
+CMakeCache.txt
+CMakeFiles/
\ No newline at end of file
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 8337719..5a8cc21 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
@@ -192,10 +192,10 @@
FlValue* args = fl_method_call_get_args(method_call);
g_autoptr(FlMethodResponse) response = nullptr;
- if (strcmp(method, kOpenFileMethod) == 0) {
+ if (strcmp(method, kOpenFileMethod) == 0 ||
+ strcmp(method, kGetDirectoryPathMethod) == 0) {
response = show_dialog(self, method, args, true);
- } else if (strcmp(method, kGetDirectoryPathMethod) == 0 ||
- strcmp(method, kGetSavePathMethod) == 0) {
+ } else if (strcmp(method, kGetSavePathMethod) == 0) {
response = show_dialog(self, method, args, false);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
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 84c55ac..8762b4a 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
@@ -169,3 +169,17 @@
EXPECT_EQ(gtk_file_chooser_get_select_multiple(GTK_FILE_CHOOSER(dialog)),
false);
}
+
+TEST(FileSelectorPlugin, TestGetMultipleDirectories) {
+ g_autoptr(FlValue) args = fl_value_new_map();
+ fl_value_set_string_take(args, "multiple", fl_value_new_bool(true));
+
+ g_autoptr(GtkFileChooserNative) dialog =
+ create_dialog_for_method(nullptr, "getDirectoryPath", args);
+
+ 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_select_multiple(GTK_FILE_CHOOSER(dialog)),
+ true);
+}
diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml
index a8aea37..5aff949 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/plugins/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.0+1
+version: 0.9.1
environment:
sdk: ">=2.12.0 <3.0.0"
@@ -18,7 +18,7 @@
dependencies:
cross_file: ^0.3.1
- file_selector_platform_interface: ^2.2.0
+ file_selector_platform_interface: ^2.4.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 748f922..4eae078 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
@@ -46,57 +46,54 @@
await plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'acceptedTypeGroups': <Map<String, dynamic>>[
- <String, Object>{
- 'label': 'text',
- 'extensions': <String>['*.txt'],
- 'mimeTypes': <String>['text/plain'],
- },
- <String, Object>{
- 'label': 'image',
- 'extensions': <String>['*.jpg'],
- 'mimeTypes': <String>['image/jpg'],
- },
- ],
- 'initialDirectory': null,
- 'confirmButtonText': null,
- 'multiple': false,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'acceptedTypeGroups': <Map<String, dynamic>>[
+ <String, Object>{
+ 'label': 'text',
+ 'extensions': <String>['*.txt'],
+ 'mimeTypes': <String>['text/plain'],
+ },
+ <String, Object>{
+ 'label': 'image',
+ 'extensions': <String>['*.jpg'],
+ 'mimeTypes': <String>['image/jpg'],
+ },
+ ],
+ 'initialDirectory': null,
+ 'confirmButtonText': null,
+ 'multiple': false,
+ },
);
});
test('passes initialDirectory correctly', () async {
await plugin.openFile(initialDirectory: '/example/directory');
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'initialDirectory': '/example/directory',
- 'confirmButtonText': null,
- 'multiple': false,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'initialDirectory': '/example/directory',
+ 'confirmButtonText': null,
+ 'multiple': false,
+ },
);
});
test('passes confirmButtonText correctly', () async {
await plugin.openFile(confirmButtonText: 'Open File');
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'initialDirectory': null,
- 'confirmButtonText': 'Open File',
- 'multiple': false,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'initialDirectory': null,
+ 'confirmButtonText': 'Open File',
+ 'multiple': false,
+ },
);
});
@@ -118,21 +115,20 @@
await plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]);
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'acceptedTypeGroups': <Map<String, dynamic>>[
- <String, Object>{
- 'label': 'any',
- 'extensions': <String>['*'],
- },
- ],
- 'initialDirectory': null,
- 'confirmButtonText': null,
- 'multiple': false,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'acceptedTypeGroups': <Map<String, dynamic>>[
+ <String, Object>{
+ 'label': 'any',
+ 'extensions': <String>['*'],
+ },
+ ],
+ 'initialDirectory': null,
+ 'confirmButtonText': null,
+ 'multiple': false,
+ },
);
});
});
@@ -156,57 +152,54 @@
await plugin.openFiles(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'acceptedTypeGroups': <Map<String, dynamic>>[
- <String, Object>{
- 'label': 'text',
- 'extensions': <String>['*.txt'],
- 'mimeTypes': <String>['text/plain'],
- },
- <String, Object>{
- 'label': 'image',
- 'extensions': <String>['*.jpg'],
- 'mimeTypes': <String>['image/jpg'],
- },
- ],
- 'initialDirectory': null,
- 'confirmButtonText': null,
- 'multiple': true,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'acceptedTypeGroups': <Map<String, dynamic>>[
+ <String, Object>{
+ 'label': 'text',
+ 'extensions': <String>['*.txt'],
+ 'mimeTypes': <String>['text/plain'],
+ },
+ <String, Object>{
+ 'label': 'image',
+ 'extensions': <String>['*.jpg'],
+ 'mimeTypes': <String>['image/jpg'],
+ },
+ ],
+ 'initialDirectory': null,
+ 'confirmButtonText': null,
+ 'multiple': true,
+ },
);
});
test('passes initialDirectory correctly', () async {
await plugin.openFiles(initialDirectory: '/example/directory');
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'initialDirectory': '/example/directory',
- 'confirmButtonText': null,
- 'multiple': true,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'initialDirectory': '/example/directory',
+ 'confirmButtonText': null,
+ 'multiple': true,
+ },
);
});
test('passes confirmButtonText correctly', () async {
await plugin.openFiles(confirmButtonText: 'Open File');
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'initialDirectory': null,
- 'confirmButtonText': 'Open File',
- 'multiple': true,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'initialDirectory': null,
+ 'confirmButtonText': 'Open File',
+ 'multiple': true,
+ },
);
});
@@ -228,21 +221,20 @@
await plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]);
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'acceptedTypeGroups': <Map<String, dynamic>>[
- <String, Object>{
- 'label': 'any',
- 'extensions': <String>['*'],
- },
- ],
- 'initialDirectory': null,
- 'confirmButtonText': null,
- 'multiple': false,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'acceptedTypeGroups': <Map<String, dynamic>>[
+ <String, Object>{
+ 'label': 'any',
+ 'extensions': <String>['*'],
+ },
+ ],
+ 'initialDirectory': null,
+ 'confirmButtonText': null,
+ 'multiple': false,
+ },
);
});
});
@@ -267,57 +259,54 @@
await plugin
.getSavePath(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('getSavePath', arguments: <String, dynamic>{
- 'acceptedTypeGroups': <Map<String, dynamic>>[
- <String, Object>{
- 'label': 'text',
- 'extensions': <String>['*.txt'],
- 'mimeTypes': <String>['text/plain'],
- },
- <String, Object>{
- 'label': 'image',
- 'extensions': <String>['*.jpg'],
- 'mimeTypes': <String>['image/jpg'],
- },
- ],
- 'initialDirectory': null,
- 'suggestedName': null,
- 'confirmButtonText': null,
- }),
- ],
+ 'getSavePath',
+ arguments: <String, dynamic>{
+ 'acceptedTypeGroups': <Map<String, dynamic>>[
+ <String, Object>{
+ 'label': 'text',
+ 'extensions': <String>['*.txt'],
+ 'mimeTypes': <String>['text/plain'],
+ },
+ <String, Object>{
+ 'label': 'image',
+ 'extensions': <String>['*.jpg'],
+ 'mimeTypes': <String>['image/jpg'],
+ },
+ ],
+ 'initialDirectory': null,
+ 'suggestedName': null,
+ 'confirmButtonText': null,
+ },
);
});
test('passes initialDirectory correctly', () async {
await plugin.getSavePath(initialDirectory: '/example/directory');
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('getSavePath', arguments: <String, dynamic>{
- 'initialDirectory': '/example/directory',
- 'suggestedName': null,
- 'confirmButtonText': null,
- }),
- ],
+ 'getSavePath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': '/example/directory',
+ 'suggestedName': null,
+ 'confirmButtonText': null,
+ },
);
});
test('passes confirmButtonText correctly', () async {
await plugin.getSavePath(confirmButtonText: 'Open File');
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('getSavePath', arguments: <String, dynamic>{
- 'initialDirectory': null,
- 'suggestedName': null,
- 'confirmButtonText': 'Open File',
- }),
- ],
+ 'getSavePath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': null,
+ 'suggestedName': null,
+ 'confirmButtonText': 'Open File',
+ },
);
});
@@ -339,21 +328,20 @@
await plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]);
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('openFile', arguments: <String, dynamic>{
- 'acceptedTypeGroups': <Map<String, dynamic>>[
- <String, Object>{
- 'label': 'any',
- 'extensions': <String>['*'],
- },
- ],
- 'initialDirectory': null,
- 'confirmButtonText': null,
- 'multiple': false,
- }),
- ],
+ 'openFile',
+ arguments: <String, dynamic>{
+ 'acceptedTypeGroups': <Map<String, dynamic>>[
+ <String, Object>{
+ 'label': 'any',
+ 'extensions': <String>['*'],
+ },
+ ],
+ 'initialDirectory': null,
+ 'confirmButtonText': null,
+ 'multiple': false,
+ },
);
});
});
@@ -362,28 +350,77 @@
test('passes initialDirectory correctly', () async {
await plugin.getDirectoryPath(initialDirectory: '/example/directory');
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('getDirectoryPath', arguments: <String, dynamic>{
- 'initialDirectory': '/example/directory',
- 'confirmButtonText': null,
- }),
- ],
+ 'getDirectoryPath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': '/example/directory',
+ 'confirmButtonText': null,
+ },
);
});
test('passes confirmButtonText correctly', () async {
- await plugin.getDirectoryPath(confirmButtonText: 'Open File');
+ await plugin.getDirectoryPath(confirmButtonText: 'Select Folder');
- expect(
+ expectMethodCall(
log,
- <Matcher>[
- isMethodCall('getDirectoryPath', arguments: <String, dynamic>{
- 'initialDirectory': null,
- 'confirmButtonText': 'Open File',
- }),
- ],
+ 'getDirectoryPath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': null,
+ 'confirmButtonText': 'Select Folder',
+ },
);
});
});
+
+ group('#getDirectoryPaths', () {
+ test('passes initialDirectory correctly', () async {
+ await plugin.getDirectoryPaths(initialDirectory: '/example/directory');
+
+ expectMethodCall(
+ log,
+ 'getDirectoryPath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': '/example/directory',
+ 'confirmButtonText': null,
+ 'multiple': true,
+ },
+ );
+ });
+ test('passes confirmButtonText correctly', () async {
+ await plugin.getDirectoryPaths(
+ confirmButtonText: 'Select one or mode folders');
+
+ expectMethodCall(
+ log,
+ 'getDirectoryPath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': null,
+ 'confirmButtonText': 'Select one or mode folders',
+ 'multiple': true,
+ },
+ );
+ });
+ test('passes multiple flag correctly', () async {
+ await plugin.getDirectoryPaths();
+
+ expectMethodCall(
+ log,
+ 'getDirectoryPath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': null,
+ 'confirmButtonText': null,
+ 'multiple': true,
+ },
+ );
+ });
+ });
+}
+
+void expectMethodCall(
+ List<MethodCall> log,
+ String methodName, {
+ Map<String, dynamic>? arguments,
+}) {
+ expect(log, <Matcher>[isMethodCall(methodName, arguments: arguments)]);
}