diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md index 4fdab0b73b5d..5258a94ce052 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.1 +* Adds `getDirectoryPaths` implementation. * Updates example code for `use_build_context_synchronously` lint. * Updates minimum Flutter version to 3.0. 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 new file mode 100644 index 000000000000..47f981e9051d --- /dev/null +++ b/packages/file_selector/file_selector_macos/example/lib/get_multiple_directories_page.dart @@ -0,0 +1,86 @@ +// 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 _getDirectoryPaths(BuildContext context) async { + const String confirmButtonText = 'Choose'; + final List directoriesPaths = + await FileSelectorPlatform.instance.getDirectoryPaths( + confirmButtonText: confirmButtonText, + ); + if (directoriesPaths.isEmpty) { + // Operation was canceled by the user. + return; + } + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) => + TextDisplay(directoriesPaths.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: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + // 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.directoryPaths, {Key? key}) : super(key: key); + + /// The paths selected in the dialog. + final String directoryPaths; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Selected Directories'), + content: Scrollbar( + child: SingleChildScrollView( + child: Text(directoryPaths), + ), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.pop(context), + ), + ], + ); + } +} diff --git a/packages/file_selector/file_selector_macos/example/lib/home_page.dart b/packages/file_selector/file_selector_macos/example/lib/home_page.dart index a4b2ae1f63ea..80e16332a017 100644 --- a/packages/file_selector/file_selector_macos/example/lib/home_page.dart +++ b/packages/file_selector/file_selector_macos/example/lib/home_page.dart @@ -55,6 +55,13 @@ class HomePage extends StatelessWidget { 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_macos/example/lib/main.dart b/packages/file_selector/file_selector_macos/example/lib/main.dart index 3e447104ef9f..b8f047645a1d 100644 --- a/packages/file_selector/file_selector_macos/example/lib/main.dart +++ b/packages/file_selector/file_selector_macos/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 @@ class MyApp extends StatelessWidget { '/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_macos/example/macos/RunnerTests/RunnerTests.swift b/packages/file_selector/file_selector_macos/example/macos/RunnerTests/RunnerTests.swift index 2dbd016f66ef..ce4e91ccda83 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 @@ -292,4 +292,54 @@ class exampleTests: XCTestCase { XCTAssertNotNil(panelController.openPanel) } + func testGetDirectoriesMultiple() throws { + let panelController = TestPanelController() + let plugin = FileSelectorPlugin( + viewProvider: TestViewProvider(), + panelController: panelController) + + let returnPaths = ["/foo/bar", "/foo/test"]; + panelController.openURLs = returnPaths.map({ path in URL(fileURLWithPath: path) }) + + let called = XCTestExpectation() + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { paths in + XCTAssertEqual(paths, returnPaths) + called.fulfill() + } + + wait(for: [called], timeout: 0.5) + XCTAssertNotNil(panelController.openPanel) + if let panel = panelController.openPanel { + XCTAssertTrue(panel.canChooseDirectories) + // For consistency across platforms, file selection is disabled. + XCTAssertFalse(panel.canChooseFiles) + XCTAssertTrue(panel.allowsMultipleSelection) + } + } + + func testGetDirectoryMultipleCancel() throws { + let panelController = TestPanelController() + let plugin = FileSelectorPlugin( + viewProvider: TestViewProvider(), + panelController: panelController) + + let called = XCTestExpectation() + let options = OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions()) + plugin.displayOpenPanel(options: options) { paths in + XCTAssertEqual(paths.count, 0) + called.fulfill() + } + + wait(for: [called], timeout: 0.5) + XCTAssertNotNil(panelController.openPanel) + } } diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml index a2122b2858b7..494e4a3cb746 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 @@ dependencies: # 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.2.0 + file_selector_platform_interface: ^2.4.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 f8a087fa6877..0fd4457a236f 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 @@ -85,6 +85,23 @@ class FileSelectorMacOS extends FileSelectorPlatform { return paths.isEmpty ? null : paths.first; } + @override + Future> getDirectoryPaths({ + String? initialDirectory, + String? confirmButtonText, + }) async { + final List paths = + await _hostApi.displayOpenPanel(OpenPanelOptions( + allowsMultipleSelection: true, + canChooseDirectories: true, + canChooseFiles: false, + baseOptions: SavePanelOptions( + directoryPath: initialDirectory, + prompt: confirmButtonText, + ))); + return paths.isEmpty ? [] : List.from(paths); + } + // Converts the type group list into a flat list of all allowed types, since // macOS doesn't support filter groups. AllowedTypes? _allowedTypesFromTypeGroups(List? typeGroups) { diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml index 3654beaca4c0..1ddbdbe99f2e 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/plugins/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.0+4 +version: 0.9.1 environment: sdk: ">=2.12.0 <3.0.0" @@ -18,7 +18,7 @@ flutter: 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_macos/test/file_selector_macos_test.dart b/packages/file_selector/file_selector_macos/test/file_selector_macos_test.dart index 181409e6f1b4..6d330619e758 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 @@ -317,6 +317,31 @@ void main() { plugin.getSavePath(acceptedTypeGroups: [group]), completes); }); + + test('ignores all type groups if any of them is a wildcard', () async { + await plugin.getSavePath(acceptedTypeGroups: [ + const XTypeGroup( + label: 'text', + extensions: ['txt'], + mimeTypes: ['text/plain'], + macUTIs: ['public.text'], + ), + const XTypeGroup( + label: 'image', + extensions: ['jpg'], + mimeTypes: ['image/jpg'], + macUTIs: ['public.image'], + ), + const XTypeGroup( + label: 'any', + ), + ]); + + final VerificationResult result = + verify(mockApi.displaySavePanel(captureAny)); + final SavePanelOptions options = result.captured[0] as SavePanelOptions; + expect(options.allowedFileTypes, null); + }); }); group('getDirectoryPath', () { @@ -366,28 +391,51 @@ void main() { }); }); - test('ignores all type groups if any of them is a wildcard', () async { - await plugin.getSavePath(acceptedTypeGroups: [ - const XTypeGroup( - label: 'text', - extensions: ['txt'], - mimeTypes: ['text/plain'], - macUTIs: ['public.text'], - ), - const XTypeGroup( - label: 'image', - extensions: ['jpg'], - mimeTypes: ['image/jpg'], - macUTIs: ['public.image'], - ), - const XTypeGroup( - label: 'any', - ), - ]); - - final VerificationResult result = - verify(mockApi.displaySavePanel(captureAny)); - final SavePanelOptions options = result.captured[0] as SavePanelOptions; - expect(options.allowedFileTypes, null); + group('getDirectoryPaths', () { + test('works as expected with no arguments', () async { + when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => + ['firstDirectory', 'secondDirectory', 'thirdDirectory']); + + final List path = await plugin.getDirectoryPaths(); + + 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.prompt, null); + }); + + test('handles cancel', () async { + when(mockApi.displayOpenPanel(any)).thenAnswer((_) async => []); + + final List paths = await plugin.getDirectoryPaths(); + + expect(paths, []); + }); + + test('passes confirmButtonText correctly', () async { + await plugin.getDirectoryPaths(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.getDirectoryPaths(initialDirectory: '/example/directory'); + + final VerificationResult result = + verify(mockApi.displayOpenPanel(captureAny)); + final OpenPanelOptions options = result.captured[0] as OpenPanelOptions; + expect(options.baseOptions.directoryPath, '/example/directory'); + }); }); }