Skip to content

Commit 3ee170b

Browse files
[file_selector] Add getDirectoryPaths (flutter#3871)
Exposes the new `getDirectoryPaths` API, and updates the desktop implementation package constraints to ensure that the implementations are present. This is a slightly updated recreation of flutter/plugins#6576 Fixes flutter#74323
1 parent 4e5620b commit 3ee170b

File tree

7 files changed

+206
-21
lines changed

7 files changed

+206
-21
lines changed

packages/file_selector/file_selector/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.9.3
2+
3+
* Adds `getDirectoryPaths` for selecting multiple directories.
4+
15
## 0.9.2+5
26

37
* Updates references to the deprecated `macUTIs`.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:file_selector/file_selector.dart';
6+
import 'package:flutter/material.dart';
7+
8+
/// Screen that allows the user to select one or more directories using `getDirectoryPaths`,
9+
/// then displays the selected directories in a dialog.
10+
class GetMultipleDirectoriesPage extends StatelessWidget {
11+
/// Returns a new instance of the page.
12+
const GetMultipleDirectoriesPage({super.key});
13+
14+
Future<void> _getDirectoryPaths(BuildContext context) async {
15+
const String confirmButtonText = 'Choose';
16+
final List<String?> directoryPaths = await getDirectoryPaths(
17+
confirmButtonText: confirmButtonText,
18+
);
19+
if (directoryPaths.isEmpty) {
20+
// Operation was canceled by the user.
21+
return;
22+
}
23+
String paths = '';
24+
for (final String? path in directoryPaths) {
25+
paths += '${path!} \n';
26+
}
27+
if (context.mounted) {
28+
await showDialog<void>(
29+
context: context,
30+
builder: (BuildContext context) => TextDisplay(paths),
31+
);
32+
}
33+
}
34+
35+
@override
36+
Widget build(BuildContext context) {
37+
return Scaffold(
38+
appBar: AppBar(
39+
title: const Text('Select multiple directories'),
40+
),
41+
body: Center(
42+
child: Column(
43+
mainAxisAlignment: MainAxisAlignment.center,
44+
children: <Widget>[
45+
ElevatedButton(
46+
style: ElevatedButton.styleFrom(
47+
// TODO(darrenaustin): Migrate to new API once it lands in stable: https://github.com/flutter/flutter/issues/105724
48+
// ignore: deprecated_member_use
49+
primary: Colors.blue,
50+
// ignore: deprecated_member_use
51+
onPrimary: Colors.white,
52+
),
53+
child: const Text(
54+
'Press to ask user to choose multiple directories'),
55+
onPressed: () => _getDirectoryPaths(context),
56+
),
57+
],
58+
),
59+
),
60+
);
61+
}
62+
}
63+
64+
/// Widget that displays a text file in a dialog.
65+
class TextDisplay extends StatelessWidget {
66+
/// Creates a `TextDisplay`.
67+
const TextDisplay(this.directoriesPaths, {super.key});
68+
69+
/// The path selected in the dialog.
70+
final String directoriesPaths;
71+
72+
@override
73+
Widget build(BuildContext context) {
74+
return AlertDialog(
75+
title: const Text('Selected Directories'),
76+
content: Scrollbar(
77+
child: SingleChildScrollView(
78+
child: Text(directoriesPaths),
79+
),
80+
),
81+
actions: <Widget>[
82+
TextButton(
83+
child: const Text('Close'),
84+
onPressed: () => Navigator.pop(context),
85+
),
86+
],
87+
);
88+
}
89+
}

packages/file_selector/file_selector/example/lib/home_page.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ class HomePage extends StatelessWidget {
5555
child: const Text('Open a get directory dialog'),
5656
onPressed: () => Navigator.pushNamed(context, '/directory'),
5757
),
58+
const SizedBox(height: 10),
59+
ElevatedButton(
60+
style: style,
61+
child: const Text('Open a get multi directories dialog'),
62+
onPressed: () =>
63+
Navigator.pushNamed(context, '/multi-directories'),
64+
),
5865
],
5966
),
6067
),

packages/file_selector/file_selector/example/lib/main.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:flutter/material.dart';
66

77
import 'get_directory_page.dart';
8+
import 'get_multiple_directories_page.dart';
89
import 'home_page.dart';
910
import 'open_image_page.dart';
1011
import 'open_multiple_images_page.dart';
@@ -36,6 +37,8 @@ class MyApp extends StatelessWidget {
3637
'/open/text': (BuildContext context) => const OpenTextPage(),
3738
'/save/text': (BuildContext context) => SaveTextPage(),
3839
'/directory': (BuildContext context) => GetDirectoryPage(),
40+
'/multi-directories': (BuildContext context) =>
41+
const GetMultipleDirectoriesPage()
3942
},
4043
);
4144
}

