[file_selector] Add file group to save return value - implementations (#4273)
Implementation package portion of https://github.com/flutter/packages/pull/4222
Part of https://github.com/flutter/flutter/issues/107093
diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md
index 8888630..5c9dc1d 100644
--- a/packages/file_selector/file_selector_linux/CHANGELOG.md
+++ b/packages/file_selector/file_selector_linux/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 0.9.2
+* Adds `getSaveLocation` and deprecates `getSavePath`.
* Updates minimum supported SDK version to Flutter 3.3/Dart 2.18.
## 0.9.1+3
diff --git a/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart
index 2d259a6..bfd83cf 100644
--- a/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart
+++ b/packages/file_selector/file_selector_linux/example/lib/save_text_page.dart
@@ -17,14 +17,12 @@
Future<void> _saveFile() async {
final String fileName = _nameController.text;
- // TODO(stuartmorgan): Update this to getSaveLocation in the next federated
- // change PR.
- // ignore: deprecated_member_use
- final String? path = await FileSelectorPlatform.instance.getSavePath(
- // Operation was canceled by the user.
- suggestedName: fileName,
+ final FileSaveLocation? result =
+ await FileSelectorPlatform.instance.getSaveLocation(
+ options: SaveDialogOptions(suggestedName: fileName),
);
- if (path == null) {
+ // Operation was canceled by the user.
+ if (result == null) {
return;
}
final String text = _contentController.text;
@@ -32,7 +30,7 @@
const String fileMimeType = 'text/plain';
final XFile textFile =
XFile.fromData(fileData, mimeType: fileMimeType, name: fileName);
- await textFile.saveTo(path);
+ await textFile.saveTo(result.path);
}
@override
diff --git a/packages/file_selector/file_selector_linux/example/pubspec.yaml b/packages/file_selector/file_selector_linux/example/pubspec.yaml
index 1596eb3..da7410f 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.4.0
+ file_selector_platform_interface: ^2.6.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 b8e3df6..b06523b 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
@@ -83,18 +83,36 @@
String? suggestedName,
String? confirmButtonText,
}) async {
+ final FileSaveLocation? location = await getSaveLocation(
+ acceptedTypeGroups: acceptedTypeGroups,
+ options: SaveDialogOptions(
+ initialDirectory: initialDirectory,
+ suggestedName: suggestedName,
+ confirmButtonText: confirmButtonText,
+ ));
+ return location?.path;
+ }
+
+ @override
+ Future<FileSaveLocation?> getSaveLocation({
+ List<XTypeGroup>? acceptedTypeGroups,
+ SaveDialogOptions options = const SaveDialogOptions(),
+ }) async {
final List<Map<String, Object>> serializedTypeGroups =
_serializeTypeGroups(acceptedTypeGroups);
- return _channel.invokeMethod<String>(
+ // TODO(stuartmorgan): Add the selected type group here and return it. See
+ // https://github.com/flutter/flutter/issues/107093
+ final String? path = await _channel.invokeMethod<String>(
_getSavePathMethod,
<String, dynamic>{
if (serializedTypeGroups.isNotEmpty)
_acceptedTypeGroupsKey: serializedTypeGroups,
- _initialDirectoryKey: initialDirectory,
- _suggestedNameKey: suggestedName,
- _confirmButtonTextKey: confirmButtonText,
+ _initialDirectoryKey: options.initialDirectory,
+ _suggestedNameKey: options.suggestedName,
+ _confirmButtonTextKey: options.confirmButtonText,
},
);
+ return path == null ? null : FileSaveLocation(path);
}
@override
diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml
index 238e0ae..0c9909f 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.1+3
+version: 0.9.2
environment:
sdk: ">=2.18.0 <4.0.0"
@@ -18,7 +18,7 @@
dependencies:
cross_file: ^0.3.1
- file_selector_platform_interface: ^2.4.0
+ file_selector_platform_interface: ^2.6.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 5127d28..f6b2cc6 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
@@ -32,7 +32,7 @@
expect(FileSelectorPlatform.instance, isA<FileSelectorLinux>());
});
- group('#openFile', () {
+ group('openFile', () {
test('passes the accepted type groups correctly', () async {
const XTypeGroup group = XTypeGroup(
label: 'text',
@@ -135,7 +135,7 @@
});
});
- group('#openFiles', () {
+ group('openFiles', () {
test('passes the accepted type groups correctly', () async {
const XTypeGroup group = XTypeGroup(
label: 'text',
@@ -209,7 +209,7 @@
);
await expectLater(
- plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]),
+ plugin.openFiles(acceptedTypeGroups: <XTypeGroup>[group]),
throwsArgumentError);
});
@@ -218,7 +218,7 @@
label: 'any',
);
- await plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]);
+ await plugin.openFiles(acceptedTypeGroups: <XTypeGroup>[group]);
expectMethodCall(
log,
@@ -232,13 +232,120 @@
],
'initialDirectory': null,
'confirmButtonText': null,
- 'multiple': false,
+ 'multiple': true,
},
);
});
});
- group('#getSavePath', () {
+ group('getSaveLocation', () {
+ test('passes the accepted type groups correctly', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'text',
+ extensions: <String>['txt'],
+ mimeTypes: <String>['text/plain'],
+ );
+
+ const XTypeGroup groupTwo = XTypeGroup(
+ label: 'image',
+ extensions: <String>['jpg'],
+ mimeTypes: <String>['image/jpg'],
+ );
+
+ await plugin
+ .getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
+
+ expectMethodCall(
+ log,
+ '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.getSaveLocation(
+ options:
+ const SaveDialogOptions(initialDirectory: '/example/directory'));
+
+ expectMethodCall(
+ log,
+ 'getSavePath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': '/example/directory',
+ 'suggestedName': null,
+ 'confirmButtonText': null,
+ },
+ );
+ });
+
+ test('passes confirmButtonText correctly', () async {
+ await plugin.getSaveLocation(
+ options: const SaveDialogOptions(confirmButtonText: 'Open File'));
+
+ expectMethodCall(
+ log,
+ 'getSavePath',
+ arguments: <String, dynamic>{
+ 'initialDirectory': null,
+ 'suggestedName': null,
+ 'confirmButtonText': 'Open File',
+ },
+ );
+ });
+
+ test('throws for a type group that does not support Linux', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'images',
+ webWildCards: <String>['images/*'],
+ );
+
+ await expectLater(
+ plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group]),
+ throwsArgumentError);
+ });
+
+ test('passes a wildcard group correctly', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'any',
+ );
+
+ await plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group]);
+
+ expectMethodCall(
+ log,
+ 'getSavePath',
+ arguments: <String, dynamic>{
+ 'acceptedTypeGroups': <Map<String, dynamic>>[
+ <String, Object>{
+ 'label': 'any',
+ 'extensions': <String>['*'],
+ },
+ ],
+ 'initialDirectory': null,
+ 'suggestedName': null,
+ 'confirmButtonText': null,
+ },
+ );
+ });
+ });
+
+ group('getSavePath (deprecated)', () {
test('passes the accepted type groups correctly', () async {
const XTypeGroup group = XTypeGroup(
label: 'text',
@@ -313,7 +420,7 @@
);
await expectLater(
- plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]),
+ plugin.getSavePath(acceptedTypeGroups: <XTypeGroup>[group]),
throwsArgumentError);
});
@@ -322,11 +429,11 @@
label: 'any',
);
- await plugin.openFile(acceptedTypeGroups: <XTypeGroup>[group]);
+ await plugin.getSavePath(acceptedTypeGroups: <XTypeGroup>[group]);
expectMethodCall(
log,
- 'openFile',
+ 'getSavePath',
arguments: <String, dynamic>{
'acceptedTypeGroups': <Map<String, dynamic>>[
<String, Object>{
@@ -335,14 +442,14 @@
},
],
'initialDirectory': null,
+ 'suggestedName': null,
'confirmButtonText': null,
- 'multiple': false,
},
);
});
});
- group('#getDirectoryPath', () {
+ group('getDirectoryPath', () {
test('passes initialDirectory correctly', () async {
await plugin.getDirectoryPath(initialDirectory: '/example/directory');
@@ -369,7 +476,7 @@
});
});
- group('#getDirectoryPaths', () {
+ group('getDirectoryPaths', () {
test('passes initialDirectory correctly', () async {
await plugin.getDirectoryPaths(initialDirectory: '/example/directory');
diff --git a/packages/file_selector/file_selector_macos/CHANGELOG.md b/packages/file_selector/file_selector_macos/CHANGELOG.md
index e697974..7a19783 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.3
+* Adds `getSaveLocation` and deprecates `getSavePath`.
* Updates minimum supported macOS version to 10.14.
## 0.9.2
diff --git a/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart
index 84180ac..6208e16 100644
--- a/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart
+++ b/packages/file_selector/file_selector_macos/example/lib/save_text_page.dart
@@ -17,13 +17,11 @@
Future<void> _saveFile() async {
final String fileName = _nameController.text;
- // TODO(stuartmorgan): Update this to getSaveLocation in the next federated
- // change PR.
- // ignore: deprecated_member_use
- final String? path = await FileSelectorPlatform.instance.getSavePath(
- suggestedName: fileName,
+ final FileSaveLocation? result =
+ await FileSelectorPlatform.instance.getSaveLocation(
+ options: SaveDialogOptions(suggestedName: fileName),
);
- if (path == null) {
+ if (result == null) {
// Operation was canceled by the user.
return;
}
@@ -32,7 +30,7 @@
const String fileMimeType = 'text/plain';
final XFile textFile =
XFile.fromData(fileData, mimeType: fileMimeType, name: fileName);
- await textFile.saveTo(path);
+ await textFile.saveTo(result.path);
}
@override
diff --git a/packages/file_selector/file_selector_macos/example/pubspec.yaml b/packages/file_selector/file_selector_macos/example/pubspec.yaml
index 9e2d831..baa27c5 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.4.0
+ file_selector_platform_interface: ^2.6.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 293c1e2..3489938 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
@@ -60,12 +60,28 @@
String? suggestedName,
String? confirmButtonText,
}) async {
- return _hostApi.displaySavePanel(SavePanelOptions(
+ final FileSaveLocation? location = await getSaveLocation(
+ acceptedTypeGroups: acceptedTypeGroups,
+ options: SaveDialogOptions(
+ initialDirectory: initialDirectory,
+ suggestedName: suggestedName,
+ confirmButtonText: confirmButtonText,
+ ));
+ return location?.path;
+ }
+
+ @override
+ Future<FileSaveLocation?> getSaveLocation({
+ List<XTypeGroup>? acceptedTypeGroups,
+ SaveDialogOptions options = const SaveDialogOptions(),
+ }) async {
+ final String? path = await _hostApi.displaySavePanel(SavePanelOptions(
allowedFileTypes: _allowedTypesFromTypeGroups(acceptedTypeGroups),
- directoryPath: initialDirectory,
- nameFieldStringValue: suggestedName,
- prompt: confirmButtonText,
+ directoryPath: options.initialDirectory,
+ nameFieldStringValue: options.suggestedName,
+ prompt: options.confirmButtonText,
));
+ return path == null ? null : FileSaveLocation(path);
}
@override
diff --git a/packages/file_selector/file_selector_macos/pubspec.yaml b/packages/file_selector/file_selector_macos/pubspec.yaml
index 731a5cc..047c7ca 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.2
+version: 0.9.3
environment:
sdk: ">=2.18.0 <4.0.0"
@@ -18,7 +18,7 @@
dependencies:
cross_file: ^0.3.1
- file_selector_platform_interface: ^2.4.0
+ file_selector_platform_interface: ^2.6.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 6450e6f..c7268a6 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
@@ -227,7 +227,7 @@
});
});
- group('getSavePath', () {
+ group('getSavePath (deprecated)', () {
test('works as expected with no arguments', () async {
when(mockApi.displaySavePanel(any)).thenAnswer((_) async => 'foo');
@@ -344,6 +344,126 @@
});
});
+ group('getSaveLocation', () {
+ test('works as expected with no arguments', () async {
+ when(mockApi.displaySavePanel(any)).thenAnswer((_) async => 'foo');
+
+ final FileSaveLocation? location = await plugin.getSaveLocation();
+
+ expect(location?.path, 'foo');
+ final VerificationResult result =
+ verify(mockApi.displaySavePanel(captureAny));
+ final SavePanelOptions options = result.captured[0] as SavePanelOptions;
+ expect(options.allowedFileTypes, null);
+ expect(options.directoryPath, null);
+ expect(options.nameFieldStringValue, null);
+ expect(options.prompt, null);
+ });
+
+ test('handles cancel', () async {
+ when(mockApi.displaySavePanel(any)).thenAnswer((_) async => null);
+
+ final FileSaveLocation? location = await plugin.getSaveLocation();
+
+ expect(location, null);
+ });
+
+ test('passes the accepted type groups correctly', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'text',
+ extensions: <String>['txt'],
+ mimeTypes: <String>['text/plain'],
+ uniformTypeIdentifiers: <String>['public.text'],
+ );
+
+ const XTypeGroup groupTwo = XTypeGroup(
+ label: 'image',
+ extensions: <String>['jpg'],
+ mimeTypes: <String>['image/jpg'],
+ uniformTypeIdentifiers: <String>['public.image'],
+ webWildCards: <String>['image/*']);
+
+ await plugin
+ .getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
+
+ final VerificationResult result =
+ verify(mockApi.displaySavePanel(captureAny));
+ final SavePanelOptions options = result.captured[0] as SavePanelOptions;
+ expect(options.allowedFileTypes!.extensions, <String>['txt', 'jpg']);
+ expect(options.allowedFileTypes!.mimeTypes,
+ <String>['text/plain', 'image/jpg']);
+ expect(options.allowedFileTypes!.utis,
+ <String>['public.text', 'public.image']);
+ });
+
+ test('passes initialDirectory correctly', () async {
+ await plugin.getSaveLocation(
+ options:
+ const SaveDialogOptions(initialDirectory: '/example/directory'));
+
+ final VerificationResult result =
+ verify(mockApi.displaySavePanel(captureAny));
+ final SavePanelOptions options = result.captured[0] as SavePanelOptions;
+ expect(options.directoryPath, '/example/directory');
+ });
+
+ test('passes confirmButtonText correctly', () async {
+ await plugin.getSaveLocation(
+ options: const SaveDialogOptions(confirmButtonText: 'Open File'));
+
+ final VerificationResult result =
+ verify(mockApi.displaySavePanel(captureAny));
+ final SavePanelOptions options = result.captured[0] as SavePanelOptions;
+ expect(options.prompt, 'Open File');
+ });
+
+ test('throws for a type group that does not support macOS', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'images',
+ webWildCards: <String>['images/*'],
+ );
+
+ await expectLater(
+ plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group]),
+ throwsArgumentError);
+ });
+
+ test('allows a wildcard group', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'text',
+ );
+
+ await expectLater(
+ plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group]),
+ completes);
+ });
+
+ test('ignores all type groups if any of them is a wildcard', () async {
+ await plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[
+ const XTypeGroup(
+ label: 'text',
+ extensions: <String>['txt'],
+ mimeTypes: <String>['text/plain'],
+ uniformTypeIdentifiers: <String>['public.text'],
+ ),
+ const XTypeGroup(
+ label: 'image',
+ extensions: <String>['jpg'],
+ mimeTypes: <String>['image/jpg'],
+ uniformTypeIdentifiers: <String>['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', () {
test('works as expected with no arguments', () async {
when(mockApi.displayOpenPanel(any))
diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md
index 43ad496..073f1c2 100644
--- a/packages/file_selector/file_selector_web/CHANGELOG.md
+++ b/packages/file_selector/file_selector_web/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 0.9.1
+* Adds `getSaveLocation` and deprecates `getSavePath`.
* Updates minimum supported SDK version to Flutter 3.3/Dart 2.18.
## 0.9.0+4
diff --git a/packages/file_selector/file_selector_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml
index bc8b984..fb3e71e 100644
--- a/packages/file_selector/file_selector_web/example/pubspec.yaml
+++ b/packages/file_selector/file_selector_web/example/pubspec.yaml
@@ -6,7 +6,7 @@
flutter: ">=3.3.0"
dependencies:
- file_selector_platform_interface: ^2.2.0
+ file_selector_platform_interface: ^2.6.0
file_selector_web:
path: ../
flutter:
diff --git a/packages/file_selector/file_selector_web/lib/file_selector_web.dart b/packages/file_selector/file_selector_web/lib/file_selector_web.dart
index 748bb3a..2380e27 100644
--- a/packages/file_selector/file_selector_web/lib/file_selector_web.dart
+++ b/packages/file_selector/file_selector_web/lib/file_selector_web.dart
@@ -61,6 +61,16 @@
'';
@override
+ Future<FileSaveLocation?> getSaveLocation({
+ List<XTypeGroup>? acceptedTypeGroups,
+ SaveDialogOptions options = const SaveDialogOptions(),
+ }) async {
+ // This is intended to be passed to XFile, which ignores the path, so
+ // provide a non-null dummy value.
+ return const FileSaveLocation('');
+ }
+
+ @override
Future<String?> getDirectoryPath({
String? initialDirectory,
String? confirmButtonText,
diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml
index 8a601cb..81ba11a 100644
--- a/packages/file_selector/file_selector_web/pubspec.yaml
+++ b/packages/file_selector/file_selector_web/pubspec.yaml
@@ -2,7 +2,7 @@
description: Web platform implementation of file_selector
repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_web
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.18.0 <4.0.0"
@@ -17,7 +17,7 @@
fileName: file_selector_web.dart
dependencies:
- file_selector_platform_interface: ^2.3.0
+ file_selector_platform_interface: ^2.6.0
flutter:
sdk: flutter
flutter_web_plugins:
diff --git a/packages/file_selector/file_selector_windows/CHANGELOG.md b/packages/file_selector/file_selector_windows/CHANGELOG.md
index 3516343..b747a45 100644
--- a/packages/file_selector/file_selector_windows/CHANGELOG.md
+++ b/packages/file_selector/file_selector_windows/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.9.3
+
+* Adds `getSaveLocation` and deprecates `getSavePath`.
+
## 0.9.2
* Adds `getDirectoryPaths` implementation.
diff --git a/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart
index 2d259a6..c92aff0 100644
--- a/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart
+++ b/packages/file_selector/file_selector_windows/example/lib/save_text_page.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:io';
import 'dart:typed_data';
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
import 'package:flutter/material.dart';
@@ -17,22 +18,39 @@
Future<void> _saveFile() async {
final String fileName = _nameController.text;
- // TODO(stuartmorgan): Update this to getSaveLocation in the next federated
- // change PR.
- // ignore: deprecated_member_use
- final String? path = await FileSelectorPlatform.instance.getSavePath(
- // Operation was canceled by the user.
- suggestedName: fileName,
+ final FileSaveLocation? result =
+ await FileSelectorPlatform.instance.getSaveLocation(
+ options: SaveDialogOptions(suggestedName: fileName),
+ acceptedTypeGroups: const <XTypeGroup>[
+ XTypeGroup(
+ label: 'Plain text',
+ extensions: <String>['txt'],
+ ),
+ XTypeGroup(
+ label: 'JSON',
+ extensions: <String>['json'],
+ ),
+ ],
);
- if (path == null) {
+ // Operation was canceled by the user.
+ if (result == null) {
return;
}
+ String path = result.path;
+ // Append an extension based on the selected type group if the user didn't
+ // include one.
+ if (!path.split(Platform.pathSeparator).last.contains('.')) {
+ final XTypeGroup? activeGroup = result.activeFilter;
+ if (activeGroup != null) {
+ // The group is one of the groups passed in above, each of which has
+ // exactly one `extensions` entry.
+ path = '$path.${activeGroup.extensions!.first}';
+ }
+ }
final String text = _contentController.text;
final Uint8List fileData = Uint8List.fromList(text.codeUnits);
- const String fileMimeType = 'text/plain';
- final XFile textFile =
- XFile.fromData(fileData, mimeType: fileMimeType, name: fileName);
- await textFile.saveTo(path);
+ final XFile textFile = XFile.fromData(fileData, name: fileName);
+ await textFile.saveTo(result.path);
}
@override
diff --git a/packages/file_selector/file_selector_windows/example/pubspec.yaml b/packages/file_selector/file_selector_windows/example/pubspec.yaml
index a22b6dc..02a1475 100644
--- a/packages/file_selector/file_selector_windows/example/pubspec.yaml
+++ b/packages/file_selector/file_selector_windows/example/pubspec.yaml
@@ -8,7 +8,7 @@
flutter: ">=3.3.0"
dependencies:
- file_selector_platform_interface: ^2.4.0
+ file_selector_platform_interface: ^2.6.0
file_selector_windows:
# When depending on this package from a real application you should use:
# file_selector_windows: ^x.y.z
diff --git a/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc b/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc
index 51812dc..e5666e0 100644
--- a/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc
+++ b/packages/file_selector/file_selector_windows/example/windows/runner/Runner.rc
@@ -60,14 +60,14 @@
// Version
//
-#ifdef FLUTTER_BUILD_NUMBER
-#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
#else
-#define VERSION_AS_NUMBER 1,0,0
+#define VERSION_AS_NUMBER 1,0,0,0
#endif
-#ifdef FLUTTER_BUILD_NAME
-#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
#else
#define VERSION_AS_STRING "1.0.0"
#endif
diff --git a/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart b/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart
index 0e935cc..b96886b 100644
--- a/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart
+++ b/packages/file_selector/file_selector_windows/lib/file_selector_windows.dart
@@ -21,7 +21,7 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
- final List<String?> paths = await _hostApi.showOpenDialog(
+ final FileDialogResult result = await _hostApi.showOpenDialog(
SelectionOptions(
allowMultiple: false,
selectFolders: false,
@@ -29,7 +29,7 @@
),
initialDirectory,
confirmButtonText);
- return paths.isEmpty ? null : XFile(paths.first!);
+ return result.paths.isEmpty ? null : XFile(result.paths.first!);
}
@override
@@ -38,7 +38,7 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
- final List<String?> paths = await _hostApi.showOpenDialog(
+ final FileDialogResult result = await _hostApi.showOpenDialog(
SelectionOptions(
allowMultiple: true,
selectFolders: false,
@@ -46,7 +46,7 @@
),
initialDirectory,
confirmButtonText);
- return paths.map((String? path) => XFile(path!)).toList();
+ return result.paths.map((String? path) => XFile(path!)).toList();
}
@override
@@ -56,16 +56,36 @@
String? suggestedName,
String? confirmButtonText,
}) async {
- final List<String?> paths = await _hostApi.showSaveDialog(
+ final FileSaveLocation? location = await getSaveLocation(
+ acceptedTypeGroups: acceptedTypeGroups,
+ options: SaveDialogOptions(
+ initialDirectory: initialDirectory,
+ suggestedName: suggestedName,
+ confirmButtonText: confirmButtonText,
+ ));
+ return location?.path;
+ }
+
+ @override
+ Future<FileSaveLocation?> getSaveLocation({
+ List<XTypeGroup>? acceptedTypeGroups,
+ SaveDialogOptions options = const SaveDialogOptions(),
+ }) async {
+ final FileDialogResult result = await _hostApi.showSaveDialog(
SelectionOptions(
allowMultiple: false,
selectFolders: false,
allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups),
),
- initialDirectory,
- suggestedName,
- confirmButtonText);
- return paths.isEmpty ? null : paths.first!;
+ options.initialDirectory,
+ options.suggestedName,
+ options.confirmButtonText);
+ final int? groupIndex = result.typeGroupIndex;
+ return result.paths.isEmpty
+ ? null
+ : FileSaveLocation(result.paths.first!,
+ activeFilter:
+ groupIndex == null ? null : acceptedTypeGroups?[groupIndex]);
}
@override
@@ -73,7 +93,7 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
- final List<String?> paths = await _hostApi.showOpenDialog(
+ final FileDialogResult result = await _hostApi.showOpenDialog(
SelectionOptions(
allowMultiple: false,
selectFolders: true,
@@ -81,7 +101,7 @@
),
initialDirectory,
confirmButtonText);
- return paths.isEmpty ? null : paths.first!;
+ return result.paths.isEmpty ? null : result.paths.first!;
}
@override
@@ -89,7 +109,7 @@
String? initialDirectory,
String? confirmButtonText,
}) async {
- final List<String?> paths = await _hostApi.showOpenDialog(
+ final FileDialogResult result = await _hostApi.showOpenDialog(
SelectionOptions(
allowMultiple: true,
selectFolders: true,
@@ -97,7 +117,7 @@
),
initialDirectory,
confirmButtonText);
- return paths.isEmpty ? <String>[] : List<String>.from(paths);
+ return result.paths.isEmpty ? <String>[] : List<String>.from(result.paths);
}
}
diff --git a/packages/file_selector/file_selector_windows/lib/src/messages.g.dart b/packages/file_selector/file_selector_windows/lib/src/messages.g.dart
index a61076b..b5b4a79 100644
--- a/packages/file_selector/file_selector_windows/lib/src/messages.g.dart
+++ b/packages/file_selector/file_selector_windows/lib/src/messages.g.dart
@@ -1,7 +1,7 @@
// 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.
-// Autogenerated from Pigeon (v9.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.0.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
@@ -68,16 +68,53 @@
}
}
+/// The result from an open or save dialog.
+class FileDialogResult {
+ FileDialogResult({
+ required this.paths,
+ this.typeGroupIndex,
+ });
+
+ /// The selected paths.
+ ///
+ /// Empty if the dialog was canceled.
+ List<String?> paths;
+
+ /// The type group index (into the list provided in [SelectionOptions]) of
+ /// the group that was selected when the dialog was confirmed.
+ ///
+ /// Null if no type groups were provided, or the dialog was canceled.
+ int? typeGroupIndex;
+
+ Object encode() {
+ return <Object?>[
+ paths,
+ typeGroupIndex,
+ ];
+ }
+
+ static FileDialogResult decode(Object result) {
+ result as List<Object?>;
+ return FileDialogResult(
+ paths: (result[0] as List<Object?>?)!.cast<String?>(),
+ typeGroupIndex: result[1] as int?,
+ );
+ }
+}
+
class _FileSelectorApiCodec extends StandardMessageCodec {
const _FileSelectorApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
- if (value is SelectionOptions) {
+ if (value is FileDialogResult) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
- } else if (value is TypeGroup) {
+ } else if (value is SelectionOptions) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
+ } else if (value is TypeGroup) {
+ buffer.putUint8(130);
+ writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -87,8 +124,10 @@
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
- return SelectionOptions.decode(readValue(buffer)!);
+ return FileDialogResult.decode(readValue(buffer)!);
case 129:
+ return SelectionOptions.decode(readValue(buffer)!);
+ case 130:
return TypeGroup.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
@@ -106,7 +145,7 @@
static const MessageCodec<Object?> codec = _FileSelectorApiCodec();
- Future<List<String?>> showOpenDialog(SelectionOptions arg_options,
+ Future<FileDialogResult> showOpenDialog(SelectionOptions arg_options,
String? arg_initialDirectory, String? arg_confirmButtonText) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.FileSelectorApi.showOpenDialog', codec,
@@ -131,11 +170,11 @@
message: 'Host platform returned null value for non-null return value.',
);
} else {
- return (replyList[0] as List<Object?>?)!.cast<String?>();
+ return (replyList[0] as FileDialogResult?)!;
}
}
- Future<List<String?>> showSaveDialog(
+ Future<FileDialogResult> showSaveDialog(
SelectionOptions arg_options,
String? arg_initialDirectory,
String? arg_suggestedName,
@@ -166,7 +205,7 @@
message: 'Host platform returned null value for non-null return value.',
);
} else {
- return (replyList[0] as List<Object?>?)!.cast<String?>();
+ return (replyList[0] as FileDialogResult?)!;
}
}
}
diff --git a/packages/file_selector/file_selector_windows/pigeons/messages.dart b/packages/file_selector/file_selector_windows/pigeons/messages.dart
index c3b3aff..8f82aec 100644
--- a/packages/file_selector/file_selector_windows/pigeons/messages.dart
+++ b/packages/file_selector/file_selector_windows/pigeons/messages.dart
@@ -37,14 +37,33 @@
List<TypeGroup?> allowedTypes;
}
+/// The result from an open or save dialog.
+class FileDialogResult {
+ FileDialogResult({required this.paths, this.typeGroupIndex});
+
+ /// The selected paths.
+ ///
+ /// Empty if the dialog was canceled.
+ // TODO(stuartmorgan): Make the generic type non-nullable once supported.
+ // https://github.com/flutter/flutter/issues/97848
+ // The Dart code treats the values as non-nullable.
+ List<String?> paths;
+
+ /// The type group index (into the list provided in [SelectionOptions]) of
+ /// the group that was selected when the dialog was confirmed.
+ ///
+ /// Null if no type groups were provided, or the dialog was canceled.
+ int? typeGroupIndex;
+}
+
@HostApi(dartHostTestHandler: 'TestFileSelectorApi')
abstract class FileSelectorApi {
- List<String?> showOpenDialog(
+ FileDialogResult showOpenDialog(
SelectionOptions options,
String? initialDirectory,
String? confirmButtonText,
);
- List<String?> showSaveDialog(
+ FileDialogResult showSaveDialog(
SelectionOptions options,
String? initialDirectory,
String? suggestedName,
diff --git a/packages/file_selector/file_selector_windows/pubspec.yaml b/packages/file_selector/file_selector_windows/pubspec.yaml
index 39d5cf7..4cd3cc5 100644
--- a/packages/file_selector/file_selector_windows/pubspec.yaml
+++ b/packages/file_selector/file_selector_windows/pubspec.yaml
@@ -2,7 +2,7 @@
description: Windows implementation of the file_selector plugin.
repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector_windows
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
-version: 0.9.2
+version: 0.9.3
environment:
sdk: ">=2.18.0 <4.0.0"
@@ -18,7 +18,7 @@
dependencies:
cross_file: ^0.3.1
- file_selector_platform_interface: ^2.4.0
+ file_selector_platform_interface: ^2.6.0
flutter:
sdk: flutter
@@ -27,4 +27,4 @@
flutter_test:
sdk: flutter
mockito: 5.4.1
- pigeon: ^9.1.0
+ pigeon: ^10.0.0
diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart
index fbe3683..4f455ee 100644
--- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart
+++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.dart
@@ -30,9 +30,10 @@
expect(FileSelectorPlatform.instance, isA<FileSelectorWindows>());
});
- group('#openFile', () {
+ group('openFile', () {
setUp(() {
- when(mockApi.showOpenDialog(any, any, any)).thenReturn(<String?>['foo']);
+ when(mockApi.showOpenDialog(any, any, any))
+ .thenReturn(FileDialogResult(paths: <String?>['foo']));
});
test('simple call works', () async {
@@ -105,10 +106,10 @@
});
});
- group('#openFiles', () {
+ group('openFiles', () {
setUp(() {
when(mockApi.showOpenDialog(any, any, any))
- .thenReturn(<String?>['foo', 'bar']);
+ .thenReturn(FileDialogResult(paths: <String?>['foo', 'bar']));
});
test('simple call works', () async {
@@ -182,9 +183,10 @@
});
});
- group('#getDirectoryPath', () {
+ group('getDirectoryPath', () {
setUp(() {
- when(mockApi.showOpenDialog(any, any, any)).thenReturn(<String?>['foo']);
+ when(mockApi.showOpenDialog(any, any, any))
+ .thenReturn(FileDialogResult(paths: <String?>['foo']));
});
test('simple call works', () async {
@@ -211,10 +213,10 @@
});
});
- group('#getDirectoryPaths', () {
+ group('getDirectoryPaths', () {
setUp(() {
when(mockApi.showOpenDialog(any, any, any))
- .thenReturn(<String?>['foo', 'bar']);
+ .thenReturn(FileDialogResult(paths: <String?>['foo', 'bar']));
});
test('simple call works', () async {
@@ -242,10 +244,122 @@
});
});
- group('#getSavePath', () {
+ group('getSaveLocation', () {
setUp(() {
when(mockApi.showSaveDialog(any, any, any, any))
- .thenReturn(<String?>['foo']);
+ .thenReturn(FileDialogResult(paths: <String?>['foo']));
+ });
+
+ test('simple call works', () async {
+ final FileSaveLocation? location = await plugin.getSaveLocation();
+
+ expect(location?.path, 'foo');
+ expect(location?.activeFilter, null);
+ final VerificationResult result =
+ verify(mockApi.showSaveDialog(captureAny, null, null, null));
+ final SelectionOptions options = result.captured[0] as SelectionOptions;
+ expect(options.allowMultiple, false);
+ expect(options.selectFolders, false);
+ });
+
+ test('passes the accepted type groups correctly', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'text',
+ extensions: <String>['txt'],
+ mimeTypes: <String>['text/plain'],
+ );
+
+ const XTypeGroup groupTwo = XTypeGroup(
+ label: 'image',
+ extensions: <String>['jpg'],
+ mimeTypes: <String>['image/jpg'],
+ );
+
+ await plugin
+ .getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
+
+ final VerificationResult result =
+ verify(mockApi.showSaveDialog(captureAny, null, null, null));
+ final SelectionOptions options = result.captured[0] as SelectionOptions;
+ expect(
+ _typeGroupListsMatch(options.allowedTypes, <TypeGroup>[
+ TypeGroup(label: 'text', extensions: <String>['txt']),
+ TypeGroup(label: 'image', extensions: <String>['jpg']),
+ ]),
+ true);
+ });
+
+ test('returns the selected type group correctly', () async {
+ when(mockApi.showSaveDialog(any, any, any, any)).thenReturn(
+ FileDialogResult(paths: <String?>['foo'], typeGroupIndex: 1));
+ const XTypeGroup group = XTypeGroup(
+ label: 'text',
+ extensions: <String>['txt'],
+ mimeTypes: <String>['text/plain'],
+ );
+
+ const XTypeGroup groupTwo = XTypeGroup(
+ label: 'image',
+ extensions: <String>['jpg'],
+ mimeTypes: <String>['image/jpg'],
+ );
+
+ final FileSaveLocation? result = await plugin
+ .getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group, groupTwo]);
+
+ verify(mockApi.showSaveDialog(captureAny, null, null, null));
+
+ expect(result?.activeFilter, groupTwo);
+ });
+
+ test('passes initialDirectory correctly', () async {
+ await plugin.getSaveLocation(
+ options:
+ const SaveDialogOptions(initialDirectory: '/example/directory'));
+
+ verify(mockApi.showSaveDialog(any, '/example/directory', null, null));
+ });
+
+ test('passes suggestedName correctly', () async {
+ await plugin.getSaveLocation(
+ options: const SaveDialogOptions(suggestedName: 'baz.txt'));
+
+ verify(mockApi.showSaveDialog(any, null, 'baz.txt', null));
+ });
+
+ test('passes confirmButtonText correctly', () async {
+ await plugin.getSaveLocation(
+ options: const SaveDialogOptions(confirmButtonText: 'Save File'));
+
+ verify(mockApi.showSaveDialog(any, null, null, 'Save File'));
+ });
+
+ test('throws for a type group that does not support Windows', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'text',
+ mimeTypes: <String>['text/plain'],
+ );
+
+ await expectLater(
+ plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group]),
+ throwsArgumentError);
+ });
+
+ test('allows a wildcard group', () async {
+ const XTypeGroup group = XTypeGroup(
+ label: 'text',
+ );
+
+ await expectLater(
+ plugin.getSaveLocation(acceptedTypeGroups: <XTypeGroup>[group]),
+ completes);
+ });
+ });
+
+ group('getSavePath (deprecated)', () {
+ setUp(() {
+ when(mockApi.showSaveDialog(any, any, any, any))
+ .thenReturn(FileDialogResult(paths: <String?>['foo']));
});
test('simple call works', () async {
diff --git a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart
index ae55f2e..7168e0c 100644
--- a/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart
+++ b/packages/file_selector/file_selector_windows/test/file_selector_windows_test.mocks.dart
@@ -1,12 +1,14 @@
-// Mocks generated by Mockito 5.4.0 from annotations
+// Mocks generated by Mockito 5.4.1 from annotations
// in file_selector_windows/test/file_selector_windows_test.dart.
// Do not manually edit this file.
+// @dart=2.19
+
// ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'package:file_selector_windows/src/messages.g.dart' as _i3;
+import 'package:file_selector_windows/src/messages.g.dart' as _i2;
import 'package:mockito/mockito.dart' as _i1;
-import 'test_api.g.dart' as _i2;
+import 'test_api.g.dart' as _i3;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -19,18 +21,29 @@
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
+class _FakeFileDialogResult_0 extends _i1.SmartFake
+ implements _i2.FileDialogResult {
+ _FakeFileDialogResult_0(
+ Object parent,
+ Invocation parentInvocation,
+ ) : super(
+ parent,
+ parentInvocation,
+ );
+}
+
/// A class which mocks [TestFileSelectorApi].
///
/// See the documentation for Mockito's code generation for more information.
class MockTestFileSelectorApi extends _i1.Mock
- implements _i2.TestFileSelectorApi {
+ implements _i3.TestFileSelectorApi {
MockTestFileSelectorApi() {
_i1.throwOnMissingStub(this);
}
@override
- List<String?> showOpenDialog(
- _i3.SelectionOptions? options,
+ _i2.FileDialogResult showOpenDialog(
+ _i2.SelectionOptions? options,
String? initialDirectory,
String? confirmButtonText,
) =>
@@ -43,11 +56,21 @@
confirmButtonText,
],
),
- returnValue: <String?>[],
- ) as List<String?>);
+ returnValue: _FakeFileDialogResult_0(
+ this,
+ Invocation.method(
+ #showOpenDialog,
+ [
+ options,
+ initialDirectory,
+ confirmButtonText,
+ ],
+ ),
+ ),
+ ) as _i2.FileDialogResult);
@override
- List<String?> showSaveDialog(
- _i3.SelectionOptions? options,
+ _i2.FileDialogResult showSaveDialog(
+ _i2.SelectionOptions? options,
String? initialDirectory,
String? suggestedName,
String? confirmButtonText,
@@ -62,6 +85,17 @@
confirmButtonText,
],
),
- returnValue: <String?>[],
- ) as List<String?>);
+ returnValue: _FakeFileDialogResult_0(
+ this,
+ Invocation.method(
+ #showSaveDialog,
+ [
+ options,
+ initialDirectory,
+ suggestedName,
+ confirmButtonText,
+ ],
+ ),
+ ),
+ ) as _i2.FileDialogResult);
}
diff --git a/packages/file_selector/file_selector_windows/test/test_api.g.dart b/packages/file_selector/file_selector_windows/test/test_api.g.dart
index f9ed8e5..778ae4f 100644
--- a/packages/file_selector/file_selector_windows/test/test_api.g.dart
+++ b/packages/file_selector/file_selector_windows/test/test_api.g.dart
@@ -1,7 +1,7 @@
// 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.
-// Autogenerated from Pigeon (v9.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.0.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, unnecessary_import
// ignore_for_file: avoid_relative_lib_imports
@@ -17,12 +17,15 @@
const _TestFileSelectorApiCodec();
@override
void writeValue(WriteBuffer buffer, Object? value) {
- if (value is SelectionOptions) {
+ if (value is FileDialogResult) {
buffer.putUint8(128);
writeValue(buffer, value.encode());
- } else if (value is TypeGroup) {
+ } else if (value is SelectionOptions) {
buffer.putUint8(129);
writeValue(buffer, value.encode());
+ } else if (value is TypeGroup) {
+ buffer.putUint8(130);
+ writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
@@ -32,8 +35,10 @@
Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case 128:
- return SelectionOptions.decode(readValue(buffer)!);
+ return FileDialogResult.decode(readValue(buffer)!);
case 129:
+ return SelectionOptions.decode(readValue(buffer)!);
+ case 130:
return TypeGroup.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
@@ -42,12 +47,14 @@
}
abstract class TestFileSelectorApi {
+ static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding =>
+ TestDefaultBinaryMessengerBinding.instance;
static const MessageCodec<Object?> codec = _TestFileSelectorApiCodec();
- List<String?> showOpenDialog(SelectionOptions options,
+ FileDialogResult showOpenDialog(SelectionOptions options,
String? initialDirectory, String? confirmButtonText);
- List<String?> showSaveDialog(
+ FileDialogResult showSaveDialog(
SelectionOptions options,
String? initialDirectory,
String? suggestedName,
@@ -60,9 +67,12 @@
'dev.flutter.pigeon.FileSelectorApi.showOpenDialog', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
- channel.setMockMessageHandler(null);
+ _testBinaryMessengerBinding!.defaultBinaryMessenger
+ .setMockDecodedMessageHandler<Object?>(channel, null);
} else {
- channel.setMockMessageHandler((Object? message) async {
+ _testBinaryMessengerBinding!.defaultBinaryMessenger
+ .setMockDecodedMessageHandler<Object?>(channel,
+ (Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.FileSelectorApi.showOpenDialog was null.');
final List<Object?> args = (message as List<Object?>?)!;
@@ -71,7 +81,7 @@
'Argument for dev.flutter.pigeon.FileSelectorApi.showOpenDialog was null, expected non-null SelectionOptions.');
final String? arg_initialDirectory = (args[1] as String?);
final String? arg_confirmButtonText = (args[2] as String?);
- final List<String?> output = api.showOpenDialog(
+ final FileDialogResult output = api.showOpenDialog(
arg_options!, arg_initialDirectory, arg_confirmButtonText);
return <Object?>[output];
});
@@ -82,9 +92,12 @@
'dev.flutter.pigeon.FileSelectorApi.showSaveDialog', codec,
binaryMessenger: binaryMessenger);
if (api == null) {
- channel.setMockMessageHandler(null);
+ _testBinaryMessengerBinding!.defaultBinaryMessenger
+ .setMockDecodedMessageHandler<Object?>(channel, null);
} else {
- channel.setMockMessageHandler((Object? message) async {
+ _testBinaryMessengerBinding!.defaultBinaryMessenger
+ .setMockDecodedMessageHandler<Object?>(channel,
+ (Object? message) async {
assert(message != null,
'Argument for dev.flutter.pigeon.FileSelectorApi.showSaveDialog was null.');
final List<Object?> args = (message as List<Object?>?)!;
@@ -94,7 +107,7 @@
final String? arg_initialDirectory = (args[1] as String?);
final String? arg_suggestedName = (args[2] as String?);
final String? arg_confirmButtonText = (args[3] as String?);
- final List<String?> output = api.showSaveDialog(arg_options!,
+ final FileDialogResult output = api.showSaveDialog(arg_options!,
arg_initialDirectory, arg_suggestedName, arg_confirmButtonText);
return <Object?>[output];
});
diff --git a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp
index 5820c4a..af2a9af 100644
--- a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp
+++ b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.cpp
@@ -51,6 +51,10 @@
return dialog_->GetResult(out_item);
}
+HRESULT FileDialogController::GetFileTypeIndex(UINT* out_index) const {
+ return dialog_->GetFileTypeIndex(out_index);
+}
+
HRESULT FileDialogController::GetResults(IShellItemArray** out_items) const {
IFileOpenDialogPtr open_dialog;
HRESULT result = dialog_->QueryInterface(IID_PPV_ARGS(&open_dialog));
diff --git a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h
index f5c9397..ab49292 100644
--- a/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h
+++ b/packages/file_selector/file_selector_windows/windows/file_dialog_controller.h
@@ -38,6 +38,7 @@
virtual HRESULT SetOptions(FILEOPENDIALOGOPTIONS options);
virtual HRESULT Show(HWND parent);
virtual HRESULT GetResult(IShellItem** out_item) const;
+ virtual HRESULT GetFileTypeIndex(UINT* out_index) const;
// IFileOpenDialog wrapper. This will fail if the IFileDialog* provided to the
// constructor was not an IFileOpenDialog instance.
diff --git a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp
index b9e6d21..3569798 100644
--- a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp
+++ b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.cpp
@@ -29,8 +29,8 @@
namespace {
+using flutter::CustomEncodableValue;
using flutter::EncodableList;
-using flutter::EncodableMap;
using flutter::EncodableValue;
// The kind of file dialog to show.
@@ -137,7 +137,7 @@
for (const EncodableValue& filter_info_value : filters) {
const auto& type_group = std::any_cast<TypeGroup>(
- std::get<flutter::CustomEncodableValue>(filter_info_value));
+ std::get<CustomEncodableValue>(filter_info_value));
filter_names.push_back(Utf16FromUtf8(type_group.label()));
filter_extensions.push_back(L"");
std::wstring& spec = filter_extensions.back();
@@ -158,8 +158,8 @@
static_cast<UINT>(filter_specs.size()), filter_specs.data());
}
- // Displays the dialog, and returns the selected files, or nullopt on error.
- std::optional<EncodableList> Show(HWND parent_window) {
+ // Displays the dialog, and returns the result, or nullopt on error.
+ std::optional<FileDialogResult> Show(HWND parent_window) {
assert(dialog_controller_);
last_result_ = dialog_controller_->Show(parent_window);
if (!SUCCEEDED(last_result_)) {
@@ -190,7 +190,14 @@
}
files.push_back(EncodableValue(GetPathForShellItem(shell_item)));
}
- return files;
+ FileDialogResult result(files, nullptr);
+ UINT file_type_index;
+ if (SUCCEEDED(dialog_controller_->GetFileTypeIndex(&file_type_index)) &&
+ file_type_index > 0) {
+ // Convert from the one-based index to a Dart index.
+ result.set_type_group_index(file_type_index - 1);
+ }
+ return result;
}
// Returns the result of the last Win32 API call related to this object.
@@ -205,7 +212,7 @@
HRESULT last_result_;
};
-ErrorOr<flutter::EncodableList> ShowDialog(
+ErrorOr<FileDialogResult> ShowDialog(
const FileDialogControllerFactory& dialog_factory, HWND parent_window,
DialogMode mode, const SelectionOptions& options,
const std::string* initial_directory, const std::string* suggested_name,
@@ -243,16 +250,16 @@
dialog.SetFileTypeFilters(options.allowed_types());
}
- std::optional<EncodableList> files = dialog.Show(parent_window);
- if (!files) {
+ std::optional<FileDialogResult> result = dialog.Show(parent_window);
+ if (!result) {
if (dialog.last_result() != HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
return FlutterError("System error", "Could not show dialog",
EncodableValue(dialog.last_result()));
} else {
- return EncodableList();
+ return FileDialogResult(EncodableList(), nullptr);
}
}
- return std::move(files.value());
+ return std::move(result.value());
}
// Returns the top-level window that owns |view|.
@@ -282,14 +289,14 @@
FileSelectorPlugin::~FileSelectorPlugin() = default;
-ErrorOr<flutter::EncodableList> FileSelectorPlugin::ShowOpenDialog(
+ErrorOr<FileDialogResult> FileSelectorPlugin::ShowOpenDialog(
const SelectionOptions& options, const std::string* initialDirectory,
const std::string* confirmButtonText) {
return ShowDialog(*controller_factory_, get_root_window_(), DialogMode::open,
options, initialDirectory, nullptr, confirmButtonText);
}
-ErrorOr<flutter::EncodableList> FileSelectorPlugin::ShowSaveDialog(
+ErrorOr<FileDialogResult> FileSelectorPlugin::ShowSaveDialog(
const SelectionOptions& options, const std::string* initialDirectory,
const std::string* suggestedName, const std::string* confirmButtonText) {
return ShowDialog(*controller_factory_, get_root_window_(), DialogMode::save,
diff --git a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h
index 1388bfd..2f17f94 100644
--- a/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h
+++ b/packages/file_selector/file_selector_windows/windows/file_selector_plugin.h
@@ -32,10 +32,10 @@
virtual ~FileSelectorPlugin();
// FileSelectorApi
- ErrorOr<flutter::EncodableList> ShowOpenDialog(
+ ErrorOr<FileDialogResult> ShowOpenDialog(
const SelectionOptions& options, const std::string* initial_directory,
const std::string* confirm_button_text) override;
- ErrorOr<flutter::EncodableList> ShowSaveDialog(
+ ErrorOr<FileDialogResult> ShowSaveDialog(
const SelectionOptions& options, const std::string* initialDirectory,
const std::string* suggestedName,
const std::string* confirmButtonText) override;
diff --git a/packages/file_selector/file_selector_windows/windows/messages.g.cpp b/packages/file_selector/file_selector_windows/windows/messages.g.cpp
index 24b831e..a60fd92 100644
--- a/packages/file_selector/file_selector_windows/windows/messages.g.cpp
+++ b/packages/file_selector/file_selector_windows/windows/messages.g.cpp
@@ -1,7 +1,7 @@
// 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.
-// Autogenerated from Pigeon (v9.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#undef _HAS_EXCEPTIONS
@@ -26,10 +26,15 @@
// TypeGroup
+TypeGroup::TypeGroup(const std::string& label, const EncodableList& extensions)
+ : label_(label), extensions_(extensions) {}
+
const std::string& TypeGroup::label() const { return label_; }
+
void TypeGroup::set_label(std::string_view value_arg) { label_ = value_arg; }
const EncodableList& TypeGroup::extensions() const { return extensions_; }
+
void TypeGroup::set_extensions(const EncodableList& value_arg) {
extensions_ = value_arg;
}
@@ -42,29 +47,28 @@
return list;
}
-TypeGroup::TypeGroup() {}
-
-TypeGroup::TypeGroup(const EncodableList& list) {
- auto& encodable_label = list[0];
- if (const std::string* pointer_label =
- std::get_if<std::string>(&encodable_label)) {
- label_ = *pointer_label;
- }
- auto& encodable_extensions = list[1];
- if (const EncodableList* pointer_extensions =
- std::get_if<EncodableList>(&encodable_extensions)) {
- extensions_ = *pointer_extensions;
- }
+TypeGroup TypeGroup::FromEncodableList(const EncodableList& list) {
+ TypeGroup decoded(std::get<std::string>(list[0]),
+ std::get<EncodableList>(list[1]));
+ return decoded;
}
// SelectionOptions
+SelectionOptions::SelectionOptions(bool allow_multiple, bool select_folders,
+ const EncodableList& allowed_types)
+ : allow_multiple_(allow_multiple),
+ select_folders_(select_folders),
+ allowed_types_(allowed_types) {}
+
bool SelectionOptions::allow_multiple() const { return allow_multiple_; }
+
void SelectionOptions::set_allow_multiple(bool value_arg) {
allow_multiple_ = value_arg;
}
bool SelectionOptions::select_folders() const { return select_folders_; }
+
void SelectionOptions::set_select_folders(bool value_arg) {
select_folders_ = value_arg;
}
@@ -72,6 +76,7 @@
const EncodableList& SelectionOptions::allowed_types() const {
return allowed_types_;
}
+
void SelectionOptions::set_allowed_types(const EncodableList& value_arg) {
allowed_types_ = value_arg;
}
@@ -85,36 +90,77 @@
return list;
}
-SelectionOptions::SelectionOptions() {}
+SelectionOptions SelectionOptions::FromEncodableList(
+ const EncodableList& list) {
+ SelectionOptions decoded(std::get<bool>(list[0]), std::get<bool>(list[1]),
+ std::get<EncodableList>(list[2]));
+ return decoded;
+}
-SelectionOptions::SelectionOptions(const EncodableList& list) {
- auto& encodable_allow_multiple = list[0];
- if (const bool* pointer_allow_multiple =
- std::get_if<bool>(&encodable_allow_multiple)) {
- allow_multiple_ = *pointer_allow_multiple;
+// FileDialogResult
+
+FileDialogResult::FileDialogResult(const EncodableList& paths)
+ : paths_(paths) {}
+
+FileDialogResult::FileDialogResult(const EncodableList& paths,
+ const int64_t* type_group_index)
+ : paths_(paths),
+ type_group_index_(type_group_index
+ ? std::optional<int64_t>(*type_group_index)
+ : std::nullopt) {}
+
+const EncodableList& FileDialogResult::paths() const { return paths_; }
+
+void FileDialogResult::set_paths(const EncodableList& value_arg) {
+ paths_ = value_arg;
+}
+
+const int64_t* FileDialogResult::type_group_index() const {
+ return type_group_index_ ? &(*type_group_index_) : nullptr;
+}
+
+void FileDialogResult::set_type_group_index(const int64_t* value_arg) {
+ type_group_index_ =
+ value_arg ? std::optional<int64_t>(*value_arg) : std::nullopt;
+}
+
+void FileDialogResult::set_type_group_index(int64_t value_arg) {
+ type_group_index_ = value_arg;
+}
+
+EncodableList FileDialogResult::ToEncodableList() const {
+ EncodableList list;
+ list.reserve(2);
+ list.push_back(EncodableValue(paths_));
+ list.push_back(type_group_index_ ? EncodableValue(*type_group_index_)
+ : EncodableValue());
+ return list;
+}
+
+FileDialogResult FileDialogResult::FromEncodableList(
+ const EncodableList& list) {
+ FileDialogResult decoded(std::get<EncodableList>(list[0]));
+ auto& encodable_type_group_index = list[1];
+ if (!encodable_type_group_index.IsNull()) {
+ decoded.set_type_group_index(encodable_type_group_index.LongValue());
}
- auto& encodable_select_folders = list[1];
- if (const bool* pointer_select_folders =
- std::get_if<bool>(&encodable_select_folders)) {
- select_folders_ = *pointer_select_folders;
- }
- auto& encodable_allowed_types = list[2];
- if (const EncodableList* pointer_allowed_types =
- std::get_if<EncodableList>(&encodable_allowed_types)) {
- allowed_types_ = *pointer_allowed_types;
- }
+ return decoded;
}
FileSelectorApiCodecSerializer::FileSelectorApiCodecSerializer() {}
+
EncodableValue FileSelectorApiCodecSerializer::ReadValueOfType(
uint8_t type, flutter::ByteStreamReader* stream) const {
switch (type) {
case 128:
- return CustomEncodableValue(
- SelectionOptions(std::get<EncodableList>(ReadValue(stream))));
+ return CustomEncodableValue(FileDialogResult::FromEncodableList(
+ std::get<EncodableList>(ReadValue(stream))));
case 129:
- return CustomEncodableValue(
- TypeGroup(std::get<EncodableList>(ReadValue(stream))));
+ return CustomEncodableValue(SelectionOptions::FromEncodableList(
+ std::get<EncodableList>(ReadValue(stream))));
+ case 130:
+ return CustomEncodableValue(TypeGroup::FromEncodableList(
+ std::get<EncodableList>(ReadValue(stream))));
default:
return flutter::StandardCodecSerializer::ReadValueOfType(type, stream);
}
@@ -124,16 +170,24 @@
const EncodableValue& value, flutter::ByteStreamWriter* stream) const {
if (const CustomEncodableValue* custom_value =
std::get_if<CustomEncodableValue>(&value)) {
- if (custom_value->type() == typeid(SelectionOptions)) {
+ if (custom_value->type() == typeid(FileDialogResult)) {
stream->WriteByte(128);
WriteValue(
EncodableValue(
+ std::any_cast<FileDialogResult>(*custom_value).ToEncodableList()),
+ stream);
+ return;
+ }
+ if (custom_value->type() == typeid(SelectionOptions)) {
+ stream->WriteByte(129);
+ WriteValue(
+ EncodableValue(
std::any_cast<SelectionOptions>(*custom_value).ToEncodableList()),
stream);
return;
}
if (custom_value->type() == typeid(TypeGroup)) {
- stream->WriteByte(129);
+ stream->WriteByte(130);
WriteValue(EncodableValue(
std::any_cast<TypeGroup>(*custom_value).ToEncodableList()),
stream);
@@ -176,14 +230,15 @@
const auto& encodable_confirm_button_text_arg = args.at(2);
const auto* confirm_button_text_arg =
std::get_if<std::string>(&encodable_confirm_button_text_arg);
- ErrorOr<EncodableList> output = api->ShowOpenDialog(
+ ErrorOr<FileDialogResult> output = api->ShowOpenDialog(
options_arg, initial_directory_arg, confirm_button_text_arg);
if (output.has_error()) {
reply(WrapError(output.error()));
return;
}
EncodableList wrapped;
- wrapped.push_back(EncodableValue(std::move(output).TakeValue()));
+ wrapped.push_back(
+ CustomEncodableValue(std::move(output).TakeValue()));
reply(EncodableValue(std::move(wrapped)));
} catch (const std::exception& exception) {
reply(WrapError(exception.what()));
@@ -219,7 +274,7 @@
const auto& encodable_confirm_button_text_arg = args.at(3);
const auto* confirm_button_text_arg =
std::get_if<std::string>(&encodable_confirm_button_text_arg);
- ErrorOr<EncodableList> output = api->ShowSaveDialog(
+ ErrorOr<FileDialogResult> output = api->ShowSaveDialog(
options_arg, initial_directory_arg, suggested_name_arg,
confirm_button_text_arg);
if (output.has_error()) {
@@ -227,7 +282,8 @@
return;
}
EncodableList wrapped;
- wrapped.push_back(EncodableValue(std::move(output).TakeValue()));
+ wrapped.push_back(
+ CustomEncodableValue(std::move(output).TakeValue()));
reply(EncodableValue(std::move(wrapped)));
} catch (const std::exception& exception) {
reply(WrapError(exception.what()));
@@ -244,6 +300,7 @@
EncodableList{EncodableValue(std::string(error_message)),
EncodableValue("Error"), EncodableValue()});
}
+
EncodableValue FileSelectorApi::WrapError(const FlutterError& error) {
return EncodableValue(EncodableList{EncodableValue(error.code()),
EncodableValue(error.message()),
diff --git a/packages/file_selector/file_selector_windows/windows/messages.g.h b/packages/file_selector/file_selector_windows/windows/messages.g.h
index 248ca89..ab8afd7 100644
--- a/packages/file_selector/file_selector_windows/windows/messages.g.h
+++ b/packages/file_selector/file_selector_windows/windows/messages.g.h
@@ -1,7 +1,7 @@
// 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.
-// Autogenerated from Pigeon (v9.1.1), do not edit directly.
+// Autogenerated from Pigeon (v10.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#ifndef PIGEON_MESSAGES_G_H_
@@ -41,10 +41,10 @@
template <class T>
class ErrorOr {
public:
- ErrorOr(const T& rhs) { new (&v_) T(rhs); }
- ErrorOr(const T&& rhs) { v_ = std::move(rhs); }
- ErrorOr(const FlutterError& rhs) { new (&v_) FlutterError(rhs); }
- ErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); }
+ ErrorOr(const T& rhs) : v_(rhs) {}
+ ErrorOr(const T&& rhs) : v_(std::move(rhs)) {}
+ ErrorOr(const FlutterError& rhs) : v_(rhs) {}
+ ErrorOr(const FlutterError&& rhs) : v_(std::move(rhs)) {}
bool has_error() const { return std::holds_alternative<FlutterError>(v_); }
const T& value() const { return std::get<T>(v_); };
@@ -61,7 +61,10 @@
// Generated class from Pigeon that represents data sent in messages.
class TypeGroup {
public:
- TypeGroup();
+ // Constructs an object setting all fields.
+ explicit TypeGroup(const std::string& label,
+ const flutter::EncodableList& extensions);
+
const std::string& label() const;
void set_label(std::string_view value_arg);
@@ -69,7 +72,7 @@
void set_extensions(const flutter::EncodableList& value_arg);
private:
- TypeGroup(const flutter::EncodableList& list);
+ static TypeGroup FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
friend class FileSelectorApi;
friend class FileSelectorApiCodecSerializer;
@@ -80,7 +83,10 @@
// Generated class from Pigeon that represents data sent in messages.
class SelectionOptions {
public:
- SelectionOptions();
+ // Constructs an object setting all fields.
+ explicit SelectionOptions(bool allow_multiple, bool select_folders,
+ const flutter::EncodableList& allowed_types);
+
bool allow_multiple() const;
void set_allow_multiple(bool value_arg);
@@ -91,7 +97,7 @@
void set_allowed_types(const flutter::EncodableList& value_arg);
private:
- SelectionOptions(const flutter::EncodableList& list);
+ static SelectionOptions FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
friend class FileSelectorApi;
friend class FileSelectorApiCodecSerializer;
@@ -100,16 +106,49 @@
flutter::EncodableList allowed_types_;
};
+// The result from an open or save dialog.
+//
+// Generated class from Pigeon that represents data sent in messages.
+class FileDialogResult {
+ public:
+ // Constructs an object setting all non-nullable fields.
+ explicit FileDialogResult(const flutter::EncodableList& paths);
+
+ // Constructs an object setting all fields.
+ explicit FileDialogResult(const flutter::EncodableList& paths,
+ const int64_t* type_group_index);
+
+ // The selected paths.
+ //
+ // Empty if the dialog was canceled.
+ const flutter::EncodableList& paths() const;
+ void set_paths(const flutter::EncodableList& value_arg);
+
+ // The type group index (into the list provided in [SelectionOptions]) of
+ // the group that was selected when the dialog was confirmed.
+ //
+ // Null if no type groups were provided, or the dialog was canceled.
+ const int64_t* type_group_index() const;
+ void set_type_group_index(const int64_t* value_arg);
+ void set_type_group_index(int64_t value_arg);
+
+ private:
+ static FileDialogResult FromEncodableList(const flutter::EncodableList& list);
+ flutter::EncodableList ToEncodableList() const;
+ friend class FileSelectorApi;
+ friend class FileSelectorApiCodecSerializer;
+ flutter::EncodableList paths_;
+ std::optional<int64_t> type_group_index_;
+};
+
class FileSelectorApiCodecSerializer : public flutter::StandardCodecSerializer {
public:
+ FileSelectorApiCodecSerializer();
inline static FileSelectorApiCodecSerializer& GetInstance() {
static FileSelectorApiCodecSerializer sInstance;
return sInstance;
}
- FileSelectorApiCodecSerializer();
-
- public:
void WriteValue(const flutter::EncodableValue& value,
flutter::ByteStreamWriter* stream) const override;
@@ -125,10 +164,10 @@
FileSelectorApi(const FileSelectorApi&) = delete;
FileSelectorApi& operator=(const FileSelectorApi&) = delete;
virtual ~FileSelectorApi() {}
- virtual ErrorOr<flutter::EncodableList> ShowOpenDialog(
+ virtual ErrorOr<FileDialogResult> ShowOpenDialog(
const SelectionOptions& options, const std::string* initial_directory,
const std::string* confirm_button_text) = 0;
- virtual ErrorOr<flutter::EncodableList> ShowSaveDialog(
+ virtual ErrorOr<FileDialogResult> ShowSaveDialog(
const SelectionOptions& options, const std::string* initial_directory,
const std::string* suggested_name,
const std::string* confirm_button_text) = 0;
diff --git a/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp b/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp
index 8efeb54..ee6d4dc 100644
--- a/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp
+++ b/packages/file_selector/file_selector_windows/windows/test/file_selector_plugin_test.cpp
@@ -28,37 +28,8 @@
using flutter::CustomEncodableValue;
using flutter::EncodableList;
-using flutter::EncodableMap;
using flutter::EncodableValue;
-// These structs and classes are a workaround for
-// https://github.com/flutter/flutter/issues/104286 and
-// https://github.com/flutter/flutter/issues/104653.
-struct AllowMultipleArg {
- bool value = false;
- AllowMultipleArg(bool val) : value(val) {}
-};
-struct SelectFoldersArg {
- bool value = false;
- SelectFoldersArg(bool val) : value(val) {}
-};
-SelectionOptions CreateOptions(AllowMultipleArg allow_multiple,
- SelectFoldersArg select_folders,
- const EncodableList& allowed_types) {
- SelectionOptions options;
- options.set_allow_multiple(allow_multiple.value);
- options.set_select_folders(select_folders.value);
- options.set_allowed_types(allowed_types);
- return options;
-}
-TypeGroup CreateTypeGroup(std::string_view label,
- const EncodableList& extensions) {
- TypeGroup group;
- group.set_label(label);
- group.set_extensions(extensions);
- return group;
-}
-
} // namespace
TEST(FileSelectorPlugin, TestOpenSimple) {
@@ -87,17 +58,18 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowOpenDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowOpenDialog(
+ SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ false, EncodableList()),
nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
- EXPECT_EQ(paths.size(), 1);
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 1);
EXPECT_EQ(std::get<std::string>(paths[0]),
Utf8FromUtf16(fake_selected_file.path()));
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
TEST(FileSelectorPlugin, TestOpenWithArguments) {
@@ -129,17 +101,18 @@
// This directory must exist.
std::string initial_directory("C:\\Program Files");
std::string confirm_button("Open it!");
- ErrorOr<EncodableList> result = plugin.ShowOpenDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowOpenDialog(
+ SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ false, EncodableList()),
&initial_directory, &confirm_button);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
- EXPECT_EQ(paths.size(), 1);
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 1);
EXPECT_EQ(std::get<std::string>(paths[0]),
Utf8FromUtf16(fake_selected_file.path()));
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
TEST(FileSelectorPlugin, TestOpenMultiple) {
@@ -173,19 +146,20 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowOpenDialog(
- CreateOptions(AllowMultipleArg(true), SelectFoldersArg(false),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowOpenDialog(
+ SelectionOptions(/* allow multiple = */ true,
+ /* select folders = */ false, EncodableList()),
nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
- EXPECT_EQ(paths.size(), 2);
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 2);
EXPECT_EQ(std::get<std::string>(paths[0]),
Utf8FromUtf16(fake_selected_file_1.path()));
EXPECT_EQ(std::get<std::string>(paths[1]),
Utf8FromUtf16(fake_selected_file_2.path()));
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
TEST(FileSelectorPlugin, TestOpenWithFilter) {
@@ -196,18 +170,18 @@
IID_PPV_ARGS(&fake_result_array));
const EncodableValue text_group =
- CustomEncodableValue(CreateTypeGroup("Text", EncodableList({
- EncodableValue("txt"),
- EncodableValue("json"),
- })));
+ CustomEncodableValue(TypeGroup("Text", EncodableList({
+ EncodableValue("txt"),
+ EncodableValue("json"),
+ })));
const EncodableValue image_group =
- CustomEncodableValue(CreateTypeGroup("Images", EncodableList({
- EncodableValue("png"),
- EncodableValue("gif"),
- EncodableValue("jpeg"),
- })));
+ CustomEncodableValue(TypeGroup("Images", EncodableList({
+ EncodableValue("png"),
+ EncodableValue("gif"),
+ EncodableValue("jpeg"),
+ })));
const EncodableValue any_group =
- CustomEncodableValue(CreateTypeGroup("Any", EncodableList()));
+ CustomEncodableValue(TypeGroup("Any", EncodableList()));
bool shown = false;
MockShow show_validator = [&shown, fake_result_array, fake_window](
@@ -234,21 +208,26 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowOpenDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false),
- EncodableList({
- text_group,
- image_group,
- any_group,
- })),
- nullptr, nullptr);
+ ErrorOr<FileDialogResult> result =
+ plugin.ShowOpenDialog(SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ false,
+ EncodableList({
+ text_group,
+ image_group,
+ any_group,
+ })),
+ nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
- EXPECT_EQ(paths.size(), 1);
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 1);
EXPECT_EQ(std::get<std::string>(paths[0]),
Utf8FromUtf16(fake_selected_file.path()));
+ // The test dialog controller always reports the last group as
+ // selected, so that should be what the plugin returns.
+ ASSERT_NE(result.value().type_group_index(), nullptr);
+ EXPECT_EQ(*(result.value().type_group_index()), 2);
}
TEST(FileSelectorPlugin, TestOpenCancel) {
@@ -265,15 +244,16 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowOpenDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowOpenDialog(
+ SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ false, EncodableList()),
nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
+ const EncodableList& paths = result.value().paths();
EXPECT_EQ(paths.size(), 0);
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
TEST(FileSelectorPlugin, TestSaveSimple) {
@@ -299,17 +279,18 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowSaveDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowSaveDialog(
+ SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ false, EncodableList()),
nullptr, nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
- EXPECT_EQ(paths.size(), 1);
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 1);
EXPECT_EQ(std::get<std::string>(paths[0]),
Utf8FromUtf16(fake_selected_file.path()));
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
TEST(FileSelectorPlugin, TestSaveWithArguments) {
@@ -341,17 +322,78 @@
std::string initial_directory("C:\\Program Files");
std::string suggested_name("a name");
std::string confirm_button("Save it!");
- ErrorOr<EncodableList> result = plugin.ShowSaveDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowSaveDialog(
+ SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ false, EncodableList()),
&initial_directory, &suggested_name, &confirm_button);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
- EXPECT_EQ(paths.size(), 1);
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 1);
EXPECT_EQ(std::get<std::string>(paths[0]),
Utf8FromUtf16(fake_selected_file.path()));
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
+}
+
+TEST(FileSelectorPlugin, TestSaveWithFilter) {
+ const HWND fake_window = reinterpret_cast<HWND>(1337);
+ ScopedTestShellItem fake_selected_file;
+
+ const EncodableValue text_group =
+ CustomEncodableValue(TypeGroup("Text", EncodableList({
+ EncodableValue("txt"),
+ EncodableValue("json"),
+ })));
+ const EncodableValue image_group =
+ CustomEncodableValue(TypeGroup("Images", EncodableList({
+ EncodableValue("png"),
+ EncodableValue("gif"),
+ EncodableValue("jpeg"),
+ })));
+
+ bool shown = false;
+ MockShow show_validator =
+ [&shown, fake_result = fake_selected_file.file(), fake_window](
+ const TestFileDialogController& dialog, HWND parent) {
+ shown = true;
+ EXPECT_EQ(parent, fake_window);
+
+ // Validate filter.
+ const std::vector<DialogFilter>& filters = dialog.GetFileTypes();
+ EXPECT_EQ(filters.size(), 2U);
+ if (filters.size() == 2U) {
+ EXPECT_EQ(filters[0].name, L"Text");
+ EXPECT_EQ(filters[0].spec, L"*.txt;*.json");
+ EXPECT_EQ(filters[1].name, L"Images");
+ EXPECT_EQ(filters[1].spec, L"*.png;*.gif;*.jpeg");
+ }
+
+ return MockShowResult(fake_result);
+ };
+
+ FileSelectorPlugin plugin(
+ [fake_window] { return fake_window; },
+ std::make_unique<TestFileDialogControllerFactory>(show_validator));
+ ErrorOr<FileDialogResult> result =
+ plugin.ShowSaveDialog(SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ false,
+ EncodableList({
+ text_group,
+ image_group,
+ })),
+ nullptr, nullptr, nullptr);
+
+ EXPECT_TRUE(shown);
+ ASSERT_FALSE(result.has_error());
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 1);
+ EXPECT_EQ(std::get<std::string>(paths[0]),
+ Utf8FromUtf16(fake_selected_file.path()));
+ // The test dialog controller always reports the last group as
+ // selected, so that should be what the plugin returns.
+ ASSERT_NE(result.value().type_group_index(), nullptr);
+ EXPECT_EQ(*(result.value().type_group_index()), 1);
}
TEST(FileSelectorPlugin, TestSaveCancel) {
@@ -368,15 +410,16 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowSaveDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(false),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowSaveDialog(
+ SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ false, EncodableList()),
nullptr, nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
+ const EncodableList& paths = result.value().paths();
EXPECT_EQ(paths.size(), 0);
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
TEST(FileSelectorPlugin, TestGetDirectorySimple) {
@@ -408,16 +451,17 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowOpenDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(true),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowOpenDialog(
+ SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ true, EncodableList()),
nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
- EXPECT_EQ(paths.size(), 1);
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 1);
EXPECT_EQ(std::get<std::string>(paths[0]), "C:\\Program Files");
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
TEST(FileSelectorPlugin, TestGetDirectoryMultiple) {
@@ -454,19 +498,20 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowOpenDialog(
- CreateOptions(AllowMultipleArg(true), SelectFoldersArg(true),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowOpenDialog(
+ SelectionOptions(/* allow multiple = */ true, /* select folders = */ true,
+ EncodableList()),
nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
- EXPECT_EQ(paths.size(), 2);
+ const EncodableList& paths = result.value().paths();
+ ASSERT_EQ(paths.size(), 2);
EXPECT_EQ(std::get<std::string>(paths[0]),
Utf8FromUtf16(fake_selected_dir_1.path()));
EXPECT_EQ(std::get<std::string>(paths[1]),
Utf8FromUtf16(fake_selected_dir_2.path()));
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
TEST(FileSelectorPlugin, TestGetDirectoryCancel) {
@@ -483,15 +528,16 @@
FileSelectorPlugin plugin(
[fake_window] { return fake_window; },
std::make_unique<TestFileDialogControllerFactory>(show_validator));
- ErrorOr<EncodableList> result = plugin.ShowOpenDialog(
- CreateOptions(AllowMultipleArg(false), SelectFoldersArg(true),
- EncodableList()),
+ ErrorOr<FileDialogResult> result = plugin.ShowOpenDialog(
+ SelectionOptions(/* allow multiple = */ false,
+ /* select folders = */ true, EncodableList()),
nullptr, nullptr);
EXPECT_TRUE(shown);
ASSERT_FALSE(result.has_error());
- const EncodableList& paths = result.value();
+ const EncodableList& paths = result.value().paths();
EXPECT_EQ(paths.size(), 0);
+ EXPECT_EQ(result.value().type_group_index(), nullptr);
}
} // namespace test
diff --git a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp
index 15065f9..e775aa6 100644
--- a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp
+++ b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.cpp
@@ -60,6 +60,13 @@
return S_OK;
}
+HRESULT TestFileDialogController::GetFileTypeIndex(UINT* out_index) const {
+ // Arbitrarily always return the last group. (No -1 because the return value
+ // from GetFileTypeIndex is defined to be one-indexed.)
+ *out_index = static_cast<UINT>(filter_groups_.size());
+ return S_OK;
+}
+
HRESULT TestFileDialogController::GetResults(
IShellItemArray** out_items) const {
*out_items = std::get<IShellItemArrayPtr>(mock_result_);
diff --git a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h
index 1c221fc..e3aa093 100644
--- a/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h
+++ b/packages/file_selector/file_selector_windows/windows/test/test_file_dialog_controller.h
@@ -56,6 +56,7 @@
HRESULT SetOkButtonLabel(const wchar_t* text) override;
HRESULT Show(HWND parent) override;
HRESULT GetResult(IShellItem** out_item) const override;
+ HRESULT GetFileTypeIndex(UINT* out_index) const override;
HRESULT GetResults(IShellItemArray** out_items) const override;
// Accessors for validating IFileDialogController setter calls.