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

[file_selector_windows] Migrate native implementation to Dart. #6467

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/file_selector/file_selector_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 0.10.0

* Migrates CCP implementation to Dart.
* Updates minimum Flutter version to 2.10.

## 0.9.1+2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@
// found in the LICENSE file.

import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';

import 'src/file_selector.dart';
import 'src/messages.g.dart';

/// An implementation of [FileSelectorPlatform] for Windows.
class FileSelectorWindows extends FileSelectorPlatform {
final FileSelectorApi _hostApi = FileSelectorApi();
/// Constructor for FileSelectorWindows. It uses default parameters for the [FileSelector].
FileSelectorWindows() : this.withFileSelectorAPI(null);

/// Constructor for FileSelectorWindows. It receives a DartFileSelectorApi parameter allowing dependency injection.
FileSelectorWindows.withFileSelectorAPI(FileSelector? api)
: _api = api ?? FileSelector.withoutParameters();

final FileSelector _api;

/// Registers the Windows implementation.
/// Registers the Windows implementation. It uses default parameters for the [FileSelector].
static void registerWith() {
FileSelectorPlatform.instance = FileSelectorWindows();
}
Expand All @@ -21,15 +28,15 @@ class FileSelectorWindows extends FileSelectorPlatform {
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<String?> paths = await _hostApi.showOpenDialog(
SelectionOptions(
final List<String> paths = _api.getFiles(
selectionOptions: SelectionOptions(
allowMultiple: false,
selectFolders: false,
allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups),
),
initialDirectory,
confirmButtonText);
return paths.isEmpty ? null : XFile(paths.first!);
initialDirectory: initialDirectory,
confirmButtonText: confirmButtonText);
return paths.isEmpty ? null : XFile(paths.first);
}

@override
Expand All @@ -38,14 +45,14 @@ class FileSelectorWindows extends FileSelectorPlatform {
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<String?> paths = await _hostApi.showOpenDialog(
SelectionOptions(
final List<String?> paths = _api.getFiles(
selectionOptions: SelectionOptions(
allowMultiple: true,
selectFolders: false,
allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups),
),
initialDirectory,
confirmButtonText);
initialDirectory: initialDirectory,
confirmButtonText: confirmButtonText);
return paths.map((String? path) => XFile(path!)).toList();
}

Expand All @@ -56,32 +63,28 @@ class FileSelectorWindows extends FileSelectorPlatform {
String? suggestedName,
String? confirmButtonText,
}) async {
final List<String?> paths = await _hostApi.showSaveDialog(
SelectionOptions(
allowMultiple: false,
selectFolders: false,
allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups),
),
initialDirectory,
suggestedName,
confirmButtonText);
return paths.isEmpty ? null : paths.first!;
final String? path = _api.getSavePath(
initialDirectory: initialDirectory,
suggestedFileName: suggestedName,
confirmButtonText: confirmButtonText,
selectionOptions: SelectionOptions(
allowMultiple: false,
selectFolders: false,
allowedTypes: _typeGroupsFromXTypeGroups(acceptedTypeGroups),
),
);
return Future<String>.value(path);
}

