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

Commit 25b65a5

Browse files
committed
example: save and read Utf-16 LE files
fix api to return empty list on cancel bump file_selector_platform_interface fix reentrance issue in SaveTextFile widget getPathForShellItem throws on error replace import rename variable
1 parent 319248a commit 25b65a5

File tree

9 files changed

+87
-52
lines changed

9 files changed

+87
-52
lines changed

packages/file_selector/file_selector_windows/example/lib/open_text_page.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:typed_data';
6+
57
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
68
import 'package:flutter/material.dart';
79

@@ -23,7 +25,14 @@ class OpenTextPage extends StatelessWidget {
2325
return;
2426
}
2527
final String fileName = file.name;
26-
final String fileContent = await file.readAsString();
28+
29+
// This behavior defaults works when reading files encoded using UTF-16 LE.
30+
// If you have files encoded with UTF-8 you can simply use file.readAsString()
31+
// For other encodings, consider using Encoding.getByName() method, to get your enconder
32+
// before calling file.readAsString()
33+
final Uint8List bytes = await file.readAsBytes();
34+
final Uint16List utf16CodeUnits = bytes.buffer.asUint16List();
35+
final String fileContent = String.fromCharCodes(utf16CodeUnits);
2736

2837
await showDialog<void>(
2938
context: context,

packages/file_selector/file_selector_windows/example/lib/save_text_page.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ class SaveTextPage extends StatelessWidget {
2525
return;
2626
}
2727
final String text = _contentController.text;
28-
final Uint8List fileData = Uint8List.fromList(text.codeUnits);
28+
29+
// This behavior saves an Utf-16 LE encoded file.
30+
final Uint16List fileData = Uint16List.fromList(text.codeUnits);
2931
const String fileMimeType = 'text/plain';
30-
final XFile textFile =
31-
XFile.fromData(fileData, mimeType: fileMimeType, name: fileName);
32+
final XFile textFile = XFile.fromData(fileData.buffer.asUint8List(),
33+
mimeType: fileMimeType, name: fileName);
3234
await textFile.saveTo(path);
3335
}
3436

packages/file_selector/file_selector_windows/example/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ environment:
88
flutter: ">=3.0.0"
99

1010
dependencies:
11-
file_selector_platform_interface: ^2.2.0
11+
file_selector_platform_interface: ^2.3.0
1212
file_selector_windows:
1313
# When depending on this package from a real application you should use:
1414
# file_selector_windows: ^x.y.z

packages/file_selector/file_selector_windows/lib/file_selector_windows.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import 'src/file_selector_dart/selection_options.dart';
1515
/// An implementation of [FileSelectorPlatform] for Windows.
1616
class FileSelectorWindows extends FileSelectorPlatform {
1717
/// Creates a new instance of [FileSelectorApi].
18-
FileSelectorWindows(int foregroundWindowHandle)
18+
FileSelectorWindows()
1919
: _hostApi = FileSelectorApi(
2020
DialogWrapperFactory(
2121
FileDialogControllerFactory(),
2222
IFileDialogFactory(),
2323
),
24-
foregroundWindowHandle);
24+
GetActiveWindow());
2525