packages/file_selector/file_selector/lib/file_selector.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Future<String?> getSavePath({
106106
}
107107

108108
/// Opens a directory selection dialog and returns the path chosen by the user.
109+
///
109110
/// This always returns `null` on the web.
110111
///
111112
/// [initialDirectory] is the full path to the directory that will be displayed
@@ -123,3 +124,24 @@ Future<String?> getDirectoryPath({
123124
return FileSelectorPlatform.instance.getDirectoryPath(
124125
initialDirectory: initialDirectory, confirmButtonText: confirmButtonText);
125126
}
127+
128+
/// Opens a directory selection dialog and returns a list of the paths chosen
129+
/// by the user.
130+
///
131+
/// This always returns an empty array on the web.
132+
///
133+
/// [initialDirectory] is the full path to the directory that will be displayed
134+
/// when the dialog is opened. When not provided, the platform will pick an
135+
/// initial location.
136+
///
137+
/// [confirmButtonText] is the text in the confirmation button of the dialog.
138+
/// When not provided, the default OS label is used (for example, "Open").
139+
///
140+
/// Returns an empty array if the user cancels the operation.
141+
Future<List<String?>> getDirectoryPaths({
142+
String? initialDirectory,
143+
String? confirmButtonText,
144+
}) async {
145+
return FileSelectorPlatform.instance.getDirectoryPaths(
146+
initialDirectory: initialDirectory, confirmButtonText: confirmButtonText);
147+
}

packages/file_selector/file_selector/pubspec.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ description: Flutter plugin for opening and saving files, or selecting
33
directories, using native file selection UI.
44
repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector
55
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
6-
version: 0.9.2+5
6+
version: 0.9.3
77

88
environment:
9-
sdk: ">=2.17.0 <4.0.0"
10-
flutter: ">=3.0.0"
9+
sdk: ">=2.18.0 <4.0.0"
10+
flutter: ">=3.3.0"
1111

1212
flutter:
1313
plugin:
@@ -25,11 +25,11 @@ flutter:
2525

2626
dependencies:
2727
file_selector_ios: ^0.5.0
28-
file_selector_linux: ^0.9.0
29-
file_selector_macos: ^0.9.0
28+
file_selector_linux: ^0.9.1
29+
file_selector_macos: ^0.9.1
3030
file_selector_platform_interface: ^2.3.0
3131
file_selector_web: ^0.9.0
32-
file_selector_windows: ^0.9.0
32+
file_selector_windows: ^0.9.2
3333
flutter:
3434
sdk: flutter
3535

packages/file_selector/file_selector/test/file_selector_test.dart

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ void main() {
154154
confirmButtonText: confirmButtonText,
155155
acceptedTypeGroups: acceptedTypeGroups,
156156
suggestedName: suggestedName)
157-
..setPathResponse(expectedSavePath);
157+
..setPathsResponse(<String>[expectedSavePath]);
158158

159159
final String? savePath = await getSavePath(
160160
initialDirectory: initialDirectory,
@@ -167,7 +167,7 @@ void main() {
167167
});
168168

169169
test('works with no arguments', () async {
170-
fakePlatformImplementation.setPathResponse(expectedSavePath);
170+
fakePlatformImplementation.setPathsResponse(<String>[expectedSavePath]);
171171

172172
final String? savePath = await getSavePath();
173173
expect(savePath, expectedSavePath);
@@ -176,7 +176,7 @@ void main() {
176176
test('sets the initial directory', () async {
177177
fakePlatformImplementation
178178
..setExpectations(initialDirectory: initialDirectory)
179-
..setPathResponse(expectedSavePath);
179+
..setPathsResponse(<String>[expectedSavePath]);
180180

181181
final String? savePath =
182182
await getSavePath(initialDirectory: initialDirectory);
@@ -186,7 +186,7 @@ void main() {
186186
test('sets the button confirmation label', () async {
187187
fakePlatformImplementation
188188
..setExpectations(confirmButtonText: confirmButtonText)
189-
..setPathResponse(expectedSavePath);
189+
..setPathsResponse(<String>[expectedSavePath]);
190190

191191
final String? savePath =
192192
await getSavePath(confirmButtonText: confirmButtonText);
@@ -196,7 +196,7 @@ void main() {
196196
test('sets the accepted type groups', () async {
197197
fakePlatformImplementation
198198
..setExpectations(acceptedTypeGroups: acceptedTypeGroups)
199-
..setPathResponse(expectedSavePath);
199+
..setPathsResponse(<String>[expectedSavePath]);
200200

201201
final String? savePath =
202202
await getSavePath(acceptedTypeGroups: acceptedTypeGroups);
@@ -206,7 +206,7 @@ void main() {
206206
test('sets the suggested name', () async {
207207
fakePlatformImplementation
208208
..setExpectations(suggestedName: suggestedName)
209-
..setPathResponse(expectedSavePath);
209+
..setPathsResponse(<String>[expectedSavePath]);
210210

211211
final String? savePath = await getSavePath(suggestedName: suggestedName);
212212
expect(savePath, expectedSavePath);
@@ -221,7 +221,7 @@ void main() {
221221
..setExpectations(
222222
initialDirectory: initialDirectory,
223223
confirmButtonText: confirmButtonText)
224-
..setPathResponse(expectedDirectoryPath);
224+
..setPathsResponse(<String>[expectedDirectoryPath]);
225225

226226
final String? directoryPath = await getDirectoryPath(
227227
initialDirectory: initialDirectory,
@@ -232,7 +232,8 @@ void main() {
232232
});
233233

234234
test('works with no arguments', () async {
235-
fakePlatformImplementation.setPathResponse(expectedDirectoryPath);
235+
fakePlatformImplementation
236+
.setPathsResponse(<String>[expectedDirectoryPath]);
236237

237238
final String? directoryPath = await getDirectoryPath();
238239
expect(directoryPath, expectedDirectoryPath);
@@ -241,7 +242,7 @@ void main() {
241242
test('sets the initial directory', () async {
242243
fakePlatformImplementation
243244
..setExpectations(initialDirectory: initialDirectory)
244-
..setPathResponse(expectedDirectoryPath);
245+
..setPathsResponse(<String>[expectedDirectoryPath]);
245246

246247
final String? directoryPath =
247248
await getDirectoryPath(initialDirectory: initialDirectory);
@@ -251,13 +252,62 @@ void main() {
251252
test('sets the button confirmation label', () async {
252253
fakePlatformImplementation
253254
..setExpectations(confirmButtonText: confirmButtonText)
254-
..setPathResponse(expectedDirectoryPath);
255+
..setPathsResponse(<String>[expectedDirectoryPath]);
255256

256257
final String? directoryPath =
257258
await getDirectoryPath(confirmButtonText: confirmButtonText);
258259
expect(directoryPath, expectedDirectoryPath);
259260
});
260261
});
262+
263+
group('getDirectoryPaths', () {
264+
const List<String> expectedDirectoryPaths = <String>[
265+
'/example/path',
266+
'/example/2/path'
267+
];
268+
269+
test('works', () async {
270+
fakePlatformImplementation
271+
..setExpectations(
272+
initialDirectory: initialDirectory,
273+
confirmButtonText: confirmButtonText)
274+
..setPathsResponse(expectedDirectoryPaths);
275+
276+
final List<String?> directoryPaths = await getDirectoryPaths(
277+
initialDirectory: initialDirectory,
278+
confirmButtonText: confirmButtonText,
279+
);
280+
281+
expect(directoryPaths, expectedDirectoryPaths);
282+
});
283+
284+
test('works with no arguments', () async {
285+
fakePlatformImplementation.setPathsResponse(expectedDirectoryPaths);
286+
287+
final List<String?> directoryPaths = await getDirectoryPaths();
288+
expect(directoryPaths, expectedDirectoryPaths);
289+
});
290+
291+
test('sets the initial directory', () async {
292+
fakePlatformImplementation
293+
..setExpectations(initialDirectory: initialDirectory)
294+
..setPathsResponse(expectedDirectoryPaths);
295+
296+
final List<String?> directoryPaths =
297+
await getDirectoryPaths(initialDirectory: initialDirectory);
298+
expect(directoryPaths, expectedDirectoryPaths);
299+
});
300+
301+
test('sets the button confirmation label', () async {
302+
fakePlatformImplementation
303+
..setExpectations(confirmButtonText: confirmButtonText)
304+
..setPathsResponse(expectedDirectoryPaths);
305+
306+
final List<String?> directoryPaths =
307+
await getDirectoryPaths(confirmButtonText: confirmButtonText);
308+
expect(directoryPaths, expectedDirectoryPaths);
309+
});
310+
});
261311
}
262312

263313
class FakeFileSelector extends Fake
@@ -270,7 +320,7 @@ class FakeFileSelector extends Fake
270320
String? suggestedName;
271321
// Return values.
272322
List<XFile>? files;
273-
String? path;
323+
List<String>? paths;
274324

275325
void setExpectations({
276326
List<XTypeGroup> acceptedTypeGroups = const <XTypeGroup>[],
@@ -290,8 +340,8 @@ class FakeFileSelector extends Fake
290340
}
291341

292342
// ignore: use_setters_to_change_properties
293-
void setPathResponse(String path) {
294-
this.path = path;
343+
void setPathsResponse(List<String> paths) {
344+
this.paths = paths;
295345
}
296346

297347
@override
@@ -329,7 +379,7 @@ class FakeFileSelector extends Fake
329379
expect(initialDirectory, this.initialDirectory);
330380
expect(suggestedName, this.suggestedName);
331381
expect(confirmButtonText, this.confirmButtonText);
332-
return path;
382+
return paths?[0];
333383
}
334384

335385
@override
@@ -339,6 +389,16 @@ class FakeFileSelector extends Fake
339389
}) async {
340390
expect(initialDirectory, this.initialDirectory);
341391
expect(confirmButtonText, this.confirmButtonText);
342-
return path;
392+
return paths?[0];
393+
}
394+
395+
@override
396+
Future<List<String>> getDirectoryPaths({
397+
String? initialDirectory,
398+
String? confirmButtonText,
399+
}) async {
400+
expect(initialDirectory, this.initialDirectory);
401+
expect(confirmButtonText, this.confirmButtonText);
402+
return paths!;
343403
}
344404
}

0 commit comments

Comments
 (0)