@override
Future<String?> getDirectoryPath({
String? initialDirectory,
String? confirmButtonText,
}) async {
final List<String?> paths = await _hostApi.showOpenDialog(
SelectionOptions(
allowMultiple: false,
selectFolders: true,
allowedTypes: <TypeGroup>[],
),
initialDirectory,
confirmButtonText);
return paths.isEmpty ? null : paths.first!;
final String? path = _api.getDirectoryPath(
initialDirectory: initialDirectory,
confirmButtonText: confirmButtonText);
return Future<String>.value(path);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';

/// FileOpenDialogWrapper provides an abstraction to interact with IFileOpenDialog related methods.
class FileOpenDialogWrapper {
/// Sets the [options](https://pub.dev/documentation/win32/latest/winrt/FILEOPENDIALOGOPTIONS-class.html) given into an IFileOpenDialog.
int setOptions(int options, IFileOpenDialog dialog) {
return dialog.setOptions(options);
}

/// Returns the IFileOpenDialog's [options](https://pub.dev/documentation/win32/latest/winrt/FILEOPENDIALOGOPTIONS-class.html).
int getOptions(Pointer<Uint32> ptrOptions, IFileOpenDialog dialog) {
return dialog.getOptions(ptrOptions);
}

/// Sets confirmation button text on an IFileOpenDialog. If the [confirmationText] is null, 'Pick' will be used.
int setOkButtonLabel(String? confirmationText, IFileOpenDialog dialog) {
return dialog.setOkButtonLabel(TEXT(confirmationText ?? 'Pick'));
}

/// Sets the allowed file type extensions in an IFileOpenDialog.
int setFileTypes(
Map<String, String> filterSpecification, IFileOpenDialog dialog) {
int operationResult = 0;
using((Arena arena) {
final Pointer<COMDLG_FILTERSPEC> registerFilterSpecification =
arena<COMDLG_FILTERSPEC>(filterSpecification.length);

int index = 0;
for (final String key in filterSpecification.keys) {
registerFilterSpecification[index]
..pszName = TEXT(key)
..pszSpec = TEXT(filterSpecification[key]!);
index++;
}

operationResult = dialog.setFileTypes(
filterSpecification.length, registerFilterSpecification);
});

return operationResult;
}

/// Shows an IFileOpenDialog using the given owner.
int show(int hwndOwner, IFileOpenDialog dialog) {
return dialog.show(hwndOwner);
}

/// Release an IFileOpenDialog.
int release(IFileOpenDialog dialog) {
return dialog.release();
}

/// Return a result from an IFileOpenDialog.
int getResult(
Pointer<Pointer<COMObject>> ptrCOMObject, IFileOpenDialog dialog) {
return dialog.getResult(ptrCOMObject);
}

/// Return results from an IFileOpenDialog. This should be used when selecting multiple items.
int getResults(
Pointer<Pointer<COMObject>> ptrCOMObject, IFileOpenDialog dialog) {
return dialog.getResults(ptrCOMObject);
}

/// Sets the initial directory for an IFileOpenDialog.
int setFolder(Pointer<Pointer<COMObject>> ptrPath, IFileOpenDialog dialog) {
return dialog.setFolder(ptrPath.value);
}

/// Sets the suggested file name for an IFileOpenDialog.
int setFileName(String suggestedFileName, IFileOpenDialog dialog) {
return dialog.setFileName(TEXT(suggestedFileName));
}

/// Creates and [initializes](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-shcreateitemfromparsingname) a Shell item object from a parsing name.
/// If the directory doesn't exist it will return an error result.
int createItemFromParsingName(String initialDirectory, Pointer<GUID> ptrGuid,
Pointer<Pointer<NativeType>> ptrPath) {
return SHCreateItemFromParsingName(
TEXT(initialDirectory), nullptr, ptrGuid, ptrPath);
}

/// Initilaize the COM library with the internal [CoInitializeEx](https://pub.dev/documentation/win32/latest/winrt/CoInitializeEx.html) method.
/// It uses the following parameters:
/// pvReserved (Pointer): nullptr
/// dwCoInit (int): COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE
/// COINIT_APARTMENTTHREADED: Initializes the thread for apartment-threaded object concurrency.
/// COINIT_DISABLE_OLE1DDE: Disables Dynamic Data Exchange for Ole1 [support](https://learn.microsoft.com/en-us/windows/win32/learnwin32/initializing-the-com-library).
int coInitializeEx() {
return CoInitializeEx(
nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
}

/// Creates instance of FileOpenDialog.
IFileOpenDialog createInstance() {
return FileOpenDialog.createInstance();
}

/// Closes the COM library on the current thread, unloads all DLLs loaded by the thread, frees any other resources that the thread maintains, and forces all RPC connections on the thread to close.
void coUninitialize() {
CoUninitialize();
}
}
Loading