From 4ce55751171197e57676cc51c9ce20adce7317ff Mon Sep 17 00:00:00 2001 From: Dipak Prajapati Date: Mon, 24 Oct 2022 13:20:39 +0530 Subject: [PATCH] feat(share_plus): share XFile created using File.fromData() (#1284) Co-authored-by: Miguel Beltran --- .../example/assets/flutter_logo.png | Bin 0 -> 7231 bytes .../integration_test/share_plus_test.dart | 11 ++++ .../share_plus/example/lib/main.dart | 32 +++++++++++ .../share_plus/example/pubspec.yaml | 3 + .../method_channel/method_channel_share.dart | 53 ++++++++++++++++-- .../pubspec.yaml | 2 + .../share_plus_platform_interface_test.dart | 5 +- 7 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 packages/share_plus/share_plus/example/assets/flutter_logo.png diff --git a/packages/share_plus/share_plus/example/assets/flutter_logo.png b/packages/share_plus/share_plus/example/assets/flutter_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cfc0ee42f375540248aeb0710a9c89144469b338 GIT binary patch literal 7231 zcmX9@bwE?!7oT)DN`q`5poD~k5+bmW93b7Hv~)=-A-##9bc_~|ZcqUcP^r-ZQX->~ zF_HSceSd%KZF}#Y&pqed&pr2Z?|Z4Q3#F&wq5*+G^l+F45(FZF1HUV&D1d9*$oy^K zi~1?d%ohZrWg&j9j4ybkS5hbw%fbcrF2 z`1_Z1F71(u3GB>$8)iKDDA}h`|Gvv0bKeVoV|V>1zrV~6G?DBY-%E;&Nzm1gsMFCM z&odwz>;cye^b4CE;?4vix+{NDJLW4E7qdGKLIS-Ov*o=Ozdlp_k~Y)Q(cqg%2;J)n zW9{?4WjkO6y=G3h*S(+I9A9g-+4xI&>*j*W#5v>NA}l{|hoi^)^@idqJxAp`AP}yg zKX5F?PhfKSM1+h5R8SWWp?yY&5xBHsf`KBnz-S+PGHOs{h#=ZWL?-Q45*yxk+n$6P z1QHxHvVx{jK|r9Z%y0_gH!c-6t|4#*G)mX|EWcrkY@C&L$vrXXv2ltbDPR%F#{nOD zBAQ0DjuaL^OADldKu`DtUC5j0F>*lPpyy0*QlM9m>@^gYGKvhO*1!x`CfrTmX+^pzNbJIrix+OpfN8ztX9gfl zM`vKfNI-PsUtnBpFjB22&!7ONt--3`OJ)EtO|w{V&{&|~Gp?8z49(~p*r5yAp#1}) zH5-N*)yKvS0*xAVNbh z1VZ#5jTJ;wkRvk;AbJCDi7p|GPGFVef*#2nU}8sRCE!6VfnbFMV$Od?EmwxK){~)! z$>%esz|1gqme}P+CVPu-`MZ zI$uwL_M&a3RL-EonACV#!HJlu2u<`iwR+JJBMTN-hfN1gvy74+1frwW-jWD&`r zv(~1>43MC|!gRgf*Zbl>+vz|JWOpVesCu8ae4mCIVDR`S5|ZEwnkY=78p+QJ@8>!O zT?vrueG=blZ=lm&22OYw&x#-)|L2ApK1dGj$O7w56=a6NX01QII>@0M0D(&Oh0&F4 zFf*bvn3LudQjpY}YZD%t6i73oca_ygFfpaHAr4Iz;t;$x!TDKCl87b>Fr_BHBc{2$ zeOwD&$pO2WELfp7M`WRXScwKQ`Tx{+{S*u__D3BT82mS6|Gc^25+s9oS#BbGl@%>o zcUJ71Y6h~2UEziYVaV5{6-y#4#HU^CDrgKVtkf-)_|Vp(5KLriIPp(b;d>P3UDTQz z51vbt$m=I5>Vl#V1=deYRRL2_g0nLE5e=p@ln>x$WGQX$(_$ujqw~2p|CX%tSk-y- zdsM|=DDY^GEeq#Eb;bc`$D)*ffSA-sRYTwuxaD~v*K)AWpS%~H|z53lPyGCBe z*>ZZl4EUaozLhXulaG*yohk>}7(0=XiB_e@+|oZFfORwU1=Fi3!3qaNe8h6%Xz0xlm1-?DP%@sDm_$0`4Ux!bh&-JP*-l~@?%z12D zKCUyS@{Xe`k)m_Dwd2k7aFuJstN)U*+_xF3eC?(W6v+ak_0Bm#g3Vfal!<9p(FeP{ zVpuJor(j31N@o;_)ZfNE5@}L-$hSe?%ztBA1+Gbk)CMTP1=Atr6T6)@u2cs&eWmI5 z>_aiJvP3Ef>IOQ229gO3qT1{lcUKZKos#rIm_HI;+M7KxSz$tr&&>H^Ld38fx~8SwK{&<*fol zN=j#FRK3hmj^MN*n&OfHV&M@ojPiq$_b^&0QHs7=UV`Ur$4c zENaw8x)tZ>=s3@je1N14F%)?ME)=-CPP=DE$rhL9F_b@aqr#7XD~8jP%55BQXr4P- zcFmQ{xwmNvuU{tAQ=6pTsMkPFsU3!}U$De)`lCNI zn`hR$HFI1eWi=P>VVl+Gs3ZstP1dJR!dL^Oro#iX=t;p&mY!K)+)CHg#ov@WMqwX* zDr&ogckP}N#^u#R42lI_DHdyVkLhUuWng9jD`Zz{2$l(6c@U1P-g9!kCC#asWpFoc zZ#d*R7Js00WmX>TD6lZmhK*myZL|W)kSuC9+n6J^Ju^?ia*eyFoh^3PcQh=iXlXQb zupvXsIw`<%;7j7rlLCf-{Y7sByZMSg^FS(z=A&8VtE%#wWhZ>|B7B*zRDo4UOp%NwrJVq@md$yznhT53MWIi5$-)b95TCo zv_F2}hZdRC3}X@HJBr%~w_B7P%a+c~4&?`Ce%l*ey3XS366g}f7#qq z^xnw6bCH`1)fA-b6?>-O7Ya`_k5ZC8!OnQgd93-7Rb*RE7f&H~Mmsk>+ar<16KLPlhS0H_d%JUQ6u3xDeIN?JkM1z4Zm%_R@T7X9i~Uf3NNuE@T}@TmglZKTQ2yqdDhw>TnSZ$=@IEOr&>BEiC%> z?)}?R_Uh}`yH^h}*GiNd*ae>`dN#ke*!<~n^GH1z!b-FRcdLnQ{uqNQYU^N~>6vmEE_(0^@=OZb#bDCZsn=)LqSRC>L< zJx`ybdDR!%C8fJ24=q6Hyoh$Y^e6TLv(f}?1Ap5VLE@i9U@8?Txqusf8pflEDDNMG z%6dIAf+#ARJoXUYR7%&@N*;H;2Fuv<8lBD!J~ZL)+R94ws`CSizEiuQ$KtIiJ~aYI zVgjUWqjK_`TQd&1D22dEr}5}C466P6obpg57yljvnl0~&@iV@T_9N)YbiLU_3?)Mh z)I?@!ipioJQ`ATz)i<@qwP(4Z&X_KK1CH3@jMU|VE#FBQv_^_Pf)e#j3Lz_(=Kf=0 zb0GhO10ZqCcrpgD{nV?K;mffQA}7CU`F@1RgUtoTJZ4|-XW!*I-3Rov2DGM{YTWGw z)^nX-MIC)}lRP(?>Q%q4VjVSs+gULv5cKP{En8-@Vox&9F+9zq$F{>Q#ag#w*pfBLH_Q##tHUL@h!MZ6^Fu3zA+gzJtjuOdGj zcXuJ<`1N#ccbM_bzqF5`C4DKRnjImmV{erQO$07qcgj_jcuK80o7=sF+BS>E`Ci&)t(| zCa7*>3|+}P?WtRBrsX=o3S=8+0C`DQh+)-qsUg|ikttggBo&<lr-SzDJ-5JBhBSH8YmCzE2D2ky(?m-qKF-X=y-oikadwa+r@PPao^Ut02Ph+I*^zja<}@RLf#~*uiy0wNF-wt#==#&7J_R2?lJCGea%mO z&`!X+&K$I#vP*8-mh^Jt9TffLDq=vwlGXh{F0I{(Mu>!3Ex@w7D7Hvs+^W3uI^6?n zTE3eQB14u79*gjXo@Ch$KZcs*iySuorWi-$WB0fbS)qKuR)gbyc@sEGFJ(GL zS>>g0BfXOzbL%^`(6`hsKyIZo>uPvxh8HD1d>iy`6vpy64jmZfSbyNef++jdu%t!s zrh*h4b10WfF6dS5xc{U6W44Tf7>Oi%x?3#+5^k3Lo~{c1j>T6YZ7RqIrP^qX>ie^} zkA(#s?WjgMx(8ZUmDeF?47W`eP8&t21q9-Fi`5q<1R#B4=<~w3-&j}N?hQ-j&^b(R zfQcb9)P`h2AQWM^YCREVSfuK`Q+U{q=MHLh&a!)9(R*?EV*K!%d7Xo^^3pjGaPhiUHqNo=Ae9{K&hUp%e(77SjoamJ(1- z$;jFPI5;ixD}!rZ<#+_bq!`#wlWG|FnOb!|isR8aJjNGV_q=jV&hJ!NSeBv-oDLfc z79#GQR#;X=s&iKjy~tP!Gzm9M`Bm=AE-wonYWWB#<3XI$g9shNzlcDi z8U_oF))4TL$5U=a+~&6}P4e)mRrml>yasOa78WvmG$^mCcXSd zt0Tn47w#5)9Mm0*@q9XEhxssA##=0sCCj0Mt|8ckp=mHK&s|pLo*`>lP!M3+%dA-3 z*{%AmfZ&$J1GDBAw#?P)EX?+ds^mMb@bx zE`e4mC%?h~@d(NEPH!IFZ5NS?@(&$&hB=Yy*is8n~iX>2!dqTodFqJ~+4ba(y&`J9fF7 zCRyNZ&EBEQ+_yEwH}9@V3l$%>E_u3hra`xV?CoC3|Dw2<&tk`qj@|l`B;i$0_whU;ZuTEa? zowgCZD+4|Zc=K;E{=zo3s>o9oc6I{TjzgE*J^JnOfk=XO_qD}CC3IWDWN*deW5bn{p36%$ORrph1JQk3p>$LWshCX>-4&Zqu0_+ z)b`&ITJ7T5>OeCqRx=B6qU|{Nnx9!~Up;)-;ivktM2jY{YV6Zjtl(}}>_D4eXS2Mw zc2@x7;M1?(CNc(WZ9KWYp9sU~KEs~S#mV_S zq02V$K27L|#rZ4oi2e0x6`zfci1mG@s5#WMH?Wlx-muyAxy*3(<9#!mT| zzr3o!`?x=GE2qObx?9dHFu}Jhbbb9Jc|FI=I@1@*nW7*c{o-rh5rzf|7?@>W1yko(nr`@Kql|gBiKup;A}V zmVd|av9H01-8;{%v~jV9w`-JMGgIBa3Mp$#aPaa=U&7Fsk4$h}StHvPn zU?1Iz^I$P%K6b_uunrZ(pr7`~;@^v%9XG!4eX;4nbf+U2B;P*!;-b=zx!O;L6VbzR z0M5SH=AT|y(Jv^(W}m$&D_rvz5W>diY<`aG-dE1Z{4!Q6lAtB7PA^z&Z{a7}^t{k}@6-iK zw%;Afrft;ycoh<6X%Xd+aVn5J>5mGKe4PX-RCj?!0eS_hPCo9CtCo=IS!)azOV3km z*#F|RcmD6;Q7T8IDMN_?LGaxmoz`45QNOoQ5}p(K%)>%*BzK}wbBof&Q| zjN=F~Zn(W@dU()e$S@IAkQx)(Q>K-+wlm3#(MqpZ_PiBgb^JB4ECL~N<=5Ji=&a9w z)v3!4TK~Q5s5fM((3f5q>-6(s6vy*u*L<>}kvkWLzHSbc-U+r$esRY+q}rkc3@?{M z+!c>O@BT)~hOYhoA$j3&JI(%tpBm3Ew|^abJwQG%b60po#AL2u9o6%>;b_xi@6K8334J<5)DmuLcekEhSn{F zJm1~%?-jWnx;A#AD?t9yhm72Nqv1Sv3CA|El20B*SE$Br^sg>dHApPoI&Aw`HBauY zI-9$?$E|oLO03Pc8GCp0b+a-|fOek#gw!2Ef?`p?yFBbH-t-UNaE^;fkscH9Bv1eJ zbY_O|#muk5ppH8DgtJLAvYM8$BWOprYI+xjYqG$08xBlb8=7}~-@u#(mj4-hSFn`r z#~yZ(XegemKlBItm{C>f@^4^|eCN^Fg>7B9Bc*uks6A83rTf8B<^$2;Hpb|mGY_Y0 zucw0&vFjS2v68QHQyD9*UY%Me6xSd#?Kx$FdKdFUz!E}dSpPZnMnUNH6>!B~$0mNo z2X2Lg%qq{zW}z3x$|jw35Wy$ti(r1q~r&^h+Be-u%OP3czzBc3xM)atMz zhCXemaMjrW3Bym>gVOj|<#GSbM9;%c-eWo8NkX_r*j{L`1fIv;=FK#Xe+6sZC&GP< zM3Zj;JYRpfH~^}e9z&0U?m!dI6q958R!x$SL(qTL7^K7)k*l{51nG#VSnoY1t8 zFt_MPW+4#BvfLl?GN&*-=dG?lSZK(>t4Qe(=~(2AH1nt@Ez#?8a5MhU4=pkJzzK9v zQ!2!c39%_72nc*H9bga43q5}z%CuA1FNKc6+}vt;PHbkH91lU=PH!LS8_XY8YY#NV zTs0#wl&CevSv>T1L=Y_Q1F`DoeAYm7Fd@3We;CZ?&x^q2Ig-KStqZZ%lPo*Sg}MUk z%BjNgCU>cMy7r@+ET{law_S}OFO5W$Rc$pYSYd&IsRA!`(TyhbFUi@VO4;hi4j~gq z-~MapW(k33hoArYY232pw?w-$8VY8CNy35Vte##;xfSNaWPC0+4$bs^wv8*rN>vA^}N%yfBQEqDr9rqWPv9qX!Xm+2-!!Ce}t;5(BahCw)L^Z zJ2_AyOgs!J(YEsIXP+@RKbtn1aYpGz4Qa)IH!A`J;;39F_td#}@kg(3YghH`V`1}O z%f-_nUxS4iYXNwsYVg?_*plrU2o$D)99mU9)p+*tJWfUSR`kf5A1Zo0FV802&Y6ff zw(Zq%iM6eYK?jv4i_(%SQp3|5$}z#7H3#PoIN(^GUSk}zRok(eaiY*&v1Br%;7^{+ zhi^+(m`hnTX}Mgtx6%_ed0R8CwZu_8~WZw>BSx@Cx1(sh?=>kBezeLKxZz>!# z9i-ARHmQi;dZ);uc-5Jf9jZtQZ|`I$Q}sAsHu)}dj}CbI5(OLj+26Iw??L{t>~Jx4 zYYaRVflUNhW=pQ!;u19IANb6tkT=4J5Wv@OAYZx&0{woDg3veEgl-JPn$JSh54Edb zu8_i|ihvwPXrN1P6*_`bc*u||hXPD5fRlKucnIlpbwr#uDeHkZzzzW?r;zDj=( zk_>25h>ts*DL)hoT{V5LMGYDaiiQn&?c_hhd(GATHO;&0!wXYh5lIF-lFbu9Z%fe& z>1m~g0CfqJ4275#u_A&;fUojs73~yG`=| literal 0 HcmV?d00001 diff --git a/packages/share_plus/share_plus/example/integration_test/share_plus_test.dart b/packages/share_plus/share_plus/example/integration_test/share_plus_test.dart index 0da8c7f5d1..f50553ef13 100644 --- a/packages/share_plus/share_plus/example/integration_test/share_plus_test.dart +++ b/packages/share_plus/share_plus/example/integration_test/share_plus_test.dart @@ -6,6 +6,8 @@ import 'dart:io'; +import 'package:file_selector/file_selector.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:share_plus/share_plus.dart'; import 'package:integration_test/integration_test.dart'; @@ -24,4 +26,13 @@ void main() { testWidgets('Can launch shareWithResult', (WidgetTester tester) async { expect(Share.shareWithResult('message', subject: 'title'), isNotNull); }); + + testWidgets('Can shareXFile created using File.fromData()', + (WidgetTester tester) async { + final bytes = Uint8List.fromList([1, 2, 3, 4, 5, 6, 7, 8]); + final XFile file = + XFile.fromData(bytes, name: 'image.jpg', mimeType: 'image/jpeg'); + + expect(Share.shareXFiles([file], text: "example"), isNotNull); + }); } diff --git a/packages/share_plus/share_plus/example/lib/main.dart b/packages/share_plus/share_plus/example/lib/main.dart index c46eba0e3e..024c956e90 100644 --- a/packages/share_plus/share_plus/example/lib/main.dart +++ b/packages/share_plus/share_plus/example/lib/main.dart @@ -8,6 +8,7 @@ import 'dart:io'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:share_plus/share_plus.dart'; @@ -123,6 +124,17 @@ class DemoAppState extends State { ); }, ), + const Padding(padding: EdgeInsets.only(top: 12.0)), + Builder( + builder: (BuildContext context) { + return ElevatedButton( + onPressed: () { + _onShareXFileFromAssets(context); + }, + child: const Text('Share XFile from Assets'), + ); + }, + ), ], ), ), @@ -185,4 +197,24 @@ class DemoAppState extends State { content: Text("Share result: ${result.status}"), )); } + + void _onShareXFileFromAssets(BuildContext context) async { + final box = context.findRenderObject() as RenderBox?; + final scaffoldMessenger = ScaffoldMessenger.of(context); + final data = await rootBundle.load('assets/flutter_logo.png'); + final buffer = data.buffer; + final result = await Share.shareXFiles( + [ + XFile.fromData( + buffer.asUint8List(data.offsetInBytes, data.lengthInBytes), + name: 'flutter_logo.png', + mimeType: 'image/png'), + ], + sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + ); + + scaffoldMessenger.showSnackBar(SnackBar( + content: Text("Share result: ${result.status}"), + )); + } } diff --git a/packages/share_plus/share_plus/example/pubspec.yaml b/packages/share_plus/share_plus/example/pubspec.yaml index 38a5462f3b..badaf5dd61 100644 --- a/packages/share_plus/share_plus/example/pubspec.yaml +++ b/packages/share_plus/share_plus/example/pubspec.yaml @@ -25,5 +25,8 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/flutter_logo.png + environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/share_plus/share_plus_platform_interface/lib/method_channel/method_channel_share.dart b/packages/share_plus/share_plus_platform_interface/lib/method_channel/method_channel_share.dart index a32614f560..8bbed87007 100644 --- a/packages/share_plus/share_plus_platform_interface/lib/method_channel/method_channel_share.dart +++ b/packages/share_plus/share_plus_platform_interface/lib/method_channel/method_channel_share.dart @@ -3,6 +3,8 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; + // Keep dart:ui for retrocompatiblity with Flutter <3.3.0 // ignore: unnecessary_import import 'dart:ui'; @@ -10,8 +12,10 @@ import 'dart:ui'; import 'package:cross_file/cross_file.dart'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart' show visibleForTesting; -import 'package:mime/mime.dart' show lookupMimeType; +import 'package:mime/mime.dart' show extensionFromMime, lookupMimeType; import 'package:share_plus_platform_interface/share_plus_platform_interface.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:uuid/uuid.dart'; /// Plugin for summoning a platform share sheet. class MethodChannelShare extends SharePlatform { @@ -141,12 +145,15 @@ class MethodChannelShare extends SharePlatform { String? subject, String? text, Rect? sharePositionOrigin, - }) { - final mimeTypes = - files.map((e) => e.mimeType ?? _mimeTypeForPath(e.path)).toList(); + }) async { + final filesWithPath = await _getFiles(files); + + final mimeTypes = filesWithPath + .map((e) => e.mimeType ?? _mimeTypeForPath(e.path)) + .toList(); return shareFilesWithResult( - files.map((e) => e.path).toList(), + filesWithPath.map((e) => e.path).toList(), mimeTypes: mimeTypes, subject: subject, text: text, @@ -154,6 +161,42 @@ class MethodChannelShare extends SharePlatform { ); } + /// if file doesn't contain path + /// then make new file in TemporaryDirectory and return with path + /// + /// the system will automatically delete files in this + /// TemporaryDirectory as disk space is needed elsewhere on the device + Future> _getFiles(List files) async { + if (files.any((element) => element.path.isEmpty)) { + final newFiles = []; + + final String tempPath = (await getTemporaryDirectory()).path; + + const uuid = Uuid(); + for (final XFile element in files) { + if (element.path.isEmpty) { + final name = uuid.v4(); + + final extension = + extensionFromMime(element.mimeType ?? 'octet-stream'); + + final path = '$tempPath/$name.$extension'; + final file = File(path); + + await file.writeAsBytes(await element.readAsBytes()); + + newFiles.add(XFile(path)); + } else { + newFiles.add(element); + } + } + + return newFiles; + } else { + return files; + } + } + static String _mimeTypeForPath(String path) { return lookupMimeType(path) ?? 'application/octet-stream'; } diff --git a/packages/share_plus/share_plus_platform_interface/pubspec.yaml b/packages/share_plus/share_plus_platform_interface/pubspec.yaml index 6edf55167f..90c09d66ea 100644 --- a/packages/share_plus/share_plus_platform_interface/pubspec.yaml +++ b/packages/share_plus/share_plus_platform_interface/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: meta: ^1.7.0 mime: ^1.0.2 plugin_platform_interface: ^2.0.0 + path_provider: ^2.0.11 + uuid: ^3.0.6 dev_dependencies: flutter_test: diff --git a/packages/share_plus/share_plus_platform_interface/test/share_plus_platform_interface_test.dart b/packages/share_plus/share_plus_platform_interface/test/share_plus_platform_interface_test.dart index 1d18348b50..3a0bfa2b33 100644 --- a/packages/share_plus/share_plus_platform_interface/test/share_plus_platform_interface_test.dart +++ b/packages/share_plus/share_plus_platform_interface/test/share_plus_platform_interface_test.dart @@ -163,10 +163,7 @@ void main() { () => SharePlatform.instance.shareFilesWithResult(['']), throwsA(const TypeMatcher()), ); - expect( - () => sharePlatform.shareXFiles([XFile('')]), - throwsA(const TypeMatcher()), - ); + verifyZeroInteractions(mockChannel); });