Skip to content

Commit 641e790

Browse files
authored
feat(share_plus)!: Migrate to package:web (#2709)
1 parent c7e56de commit 641e790

4 files changed

Lines changed: 239 additions & 32 deletions

File tree

packages/share_plus/share_plus/example/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ flutter:
2424
- assets/flutter_logo.png
2525

2626
environment:
27-
sdk: '>=2.18.0 <4.0.0'
27+
sdk: '>=3.3.0 <4.0.0'
28+
flutter: '>=3.19.0'

packages/share_plus/share_plus/lib/share_plus.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export 'package:share_plus_platform_interface/share_plus_platform_interface.dart
1212

1313
export 'src/share_plus_linux.dart';
1414
export 'src/share_plus_windows.dart'
15-
if (dart.library.html) 'src/share_plus_web.dart';
15+
if (dart.library.js_interop) 'src/share_plus_web.dart';
1616

1717
/// Plugin for summoning a platform share sheet.
1818
class Share {

packages/share_plus/share_plus/lib/src/share_plus_web.dart

Lines changed: 233 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import 'dart:html' as html;
1+
import 'dart:developer' as developer;
2+
import 'dart:js_interop';
23
import 'dart:typed_data';
4+
import 'dart:ui';
35

4-
import 'package:flutter/widgets.dart';
56
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
7+
import 'package:meta/meta.dart';
68
import 'package:mime/mime.dart' show lookupMimeType;
79
import 'package:share_plus_platform_interface/share_plus_platform_interface.dart';
810
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
911
import 'package:url_launcher_web/url_launcher_web.dart';
12+
import 'package:web/web.dart' as web
13+
show DOMException, File, FilePropertyBag, Navigator, window;
1014

1115
/// The web implementation of [SharePlatform].
1216
class SharePlusWebPlugin extends SharePlatform {
@@ -17,25 +21,91 @@ class SharePlusWebPlugin extends SharePlatform {
1721
SharePlatform.instance = SharePlusWebPlugin(UrlLauncherPlugin());
1822
}
1923

20-
final html.Navigator _navigator;
24+
final web.Navigator _navigator;
2125

2226
/// A constructor that allows tests to override the window object used by the plugin.
2327
SharePlusWebPlugin(
2428
this.urlLauncher, {
25-
@visibleForTesting html.Navigator? debugNavigator,
26-
}) : _navigator = debugNavigator ?? html.window.navigator;
29+
@visibleForTesting web.Navigator? debugNavigator,
30+
}) : _navigator = debugNavigator ?? web.window.navigator;
31+
32+
@override
33+
Future<void> shareUri(
34+
Uri uri, {
35+
Rect? sharePositionOrigin,
36+
}) async {
37+
final data = ShareData.url(
38+
url: uri.toString(),
39+
);
40+
41+
final bool canShare;
42+
try {
43+
canShare = _navigator.canShare(data);
44+
} on NoSuchMethodError catch (e) {
45+
developer.log(
46+
'Share API is not supported in this User Agent.',
47+
error: e,
48+
);
49+
50+
return;
51+
}
52+
53+
if (!canShare) {
54+
return;
55+
}
56+
57+
try {
58+
await _navigator.share(data).toDart;
59+
} on web.DOMException catch (e) {
60+
// Ignore DOMException
61+
developer.log(
62+
'Failed to share uri',
63+
error: '${e.name}: ${e.message}',
64+
);
65+
}
66+
}
2767

28-
/// Share text
2968
@override
3069
Future<void> share(
3170
String text, {
3271
String? subject,
3372
Rect? sharePositionOrigin,
3473
}) async {
74+
await shareWithResult(
75+
text,
76+
subject: subject,
77+
sharePositionOrigin: sharePositionOrigin,
78+
);
79+
}
80+
81+
@override
82+
Future<ShareResult> shareWithResult(
83+
String text, {
84+
String? subject,
85+
Rect? sharePositionOrigin,
86+
}) async {
87+
final ShareData data;
88+
if (subject != null && subject.isNotEmpty) {
89+
data = ShareData.textWithTitle(
90+
title: subject,
91+
text: text,
92+
);
93+
} else {
94+
data = ShareData.text(
95+
text: text,
96+
);
97+
}
98+
99+
final bool canShare;
35100
try {
36-
await _navigator.share({'title': subject, 'text': text});
37-
} on NoSuchMethodError catch (_) {
38-
//Navigator is not available or the webPage is not served on https
101+
canShare = _navigator.canShare(data);
102+
} on NoSuchMethodError catch (e) {
103+
developer.log(
104+
'Share API is not supported in this User Agent.',
105+
error: e,
106+
);
107+
108+
// Navigator is not available or the webPage is not served on https
39109
final queryParameters = {
40110
if (subject != null) 'subject': subject,
41111
'body': text,
@@ -57,18 +127,58 @@ class SharePlusWebPlugin extends SharePlatform {
57127
if (!launchResult) {
58128
throw Exception('Failed to launch $uri');
59129
}
130+
131+
return _resultUnavailable;
60132
}
133+
134+
if (!canShare) {
135+
return _resultUnavailable;
136+
}
137+
138+
try {
139+
await _navigator.share(data).toDart;
140+
141+
// actions is success, but can't get the action name
142+
return _resultUnavailable;
143+
} on web.DOMException catch (e) {
144+
if (e.name case 'AbortError') {
145+
return _resultDismissed;
146+
}
147+
148+
developer.log(
149+
'Failed to share text',
150+
error: '${e.name}: ${e.message}',
151+
);
152+
}
153+
154+
return _resultUnavailable;
61155
}
62156

63-
/// Share files
64157
@override
65158
Future<void> shareFiles(
66159
List<String> paths, {
67160
List<String>? mimeTypes,
68161
String? subject,
69162
String? text,
70163
Rect? sharePositionOrigin,
71-
}) {
164+
}) async {
165+
await shareFilesWithResult(
166+
paths,
167+
mimeTypes: mimeTypes,
168+
subject: subject,
169+
text: text,
170+
sharePositionOrigin: sharePositionOrigin,
171+
);
172+
}
173+
174+
@override
175+
Future<ShareResult> shareFilesWithResult(
176+
List<String> paths, {
177+
List<String>? mimeTypes,
178+
String? subject,
179+
String? text,
180+
Rect? sharePositionOrigin,
181+
}) async {
72182
final files = <XFile>[];
73183
for (var i = 0; i < paths.length; i++) {
74184
files.add(XFile(paths[i], mimeType: mimeTypes?[i]));
@@ -85,9 +195,7 @@ class SharePlusWebPlugin extends SharePlatform {
85195
///
86196
/// Remarks for the web implementation:
87197
/// This uses the [Web Share API](https://web.dev/web-share/) if it's
88-
/// available. Otherwise, uncaught Errors will be thrown.
89-
/// See [Can I Use - Web Share API](https://caniuse.com/web-share) to
90-
/// understand which browsers are supported. This builds on the
198+
/// available. This builds on the
91199
/// [`cross_file`](https://pub.dev/packages/cross_file) package.
92200
@override
93201
Future<ShareResult> shareXFiles(
@@ -96,29 +204,78 @@ class SharePlusWebPlugin extends SharePlatform {
96204
String? text,
97205
Rect? sharePositionOrigin,
98206
}) async {
99-
// See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share
100-
101-
final webFiles = <html.File>[];
207+
final webFiles = <web.File>[];
102208
for (final xFile in files) {
103209
webFiles.add(await _fromXFile(xFile));
104210
}
105-
await _navigator.share({
106-
if (subject?.isNotEmpty ?? false) 'title': subject,
107-
if (text?.isNotEmpty ?? false) 'text': text,
108-
if (webFiles.isNotEmpty) 'files': webFiles,
109-
});
211+
212+
final ShareData data;
213+
if (text != null && text.isNotEmpty) {
214+
if (subject != null && subject.isNotEmpty) {
215+
data = ShareData.filesWithTextAndTitle(
216+
files: webFiles.toJS,
217+
text: text,
218+
title: subject,
219+
);
220+
} else {
221+
data = ShareData.filesWithText(
222+
files: webFiles.toJS,
223+
text: text,
224+
);
225+
}
226+
} else if (subject != null && subject.isNotEmpty) {
227+
data = ShareData.filesWithTitle(
228+
files: webFiles.toJS,
229+
title: subject,
230+
);
231+
} else {
232+
data = ShareData.files(
233+
files: webFiles.toJS,
234+
);
235+
}
236+
237+
final bool canShare;
238+
try {
239+
canShare = _navigator.canShare(data);
240+
} on NoSuchMethodError catch (e) {
241+
developer.log(
242+
'Share API is not supported in this User Agent.',
243+
error: e,
244+
);
245+
246+
return _resultUnavailable;
247+
}
248+
249+
if (!canShare) {
250+
return _resultUnavailable;
251+
}
252+
253+
try {
254+
await _navigator.share(data).toDart;
255+
256+
// actions is success, but can't get the action name
257+
return _resultUnavailable;
258+
} on web.DOMException catch (e) {
259+
if (e.name case 'AbortError') {
260+
return _resultDismissed;
261+
}
262+
263+
developer.log(
264+
'Failed to share files',
265+
error: '${e.name}: ${e.message}',
266+
);
267+
}
110268

111269
return _resultUnavailable;
112270
}
113271

114-
static Future<html.File> _fromXFile(XFile file) async {
272+
static Future<web.File> _fromXFile(XFile file) async {
115273
final bytes = await file.readAsBytes();
116-
return html.File(
117-
[ByteData.sublistView(bytes)],
274+
return web.File(
275+
[bytes.buffer.toJS].toJS,
118276
file.name,
119-
{
120-
'type': file.mimeType ?? _mimeTypeForPath(file, bytes),
121-
},
277+
web.FilePropertyBag()
278+
..type = file.mimeType ?? _mimeTypeForPath(file, bytes),
122279
);
123280
}
124281

@@ -128,7 +285,55 @@ class SharePlusWebPlugin extends SharePlatform {
128285
}
129286
}
130287

288+
const _resultDismissed = ShareResult(
289+
'',
290+
ShareResultStatus.dismissed,
291+
);
292+
131293
const _resultUnavailable = ShareResult(
132294
'dev.fluttercommunity.plus/share/unavailable',
133295
ShareResultStatus.unavailable,
134296
);
297+
298+
extension on web.Navigator {
299+
/// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/canShare
300+
external bool canShare(ShareData data);
301+
302+
/// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share
303+
external JSPromise share(ShareData data);
304+
}
305+
306+
extension type ShareData._(JSObject _) implements JSObject {
307+
external factory ShareData.text({
308+
String text,
309+
});
310+
311+
external factory ShareData.textWithTitle({
312+
String text,
313+
String title,
314+
});
315+
316+
external factory ShareData.files({
317+
JSArray<web.File> files,
318+
});
319+
320+
external factory ShareData.filesWithText({
321+
JSArray<web.File> files,
322+
String text,
323+
});
324+
325+
external factory ShareData.filesWithTitle({
326+
JSArray<web.File> files,
327+
String title,
328+
});
329+
330+
external factory ShareData.filesWithTextAndTitle({
331+
JSArray<web.File> files,
332+
String text,
333+
String title,
334+
});
335+
336+
external factory ShareData.url({
337+
String url,
338+
});
339+
}

packages/share_plus/share_plus/pubspec.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies:
3939
url_launcher_linux: ^3.0.5
4040
url_launcher_platform_interface: ^2.1.2
4141
ffi: ^2.0.1
42+
web: ^0.5.0
4243

4344
# win32 is compatible across v4 and v5 for Win32 only (not COM)
4445
win32: ">=4.0.0 <6.0.0"
@@ -49,5 +50,5 @@ dev_dependencies:
4950
flutter_lints: ">=2.0.1 <4.0.0"
5051

5152
environment:
52-
sdk: ">=2.18.0 <4.0.0"
53-
flutter: ">=3.3.0"
53+
sdk: ">=3.3.0 <4.0.0"
54+
flutter: ">=3.19.0"

0 commit comments

Comments
 (0)