Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 3500291

Browse files
committed
Add getDirectoryPaths for macos
1 parent d065e4e commit 3500291

File tree

9 files changed

+234
-28
lines changed

9 files changed

+234
-28
lines changed

packages/file_selector/file_selector_macos/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 0.9.1
22

3+
* Adds `getDirectoryPaths` implementation.
34
* Updates example code for `use_build_context_synchronously` lint.
45
* Updates minimum Flutter version to 3.0.
56

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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_platform_interface/file_selector_platform_interface.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+
/// Default Constructor
12+
const GetMultipleDirectoriesPage({Key? key}) : super(key: key);
13+
14+
Future<void> _getDirectoryPaths(BuildContext context) async {
15+
const String confirmButtonText = 'Choose';
16+
final List<String?> directoriesPaths = await FileSelectorPlatform.instance.getDirectoryPaths(
17+
confirmButtonText: confirmButtonText,
18+
);
19+
if (directoriesPaths == null) {
20+
// Operation was canceled by the user.
21+
return;
22+
}
23+
if (context.mounted) {
24+
await showDialog<void>(
25+
context: context,
26+
builder: (BuildContext context) => TextDisplay(directoriesPaths.join('\n')),
27+
);
28+
}
29+
}
30+
31+
@override
32+
Widget build(BuildContext context) {
33+
return Scaffold(
34+
appBar: AppBar(
35+
title: const Text('Select multiple directories'),
36+
),
37+
body: Center(
38+
child: Column(
39+
mainAxisAlignment: MainAxisAlignment.center,
40+
children: <Widget>[
41+
ElevatedButton(
42+
style: ElevatedButton.styleFrom(
43+
// ignore: deprecated_member_use
44+
primary: Colors.blue,
45+
// ignore: deprecated_member_use
46+
onPrimary: Colors.white,
47+
),
48+
child: const Text('Press to ask user to choose multiple directories'),
49+
onPressed: () => _getDirectoryPaths(context),
50+
),
51+
],
52+
),
53+
),
54+
);
55+
}
56+
}
57+
58+
/// Widget that displays a text file in a dialog.
59+
class TextDisplay extends StatelessWidget {
60+
/// Creates a `TextDisplay`.
61+
const TextDisplay(this.directoryPaths, {Key? key}) : super(key: key);
62+
63+
/// The paths selected in the dialog.
64+
final String directoryPaths;
65+
66+
@override
67+
Widget build(BuildContext context) {
68+
return AlertDialog(
69+
title: const Text('Selected Directories'),
70+
content: Scrollbar(
71+
child: SingleChildScrollView(
72+
child: Text(directoryPaths),
73+
),
74+
),
75+
actions: <Widget>[
76+
TextButton(
77+
child: const Text('Close'),
78+
onPressed: () => Navigator.pop(context),
79+
),
80+
],
81+
);
82+
}
83+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ 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 directories dialog'),
62+
onPressed: () => Navigator.pushNamed(context, '/multi-directories'),
63+
),
5864
],
5965
),
6066
),

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

Lines changed: 2 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,7 @@ class MyApp extends StatelessWidget {
3637
'/open/text': (BuildContext context) => const OpenTextPage(),
3738
'/save/text': (BuildContext context) => SaveTextPage(),
3839
'/directory': (BuildContext context) => const GetDirectoryPage(),
40+
'/multi-directories': (BuildContext context) => const GetMultipleDirectoriesPage()
3941
},
4042
);
4143
}

packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,54 @@ class exampleTests: XCTestCase {
292292
XCTAssertNotNil(panelController.openPanel)
293293
}
294294

