Skip to content

Commit

Permalink
[file_selector] Convert Linux to Pigeon (#7770)
Browse files Browse the repository at this point in the history
Replaces manual method channel code with Pigeon.

I made a couple of simplifications to the Dart/C boundary while converting:
- The return value is a always a list now; it's easier to handle that on the Dart side than track and handle in the native code.
- It's a single method with a type enum, instead of several different methods, since that mirrors the underlying SDK.

These changes are consistent with our general trend toward putting more logic in Dart and less in native code, for ease of maintenance.

Fixes flutter/flutter#117906
  • Loading branch information
stuartmorgan authored Oct 2, 2024
1 parent aeecebc commit 7c97c88
Show file tree
Hide file tree
Showing 13 changed files with 1,482 additions and 581 deletions.
3 changes: 2 additions & 1 deletion packages/file_selector/file_selector_linux/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 0.9.3

* Updates method channel implementation to use Pigeon.
* Updates minimum supported SDK version to Flutter 3.19/Dart 3.3.

## 0.9.2+1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,17 @@

import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/services.dart';

const MethodChannel _channel =
MethodChannel('plugins.flutter.dev/file_selector_linux');

const String _typeGroupLabelKey = 'label';
const String _typeGroupExtensionsKey = 'extensions';
const String _typeGroupMimeTypesKey = 'mimeTypes';

const String _openFileMethod = 'openFile';
const String _getSavePathMethod = 'getSavePath';
const String _getDirectoryPathMethod = 'getDirectoryPath';

const String _acceptedTypeGroupsKey = 'acceptedTypeGroups';
const String _confirmButtonTextKey = 'confirmButtonText';
const String _initialDirectoryKey = 'initialDirectory';
const String _multipleKey = 'multiple';
const String _suggestedNameKey = 'suggestedName';
import 'src/messages.g.dart';

/// An implementation of [FileSelectorPlatform] for Linux.
class FileSelectorLinux extends FileSelectorPlatform {
/// The MethodChannel that is being used by this implementation of the plugin.
@visibleForTesting
MethodChannel get channel => _channel;
/// Creates a new plugin implementation instance.
FileSelectorLinux({
@visibleForTesting FileSelectorApi? api,
}) : _hostApi = api ?? FileSelectorApi();

final FileSelectorApi _hostApi;

/// Registers the Linux implementation.
static void registerWith() {
Expand All @@ -40,19 +27,16 @@ class FileSelectorLinux extends FileSelectorPlatform {
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<Map<String, Object>> serializedTypeGroups =
_serializeTypeGroups(acceptedTypeGroups);
final List<String>? path = await _channel.invokeListMethod<String>(
_openFileMethod,
<String, dynamic>{
if (serializedTypeGroups.isNotEmpty)
_acceptedTypeGroupsKey: serializedTypeGroups,
'initialDirectory': initialDirectory,
_confirmButtonTextKey: confirmButtonText,
_multipleKey: false,
},
);
return path == null ? null : XFile(path.first);
final List<String> paths = await _hostApi.showFileChooser(
PlatformFileChooserActionType.open,
PlatformFileChooserOptions(
allowedFileTypes:
_platformTypeGroupsFromXTypeGroups(acceptedTypeGroups),
currentFolderPath: initialDirectory,
acceptButtonLabel: confirmButtonText,
selectMultiple: false,
));
return paths.isEmpty ? null : XFile(paths.first);
}

@override
Expand All @@ -61,19 +45,16 @@ class FileSelectorLinux extends FileSelectorPlatform {
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<Map<String, Object>> serializedTypeGroups =
_serializeTypeGroups(acceptedTypeGroups);
final List<String>? pathList = await _channel.invokeListMethod<String>(
_openFileMethod,
<String, dynamic>{
if (serializedTypeGroups.isNotEmpty)
_acceptedTypeGroupsKey: serializedTypeGroups,
_initialDirectoryKey: initialDirectory,
_confirmButtonTextKey: confirmButtonText,
_multipleKey: true,
},
);
return pathList?.map((String path) => XFile(path)).toList() ?? <XFile>[];
final List<String> paths = await _hostApi.showFileChooser(
PlatformFileChooserActionType.open,
PlatformFileChooserOptions(
allowedFileTypes:
_platformTypeGroupsFromXTypeGroups(acceptedTypeGroups),
currentFolderPath: initialDirectory,
acceptButtonLabel: confirmButtonText,
selectMultiple: true,
));
return paths.map((String path) => XFile(path)).toList();
}

@override
Expand All @@ -98,78 +79,76 @@ class FileSelectorLinux extends FileSelectorPlatform {
List<XTypeGroup>? acceptedTypeGroups,
SaveDialogOptions options = const SaveDialogOptions(),
}) async {
final List<Map<String, Object>> serializedTypeGroups =
_serializeTypeGroups(acceptedTypeGroups);
// 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: options.initialDirectory,
_suggestedNameKey: options.suggestedName,
_confirmButtonTextKey: options.confirmButtonText,
},
);
return path == null ? null : FileSaveLocation(path);
final List<String> paths = await _hostApi.showFileChooser(
PlatformFileChooserActionType.save,
PlatformFileChooserOptions(
allowedFileTypes:
_platformTypeGroupsFromXTypeGroups(acceptedTypeGroups),
currentFolderPath: options.initialDirectory,
currentName: options.suggestedName,
acceptButtonLabel: options.confirmButtonText,
));
return paths.isEmpty ? null : FileSaveLocation(paths.first);
}