2626
/// Creates a fake implementation of [FileSelectorApi] for testing purposes.
2727
@visibleForTesting
@@ -31,7 +31,7 @@ class FileSelectorWindows extends FileSelectorPlatform {
3131

3232
/// Registers the Windows implementation.
3333
static void registerWith() {
34-
FileSelectorPlatform.instance = FileSelectorWindows(GetForegroundWindow());
34+
FileSelectorPlatform.instance = FileSelectorWindows();
3535
}
3636

3737
@override

packages/file_selector/file_selector_windows/lib/src/file_selector_api.dart

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import 'file_selector_dart/selection_options.dart';
1313
class FileSelectorApi {
1414
/// Creates a new instance of [FileSelectorApi].
1515
/// Allows Dependency Injection of a [DialogWrapperFactory] to handle dialog creation.
16-
FileSelectorApi(this._dialogWrapperFactory, this._foregroundWindow);
16+
FileSelectorApi(this._dialogWrapperFactory, this._activeWindow);
1717

1818
final DialogWrapperFactory _dialogWrapperFactory;
1919

20-
final int _foregroundWindow;
20+
final int _activeWindow;
2121

2222
/// Displays a dialog window to open one or more files.
2323
List<String?> showOpenDialog(
@@ -26,7 +26,7 @@ class FileSelectorApi {
2626
String? confirmButtonText,
2727
) =>
2828
_showDialog(
29-
_foregroundWindow,
29+
_activeWindow,
3030
DialogMode.open,
3131
options,
3232
initialDirectory,
@@ -42,7 +42,7 @@ class FileSelectorApi {
4242
String? confirmButtonText,
4343
) =>
4444
_showDialog(
45-
_foregroundWindow,
45+
_activeWindow,
4646
DialogMode.save,
4747
options,
4848
initialDirectory,
@@ -90,9 +90,6 @@ class FileSelectorApi {
9090

9191
final List<String?>? files = dialogWrapper.show(parentWindow);
9292
dialogWrapper.release();
93-
if (files != null) {
94-
return files;
95-
}
96-
throw WindowsException(E_FAIL);
93+
return files ?? <String>[];
9794
}
9895
}

packages/file_selector/file_selector_windows/lib/src/file_selector_dart/dialog_wrapper.dart

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'dart:ffi';
77

88
import 'package:ffi/ffi.dart';
99
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
10-
import 'package:flutter/cupertino.dart';
10+
import 'package:flutter/foundation.dart';
1111
import 'package:win32/win32.dart';
1212

1313
import 'dialog_mode.dart';
@@ -181,34 +181,40 @@ class DialogWrapper {
181181
Pointer<Pointer<COMObject>> shellItemArrayPtr,
182182
Pointer<Uint32> shellItemCountPtr,
183183
Pointer<Pointer<COMObject>> shellItemPtr) {
184-
final List<String> files = <String>[];
185-
if (_isOpenDialog) {
186-
_lastResult = _dialogController.getResults(shellItemArrayPtr);
187-
if (!SUCCEEDED(_lastResult)) {
188-
return null;
189-
}
184+
try {
185+
final List<String> files = <String>[];
186+
int lastOperationResult;
187+
if (_isOpenDialog) {
188+
lastOperationResult = _dialogController.getResults(shellItemArrayPtr);
189+
if (!SUCCEEDED(lastOperationResult)) {
190+
throw WindowsException(lastOperationResult);
191+
}
190192

191-
final IShellItemArray shellItemResources =
192-
IShellItemArray(shellItemArrayPtr.cast());
193-
_lastResult = shellItemResources.getCount(shellItemCountPtr);
194-
if (!SUCCEEDED(_lastResult)) {
195-
return null;
196-
}
197-
for (int index = 0; index < shellItemCountPtr.value; index += 1) {
198-
shellItemResources.getItemAt(index, shellItemPtr);
193+
final IShellItemArray shellItemResources =
194+
IShellItemArray(shellItemArrayPtr.cast());
195+
lastOperationResult = shellItemResources.getCount(shellItemCountPtr);
196+
if (!SUCCEEDED(lastOperationResult)) {
197+
throw WindowsException(lastOperationResult);
198+
}
199+
for (int index = 0; index < shellItemCountPtr.value; index += 1) {
200+
shellItemResources.getItemAt(index, shellItemPtr);
201+
final IShellItem shellItem = IShellItem(shellItemPtr.cast());
202+
files.add(_shellWin32Api.getPathForShellItem(shellItem));
203+
_shellWin32Api.releaseShellItem(shellItem);
204+
}
205+
} else {
206+
lastOperationResult = _dialogController.getResult(shellItemPtr);
207+
if (!SUCCEEDED(lastOperationResult)) {
208+
throw WindowsException(lastOperationResult);
209+
}
199210
final IShellItem shellItem = IShellItem(shellItemPtr.cast());
200211
files.add(_shellWin32Api.getPathForShellItem(shellItem));
201212
_shellWin32Api.releaseShellItem(shellItem);
202213
}
203-
} else {
204-
_lastResult = _dialogController.getResult(shellItemPtr);
205-
if (!SUCCEEDED(_lastResult)) {
206-
return null;
207-
}
208-
final IShellItem shellItem = IShellItem(shellItemPtr.cast());
209-
files.add(_shellWin32Api.getPathForShellItem(shellItem));
210-
_shellWin32Api.releaseShellItem(shellItem);
214+
return files;
215+
} on WindowsException catch (ex) {
216+
_lastResult = ex.hr;
217+
return null;
211218
}
212-
return files;
213219
}
214220
}

packages/file_selector/file_selector_windows/lib/src/file_selector_dart/shell_win32_api.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class ShellWin32Api {
3232
);
3333

3434
if (!SUCCEEDED(operationResult)) {
35-
return '';
35+
throw WindowsException(operationResult);
3636
}
3737
return ptrPath.value.toDartString();
3838
});

packages/file_selector/file_selector_windows/test/file_selector_api_test.dart

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,16 @@ void main() {
6868
.called(1);
6969
});
7070

71-
test('should throw an exception if file list is null', () {
71+
test('should return an empty list if operation is cancelled', () {
7272
// Arrange
7373
when(mockDialogWrapper.show(parentWindow)).thenReturn(null);
7474

75-
// Act - Assert
76-
expect(
77-
() => fileSelectorApi.showSaveDialog(emptyOptions, null, null, null),
78-
throwsA(const TypeMatcher<WindowsException>()));
75+
// Act
76+
final List<String?> result =
77+
fileSelectorApi.showSaveDialog(emptyOptions, null, null, null);
78+
79+
// Assert
80+
expect(result.isEmpty, true);
7981
});
8082
});
8183
group('showOpenDialog', () {
@@ -88,14 +90,16 @@ void main() {
8890
.called(1);
8991
});
9092

91-
test('should throw an exception if file list is null', () {
93+
test('should return an empty list if operation is cancelled', () {
9294
// Arrange
9395
when(mockDialogWrapper.show(parentWindow)).thenReturn(null);
9496

95-
// Act - Assert
96-
expect(
97-
() => fileSelectorApi.showOpenDialog(emptyOptions, null, null),
98-
throwsA(const TypeMatcher<WindowsException>()));
97+
// Act
98+
final List<String?> result =
99+
fileSelectorApi.showOpenDialog(emptyOptions, null, null);
100+
101+
// Assert
102+
expect(result.isEmpty, true);
99103
});
100104
});
101105
group('Common behavior', () {

packages/file_selector/file_selector_windows/test/file_selector_dart/dialog_wrapper_test.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,24 @@ void main() {
272272
verify(mockFileDialogController.getResult(any)).called(1);
273273
});
274274

275-
test('[DialogMode == Save] show should the selected directory for', () {
275+
test(
276+
'[DialogMode == Save] show should return null if cannot getPathForShellItem',
277+
() {
278+
final DialogWrapper dialogWrapperModeSave =
279+
DialogWrapper.withFakeDependencies(
280+
mockFileDialogController, DialogMode.save, mockShellWin32Api);
281+
const int parentWindow = 0;
282+
when(mockFileDialogController.show(parentWindow)).thenReturn(S_OK);
283+
when(mockFileDialogController.getResult(any)).thenReturn(S_OK);
284+
when(mockShellWin32Api.getPathForShellItem(any))
285+
.thenThrow(WindowsException(E_FAIL));
286+
287+
final List<String?>? result = dialogWrapperModeSave.show(parentWindow);
288+
289+
expect(result, null);
290+
});
291+
292+
test('[DialogMode == Save] show should return the file path', () {
276293
const String filePath = 'path/to/file.txt';
277294
final DialogWrapper dialogWrapperModeSave =
278295
DialogWrapper.withFakeDependencies(

0 commit comments

Comments
 (0)