1- import 'dart:html' as html;
1+ import 'dart:developer' as developer;
2+ import 'dart:js_interop' ;
23import 'dart:typed_data' ;
4+ import 'dart:ui' ;
35
4- import 'package:flutter/widgets.dart' ;
56import 'package:flutter_web_plugins/flutter_web_plugins.dart' ;
7+ import 'package:meta/meta.dart' ;
68import 'package:mime/mime.dart' show lookupMimeType;
79import 'package:share_plus_platform_interface/share_plus_platform_interface.dart' ;
810import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart' ;
911import '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] .
1216class 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+
131293const _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+ }
0 commit comments