295-
}
295+
func testGetDirectoriesMultiple() throws {
296+
let panelController = TestPanelController()
297+
let plugin = FileSelectorPlugin(
298+
viewProvider: TestViewProvider(),
299+
panelController: panelController)
300+
301+
let returnPaths = ["/foo/bar", "/foo/test"];
302+
panelController.openURLs = returnPaths.map({ path in URL(fileURLWithPath: path) })
303+
304+
let called = XCTestExpectation()
305+
let options = OpenPanelOptions(
306+
allowsMultipleSelection: true,
307+
canChooseDirectories: true,
308+
canChooseFiles: false,
309+
baseOptions: SavePanelOptions())
310+
plugin.displayOpenPanel(options: options) { paths in
311+
XCTAssertEqual(paths, returnPaths)
312+
called.fulfill()
313+
}
314+
315+
wait(for: [called], timeout: 0.5)
316+
XCTAssertNotNil(panelController.openPanel)
317+
if let panel = panelController.openPanel {
318+
XCTAssertTrue(panel.canChooseDirectories)
319+
// For consistency across platforms, file selection is disabled.
320+
XCTAssertFalse(panel.canChooseFiles)
321+
XCTAssertTrue(panel.allowsMultipleSelection)
322+
}
323+
}
324+
325+
func testGetDirectoryMultipleCancel() throws {
326+
let panelController = TestPanelController()
327+
let plugin = FileSelectorPlugin(
328+
viewProvider: TestViewProvider(),
329+
panelController: panelController)
330+
331+
let called = XCTestExpectation()
332+
let options = OpenPanelOptions(
333+
allowsMultipleSelection: true,
334+
canChooseDirectories: true,
335+
canChooseFiles: false,
336+
baseOptions: SavePanelOptions())
337+
plugin.displayOpenPanel(options: options) { paths in
338+
XCTAssertEqual(paths.count, 0)
339+
called.fulfill()
340+
}
341+
342+
wait(for: [called], timeout: 0.5)
343+
XCTAssertNotNil(panelController.openPanel)
344+
}
345+
}

packages/file_selector/file_selector_macos/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ dependencies:
1515
# The example app is bundled with the plugin so we use a path dependency on
1616
# the parent directory to use the current plugin's version.
1717
path: ..
18-
file_selector_platform_interface: ^2.2.0
18+
file_selector_platform_interface: ^2.4.0
1919
flutter:
2020
sdk: flutter
2121

packages/file_selector/file_selector_macos/lib/file_selector_macos.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ class FileSelectorMacOS extends FileSelectorPlatform {
8585
return paths.isEmpty ? null : paths.first;
8686
}
8787

88+
@override
89+
Future<List<String>> getDirectoryPaths({
90+
String? initialDirectory,
91+
String? confirmButtonText,
92+
}) async {
93+
final List<String?> paths =
94+
await _hostApi.displayOpenPanel(OpenPanelOptions(
95+
allowsMultipleSelection: true,
96+
canChooseDirectories: true,
97+
canChooseFiles: false,
98+
baseOptions: SavePanelOptions(
99+
directoryPath: initialDirectory,
100+
prompt: confirmButtonText,
101+
)));
102+
return paths.isEmpty ? <String>[] : List<String>.from(paths);
103+
}
104+
88105
// Converts the type group list into a flat list of all allowed types, since
89106
// macOS doesn't support filter groups.
90107
AllowedTypes? _allowedTypesFromTypeGroups(List<XTypeGroup>? typeGroups) {

packages/file_selector/file_selector_macos/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: file_selector_macos
22
description: macOS implementation of the file_selector plugin.
33
repository: https://github.com/flutter/plugins/tree/main/packages/file_selector/file_selector_macos
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
5-
version: 0.9.0+4
5+
version: 0.9.1
66

77
environment:
88
sdk: ">=2.12.0 <3.0.0"
@@ -18,7 +18,7 @@ flutter:
1818

1919
dependencies:
2020
cross_file: ^0.3.1
21-
file_selector_platform_interface: ^2.2.0
21+
file_selector_platform_interface: ^2.4.0
2222
flutter:
2323
sdk: flutter
2424

packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,31 @@ void main() {
317317
plugin.getSavePath(acceptedTypeGroups: <XTypeGroup>[group]),
318318
completes);
319319
});
320+
321+
test('ignores all type groups if any of them is a wildcard', () async {
322+
await plugin.getSavePath(acceptedTypeGroups: <XTypeGroup>[
323+
const XTypeGroup(
324+
label: 'text',
325+
extensions: <String>['txt'],
326+
mimeTypes: <String>['text/plain'],
327+
macUTIs: <String>['public.text'],
328+
),
329+
const XTypeGroup(
330+
label: 'image',
331+
extensions: <String>['jpg'],
332+
mimeTypes: <String>['image/jpg'],
333+
macUTIs: <String>['public.image'],
334+
),
335+
const XTypeGroup(
336+
label: 'any',
337+
),
338+
]);
339+
340+
final VerificationResult result =
341+
verify(mockApi.displaySavePanel(captureAny));
342+
final SavePanelOptions options = result.captured[0] as SavePanelOptions;
343+
expect(options.allowedFileTypes, null);
344+
});
320345
});
321346

