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

fix PR 970 #1422

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/share/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ Then invoke the static `share` method anywhere in your Dart code
``` dart
Share.share('check out my website https://example.com');
```

To share a file invoke the static `shareFile` method anywhere in your Dart code
``` dart
Share.shareFile(File('${directory.path}/image.jpg'));
```
11 changes: 8 additions & 3 deletions packages/share/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath 'com.android.tools.build:gradle:3.3.2'
}
}

Expand All @@ -35,8 +35,8 @@ rootProject.allprojects {
apply plugin: 'com.android.library'

android {
compileSdkVersion 27

compileSdkVersion 28
defaultConfig {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand All @@ -45,3 +45,8 @@ android {
disable 'InvalidPackage'
}
}

dependencies {
// use support-compat starting from 28.0.0
implementation 'androidx.core:core:1.0.0'
}
2 changes: 1 addition & 1 deletion packages/share/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx1536M
14 changes: 13 additions & 1 deletion packages/share/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.share">
package="io.flutter.plugins.share">

<application>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.flutter.share_provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/flutter_share_file_paths" />
</provider>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
package io.flutter.plugins.share;

import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import java.io.*;
import java.util.Map;

/** Plugin method host for presenting a share sheet via Intent */
Expand All @@ -29,15 +34,36 @@ private SharePlugin(Registrar registrar) {

@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
if (call.method.equals("share")) {
if (!(call.arguments instanceof Map)) {
throw new IllegalArgumentException("Map argument expected");
}
// Android does not support showing the share sheet at a particular point on screen.
share((String) call.argument("text"));
result.success(null);
} else {
result.notImplemented();
switch (call.method) {
case "share":
expectMapArguments(call);
// Android does not support showing the share sheet at a particular point on screen.
share((String) call.argument("text"));
result.success(null);
break;
case "shareFile":
expectMapArguments(call);
// Android does not support showing the share sheet at a particular point on screen.
try {
shareFile(
(String) call.argument("path"),
(String) call.argument("mimeType"),
(String) call.argument("subject"),
(String) call.argument("text"));
result.success(null);
} catch (IOException e) {
result.error(e.getMessage(), null, null);
}
break;
default:
result.notImplemented();
break;
}
}

private void expectMapArguments(MethodCall call) throws IllegalArgumentException {
if (!(call.arguments instanceof Map)) {
throw new IllegalArgumentException("Map argument expected");
}
}

Expand All @@ -58,4 +84,95 @@ private void share(String text) {
mRegistrar.context().startActivity(chooserIntent);
}
}

private void shareFile(String path, String mimeType, String subject, String text)
throws IOException {
if (path == null || path.isEmpty()) {
throw new IllegalArgumentException("Non-empty path expected");
}

File file = new File(path);
clearExternalShareFolder();
if (!fileIsOnExternal(file)) {
file = copyToExternalShareFolder(file);
}

Uri fileUri =
FileProvider.getUriForFile(
mRegistrar.context(),
mRegistrar.context().getPackageName() + ".flutter.share_provider",
file);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
if (subject != null) shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
if (text != null) shareIntent.putExtra(Intent.EXTRA_TEXT, text);
shareIntent.setType(mimeType != null ? mimeType : "*/*");
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Intent chooserIntent = Intent.createChooser(shareIntent, null /* dialog title optional */);
if (mRegistrar.activity() != null) {
mRegistrar.activity().startActivity(chooserIntent);
} else {
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mRegistrar.context().startActivity(chooserIntent);
}
}

private boolean fileIsOnExternal(File file) {
try {
String filePath = file.getCanonicalPath();
File externalDir = Environment.getExternalStorageDirectory();
return externalDir != null && filePath.startsWith(externalDir.getCanonicalPath());
} catch (IOException e) {
return false;
}
}

@SuppressWarnings("ResultOfMethodCallIgnored")
private void clearExternalShareFolder() {
File folder = getExternalShareFolder();
if (folder.exists()) {
for (File file : folder.listFiles()) {
file.delete();
}
folder.delete();
}
}

@SuppressWarnings("ResultOfMethodCallIgnored")
private File copyToExternalShareFolder(File file) throws IOException {
File folder = getExternalShareFolder();
if (!folder.exists()) {
folder.mkdirs();
}

File newFile = new File(folder, file.getName());
copy(file, newFile);
return newFile;
}

@NonNull
private File getExternalShareFolder() {
return new File(mRegistrar.context().getExternalCacheDir(), "share");
}

private static void copy(File src, File dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
} finally {
out.close();
}
} finally {
in.close();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external" path="."/>
<external-files-path name="external_files" path="."/>
<external-cache-path name="external_cache" path="."/>
</paths>
71 changes: 58 additions & 13 deletions packages/share/ios/Classes/SharePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,19 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
binaryMessenger:registrar.messenger];

[shareChannel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
NSDictionary *arguments = [call arguments];
NSNumber *originX = arguments[@"originX"];
NSNumber *originY = arguments[@"originY"];
NSNumber *originWidth = arguments[@"originWidth"];
NSNumber *originHeight = arguments[@"originHeight"];

CGRect originRect;
if (originX != nil && originY != nil && originWidth != nil && originHeight != nil) {
originRect = CGRectMake([originX doubleValue], [originY doubleValue],
[originWidth doubleValue], [originHeight doubleValue]);
}

if ([@"share" isEqualToString:call.method]) {
NSDictionary *arguments = [call arguments];
NSString *shareText = arguments[@"text"];

if (shareText.length == 0) {
Expand All @@ -25,18 +36,27 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
return;
}

NSNumber *originX = arguments[@"originX"];
NSNumber *originY = arguments[@"originY"];
NSNumber *originWidth = arguments[@"originWidth"];
NSNumber *originHeight = arguments[@"originHeight"];
[self share:@[ shareText ]
withController:[UIApplication sharedApplication].keyWindow.rootViewController
atSource:originRect];
result(nil);
} else if ([@"shareFile" isEqualToString:call.method]) {
NSString *path = arguments[@"path"];
NSString *mimeType = arguments[@"mimeType"];
NSString *subject = arguments[@"subject"];
NSString *text = arguments[@"text"];

CGRect originRect;
if (originX != nil && originY != nil && originWidth != nil && originHeight != nil) {
originRect = CGRectMake([originX doubleValue], [originY doubleValue],
[originWidth doubleValue], [originHeight doubleValue]);
if (path.length == 0) {
result([FlutterError errorWithCode:@"error"
message:@"Non-empty path expected"
details:nil]);
return;
}

[self share:shareText
[self shareFile:path
withMimeType:mimeType
withSubject:subject
withText:text
withController:[UIApplication sharedApplication].keyWindow.rootViewController
atSource:originRect];
result(nil);
Expand All @@ -46,17 +66,42 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
}];
}

+ (void)share:(id)sharedItems
+ (void)share:(NSArray *)shareItems
withController:(UIViewController *)controller
atSource:(CGRect)origin {
UIActivityViewController *activityViewController =
[[UIActivityViewController alloc] initWithActivityItems:@[ sharedItems ]
applicationActivities:nil];
[[UIActivityViewController alloc] initWithActivityItems:shareItems applicationActivities:nil];
activityViewController.popoverPresentationController.sourceView = controller.view;
if (!CGRectIsEmpty(origin)) {
activityViewController.popoverPresentationController.sourceRect = origin;
}
[controller presentViewController:activityViewController animated:YES completion:nil];
}

+ (void)shareFile:(id)path
withMimeType:(id)mimeType
withSubject:(NSString *)subject
withText:(NSString *)text
withController:(UIViewController *)controller
atSource:(CGRect)origin {
NSMutableArray *items = [[NSMutableArray alloc] init];

if (subject != nil && subject.length != 0) {
[items addObject:subject];
}
if (text != nil && text.length != 0) {
[items addObject:text];
}

if ([mimeType hasPrefix:@"image/"]) {
UIImage *image = [UIImage imageWithContentsOfFile:path];
[items addObject:image];
} else {
NSURL *url = [NSURL fileURLWithPath:path];
[items addObject:url];
}

[self share:items withController:controller atSource:origin];
}

@end
Loading