@override
Future<String?> getDirectoryPath({
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<String>? path = await _channel
.invokeListMethod<String>(_getDirectoryPathMethod, <String, dynamic>{
_initialDirectoryKey: initialDirectory,
_confirmButtonTextKey: confirmButtonText,
});
return path?.first;
final List<String> paths = await _hostApi.showFileChooser(
PlatformFileChooserActionType.chooseDirectory,
PlatformFileChooserOptions(
currentFolderPath: initialDirectory,
acceptButtonLabel: confirmButtonText,
selectMultiple: false,
));
return paths.isEmpty ? null : paths.first;
}

@override
Future<List<String>> getDirectoryPaths({
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<String>? pathList = await _channel
.invokeListMethod<String>(_getDirectoryPathMethod, <String, dynamic>{
_initialDirectoryKey: initialDirectory,
_confirmButtonTextKey: confirmButtonText,
_multipleKey: true,
});
return pathList ?? <String>[];
return _hostApi.showFileChooser(
PlatformFileChooserActionType.chooseDirectory,
PlatformFileChooserOptions(
currentFolderPath: initialDirectory,
acceptButtonLabel: confirmButtonText,
selectMultiple: true,
));
}
}

List<Map<String, Object>> _serializeTypeGroups(List<XTypeGroup>? groups) {
return (groups ?? <XTypeGroup>[]).map(_serializeTypeGroup).toList();
List<PlatformTypeGroup>? _platformTypeGroupsFromXTypeGroups(
List<XTypeGroup>? groups) {
return groups?.map(_platformTypeGroupFromXTypeGroup).toList();
}

Map<String, Object> _serializeTypeGroup(XTypeGroup group) {
final Map<String, Object> serialization = <String, Object>{
_typeGroupLabelKey: group.label ?? '',
};
PlatformTypeGroup _platformTypeGroupFromXTypeGroup(XTypeGroup group) {
final String label = group.label ?? '';
if (group.allowsAny) {
serialization[_typeGroupExtensionsKey] = <String>['*'];
} else {
if ((group.extensions?.isEmpty ?? true) &&
(group.mimeTypes?.isEmpty ?? true)) {
throw ArgumentError('Provided type group $group does not allow '
'all files, but does not set any of the Linux-supported filter '
'categories. "extensions" or "mimeTypes" must be non-empty for Linux '
'if anything is non-empty.');
}
if (group.extensions?.isNotEmpty ?? false) {
serialization[_typeGroupExtensionsKey] = group.extensions
return PlatformTypeGroup(
label: label,
extensions: <String>['*'],
);
}
if ((group.extensions?.isEmpty ?? true) &&
(group.mimeTypes?.isEmpty ?? true)) {
throw ArgumentError('Provided type group $group does not allow '
'all files, but does not set any of the Linux-supported filter '
'categories. "extensions" or "mimeTypes" must be non-empty for Linux '
'if anything is non-empty.');
}
return PlatformTypeGroup(
label: label,
// Covert to GtkFileFilter's *.<extension> format.
extensions: group.extensions
?.map((String extension) => '*.$extension')
.toList() ??
<String>[];
}
if (group.mimeTypes?.isNotEmpty ?? false) {
serialization[_typeGroupMimeTypesKey] = group.mimeTypes ?? <String>[];
}
}
return serialization;
<String>[],
mimeTypes: group.mimeTypes ?? <String>[]);
}
Loading

0 comments on commit 7c97c88

Please sign in to comment.