322347
group('getDirectoryPath', () {
@@ -366,28 +391,50 @@ void main() {
366391
});
367392
});
368393

369-
test('ignores all type groups if any of them is a wildcard', () async {
370-
await plugin.getSavePath(acceptedTypeGroups: <XTypeGroup>[
371-
const XTypeGroup(
372-
label: 'text',
373-
extensions: <String>['txt'],
374-
mimeTypes: <String>['text/plain'],
375-
macUTIs: <String>['public.text'],
376-
),
377-
const XTypeGroup(
378-
label: 'image',
379-
extensions: <String>['jpg'],
380-
mimeTypes: <String>['image/jpg'],
381-
macUTIs: <String>['public.image'],
382-
),
383-
const XTypeGroup(
384-
label: 'any',
385-
),
386-
]);
387-
388-
final VerificationResult result =
389-
verify(mockApi.displaySavePanel(captureAny));
390-
final SavePanelOptions options = result.captured[0] as SavePanelOptions;
391-
expect(options.allowedFileTypes, null);
394+
group('getDirectoryPaths', () {
395+
test('works as expected with no arguments', () async {
396+
when(mockApi.displayOpenPanel(any))
397+
.thenAnswer((_) async => <String>['firstDirectory','secondDirectory', 'thirdDirectory']);
398+
399+
final List<String> path = await plugin.getDirectoryPaths();
400+
401+
expect(path, <String>['firstDirectory','secondDirectory', 'thirdDirectory']);
402+
final VerificationResult result =
403+
verify(mockApi.displayOpenPanel(captureAny));
404+
final OpenPanelOptions options = result.captured[0] as OpenPanelOptions;
405+
expect(options.allowsMultipleSelection, true);
406+
expect(options.canChooseFiles, false);
407+
expect(options.canChooseDirectories, true);
408+
expect(options.baseOptions.allowedFileTypes, null);
409+
expect(options.baseOptions.directoryPath, null);
410+
expect(options.baseOptions.nameFieldStringValue, null);
411+
expect(options.baseOptions.prompt, null);
412+
});
413+
414+
test('handles cancel', () async {
415+
when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => <String?>[]);
416+
417+
final List<String> paths = await plugin.getDirectoryPaths();
418+
419+
expect(paths, <String>[]);
420+
});
421+
422+
test('passes confirmButtonText correctly', () async {
423+
await plugin.getDirectoryPaths(confirmButtonText: 'Select directories');
424+
425+
final VerificationResult result =
426+
verify(mockApi.displayOpenPanel(captureAny));
427+
final OpenPanelOptions options = result.captured[0] as OpenPanelOptions;
428+
expect(options.baseOptions.prompt, 'Select directories');
429+
});
430+
431+
test('passes initialDirectory correctly', () async {
432+
await plugin.getDirectoryPaths(initialDirectory: '/example/directory');
433+
434+
final VerificationResult result =
435+
verify(mockApi.displayOpenPanel(captureAny));
436+
final OpenPanelOptions options = result.captured[0] as OpenPanelOptions;
437+
expect(options.baseOptions.directoryPath, '/example/directory');
438+
});
392439
});
393440
}

0 commit comments

Comments
 (0)