Skip to content

Commit 8e051b4

Browse files
feat(share_plus)!: Native share UI for Windows (#1158)
* ci: enable share_plus integration tests for windows * fix: windows support in share_plus example * test: update share_plus_windows tests * feat(share_plus)!: native share dialog for windows * Platform specific implementation for using DataTransferManager on Windows * DataTransferManager is only used on (& available for) Windows 10 builds 17763 or higher. * On older Windows versions, existing old implementation is used i.e. launching mailto: URIs with subject & body query parameters. * Getting the share result is not supported on Windows. Thus, shareWithResult & shareFilesWithResult always return ShareResultStatus.unavailable. * This platform specific implementation beings supports for all existing methods available in package:share_plus to Windows, namely: share, shareFiles, shareWithResult, shareFilesWithResult, shareXFiles. BREAKING CHANGE: New Windows implementation
1 parent 4dac782 commit 8e051b4

File tree

17 files changed

+1190
-40
lines changed

17 files changed

+1190
-40
lines changed

.github/workflows/share_plus.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,6 @@ jobs:
165165
run: ./.github/workflows/scripts/build-examples.sh windows ./lib/main.dart
166166

167167
windows_integration_test:
168-
if: false # TODO: enable this if it is possible to register a mailto protocol handler on github hosted runners
169168
runs-on: windows-latest
170169
timeout-minutes: 30
171170
steps:

packages/share_plus/share_plus/example/lib/main.dart

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44

55
// ignore_for_file: public_member_api_docs
66

7+
import 'dart:io';
78
import 'package:flutter/material.dart';
8-
import 'package:image_picker/image_picker.dart';
99
import 'package:share_plus/share_plus.dart';
10+
import 'package:image_picker/image_picker.dart';
1011

1112
import 'image_previews.dart';
13+
import 'utils/file_picker_win.dart'
14+
if (dart.library.html) 'utils/file_picker_web.dart';
1215

1316
void main() {
1417
runApp(const DemoApp());
@@ -67,15 +70,27 @@ class DemoAppState extends State<DemoApp> {
6770
leading: const Icon(Icons.add),
6871
title: const Text('Add image'),
6972
onTap: () async {
70-
final imagePicker = ImagePicker();
71-
final pickedFile = await imagePicker.pickImage(
72-
source: ImageSource.gallery,
73-
);
74-
if (pickedFile != null) {
75-
setState(() {
76-
imagePaths.add(pickedFile.path);
77-
imageNames.add(pickedFile.name);
78-
});
73+
// Using `package:image_picker` to get image from gallery.
74+
if (Platform.isWindows) {
75+
// Using `package:filepicker_windows` on Windows, since `package:image_picker` is not supported.
76+
final path = await pickFile();
77+
if (path != null) {
78+
setState(() {
79+
imagePaths.add(path);
80+
imageNames.add(path.split('\\').last);
81+
});
82+
}
83+
} else {
84+
final imagePicker = ImagePicker();
85+
final pickedFile = await imagePicker.pickImage(
86+
source: ImageSource.gallery,
87+
);
88+
if (pickedFile != null) {
89+
setState(() {
90+
imagePaths.add(pickedFile.path);
91+
imageNames.add(pickedFile.name);
92+
});
93+
}
7994
}
8095
},
8196
),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/// Dummy implementation for non `dart.library.io` platforms.
2+
Future<String?> pickFile() async {
3+
throw UnimplementedError();
4+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import 'package:filepicker_windows/filepicker_windows.dart';
2+
3+
/// Picks a file from the file system on Windows & returns it's path as [String].
4+
Future<String?> pickFile() async {
5+
final picker = OpenFilePicker()
6+
..filterSpecification = {'Images': '*.jpg;*.jpeg;*.png;*.gif'};
7+
final result = picker.getFile();
8+
return result?.path;
9+
}

packages/share_plus/share_plus/example/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ dependencies:
77
share_plus:
88
path: ../
99
image_picker: ^0.8.4
10+
filepicker_windows: ^2.0.2
1011

1112
dependency_overrides:
1213
share_plus_linux:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Defines the Chromium style for automatic reformatting.
2+
# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
3+
BasedOnStyle: Chromium
4+
# This defaults to 'Auto'. Explicitly set it for a while, so that
5+
# 'vector<vector<int> >' in existing files gets formatted to
6+
# 'vector<vector<int>>'. ('Auto' means that clang-format will only use
7+
# 'int>>' if the file already contains at least one such instance.)
8+
Standard: Cpp11
9+
SortIncludes: true
10+
---
11+
Language: ObjC
12+
ColumnLimit: 100

packages/share_plus/share_plus_windows/lib/share_plus_windows.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ library share_plus_windows;
33

44
import 'dart:ui';
55

6+
import 'package:share_plus_windows/src/version_helper.dart';
67
import 'package:url_launcher/url_launcher.dart';
78
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';
89

9-
/// The Windows implementation of SharePlatform.
10+
/// The fallback Windows implementation of [SharePlatform], for older Windows versions.
11+
///
1012
class ShareWindows extends SharePlatform {
11-
/// Register this dart class as the platform implementation for linux
13+
/// If the modern Share UI i.e. `DataTransferManager` is not available, then use this Dart class instead of platform specific implementation.
14+
///
1215
static void registerWith() {
13-
SharePlatform.instance = ShareWindows();
16+
if (!VersionHelper.instance.isWindows10RS5OrGreater) {
17+
SharePlatform.instance = ShareWindows();
18+
}
1419
}
1520

1621
/// Share text.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import 'dart:io';
2+
import 'dart:ffi';
3+
import 'package:ffi/ffi.dart';
4+
import 'package:win32/win32.dart';
5+
6+
class VersionHelper {
7+
static VersionHelper instance = VersionHelper._();
8+
9+
/// Whether the current OS is Windows 10 Redstone 5 or later.
10+
/// This is used to determine whether the modern Share UI i.e. `DataTransferManager` is available or not.
11+
///
12+
/// References: https://en.wikipedia.org/wiki/Windows_10_version_history
13+
/// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa
14+
///
15+
bool isWindows10RS5OrGreater = false;
16+
17+
static const int _kWindows10RS5BuildNumber = 17763;
18+
19+
VersionHelper._() {
20+
if (Platform.isWindows) {
21+
final pointer = calloc<OSVERSIONINFOEX>();
22+
pointer.ref
23+
..dwOSVersionInfoSize = sizeOf<OSVERSIONINFOEX>()
24+
..dwBuildNumber = 0
25+
..dwMajorVersion = 0
26+
..dwMinorVersion = 0
27+
..dwPlatformId = 0
28+
..szCSDVersion = ''
29+
..wServicePackMajor = 0
30+
..wServicePackMinor = 0
31+
..wSuiteMask = 0
32+
..wProductType = 0
33+
..wReserved = 0;
34+
final rtlGetVersion = DynamicLibrary.open('ntdll.dll').lookupFunction<
35+
Void Function(Pointer<OSVERSIONINFOEX>),
36+
void Function(Pointer<OSVERSIONINFOEX>)>('RtlGetVersion');
37+
rtlGetVersion(pointer);
38+
isWindows10RS5OrGreater =
39+
pointer.ref.dwBuildNumber >= _kWindows10RS5BuildNumber;
40+
calloc.free(pointer);
41+
}
42+
}
43+
}

packages/share_plus/share_plus_windows/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ dependencies:
1414
sdk: flutter
1515
meta: ^1.7.0
1616
url_launcher: ^6.1.2
17+
ffi: ^2.0.1
18+
win32: ^2.3.8
1719

1820
dev_dependencies:
1921
flutter_test:
@@ -23,8 +25,6 @@ dev_dependencies:
2325

2426
flutter:
2527
plugin:
26-
implements: share_plus
2728
platforms:
2829
windows:
29-
dartPluginClass: ShareWindows
30-
pluginClass: none
30+
pluginClass: SharePlusWindowsPluginCApi

packages/share_plus/share_plus_windows/test/share_plus_windows_test.dart

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,72 @@
11
import 'package:flutter_test/flutter_test.dart';
2-
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';
32
import 'package:share_plus_windows/share_plus_windows.dart';
3+
import 'package:share_plus_windows/src/version_helper.dart';
4+
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';
5+
import 'package:share_plus_platform_interface/method_channel/method_channel_share.dart';
6+
47
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
58
import 'package:url_launcher_platform_interface/link.dart';
69

710
void main() {
8-
test('registered instance', () {
9-
ShareWindows.registerWith();
10-
expect(SharePlatform.instance, isA<ShareWindows>());
11-
});
12-
test('url encoding is correct for &', () async {
13-
final mock = MockUrlLauncherPlatform();
14-
UrlLauncherPlatform.instance = mock;
11+
test(
12+
'registered instance',
13+
() {
14+
ShareWindows.registerWith();
15+
expect(SharePlatform.instance, isA<ShareWindows>());
16+
},
17+
skip: VersionHelper.instance.isWindows10RS5OrGreater,
18+
);
19+
20+
test(
21+
'registered instance',
22+
() {
23+
ShareWindows.registerWith();
24+
expect(SharePlatform.instance, isA<MethodChannelShare>());
25+
},
26+
skip: !VersionHelper.instance.isWindows10RS5OrGreater,
27+
);
28+
29+
// These tests are only valid on Windows versions lower than 10.0.17763.0.
30+
31+
test(
32+
'url encoding is correct for &',
33+
() async {
34+
final mock = MockUrlLauncherPlatform();
35+
UrlLauncherPlatform.instance = mock;
1536

16-
await ShareWindows().share('foo&bar', subject: 'bar&foo');
37+
await ShareWindows().share('foo&bar', subject: 'bar&foo');
1738

18-
expect(mock.url, 'mailto:?subject=bar%26foo&body=foo%26bar');
19-
});
39+
expect(mock.url, 'mailto:?subject=bar%26foo&body=foo%26bar');
40+
},
41+
skip: VersionHelper.instance.isWindows10RS5OrGreater,
42+
);
2043

2144
// see https://github.com/dart-lang/sdk/issues/43838#issuecomment-823551891
22-
test('url encoding is correct for spaces', () async {
23-
final mock = MockUrlLauncherPlatform();
24-
UrlLauncherPlatform.instance = mock;
45+
test(
46+
'url encoding is correct for spaces',
47+
() async {
48+
final mock = MockUrlLauncherPlatform();
49+
UrlLauncherPlatform.instance = mock;
2550

26-
await ShareWindows().share('foo bar', subject: 'bar foo');
51+
await ShareWindows().share('foo bar', subject: 'bar foo');
2752

28-
expect(mock.url, 'mailto:?subject=bar%20foo&body=foo%20bar');
29-
});
53+
expect(mock.url, 'mailto:?subject=bar%20foo&body=foo%20bar');
54+
},
55+
skip: VersionHelper.instance.isWindows10RS5OrGreater,
56+
);
3057

31-
test('throws when url_launcher can\'t launch uri', () async {
32-
final mock = MockUrlLauncherPlatform();
33-
mock.canLaunchMockValue = false;
34-
UrlLauncherPlatform.instance = mock;
58+
test(
59+
'throws when url_launcher can\'t launch uri',
60+
() async {
61+
final mock = MockUrlLauncherPlatform();
62+
mock.canLaunchMockValue = false;
63+
UrlLauncherPlatform.instance = mock;
3564

36-
expect(() async => await ShareWindows().share('foo bar'), throwsException);
37-
});
65+
expect(
66+
() async => await ShareWindows().share('foo bar'), throwsException);
67+
},
68+
skip: VersionHelper.instance.isWindows10RS5OrGreater,
69+
);
3870
}
3971

4072
class MockUrlLauncherPlatform extends UrlLauncherPlatform {

0 commit comments

Comments
 (0)