diff --git a/packages/share_plus/share_plus/CHANGELOG.md b/packages/share_plus/share_plus/CHANGELOG.md index d103573cf0..ac6f708106 100644 --- a/packages/share_plus/share_plus/CHANGELOG.md +++ b/packages/share_plus/share_plus/CHANGELOG.md @@ -1,3 +1,10 @@ +## 3.1.0 + +- Android: Migrate to Kotlin +- Android: Update dependencies, build config updates +- Example: Fix project title +- Example: Set min Flutter version to 1.20.0 + ## 3.0.5 - Fix example embedding issue diff --git a/packages/share_plus/share_plus/README.md b/packages/share_plus/share_plus/README.md index 7484d9a596..f9a32ab6b7 100644 --- a/packages/share_plus/share_plus/README.md +++ b/packages/share_plus/share_plus/README.md @@ -1,16 +1,16 @@ +# Share plugin + [![Flutter Community: share_plus](https://fluttercommunity.dev/_github/header/share_plus)](https://github.com/fluttercommunity/community) +[![share_plus](https://github.com/fluttercommunity/plus_plugins/actions/workflows/share_plus.yaml/badge.svg)](https://github.com/fluttercommunity/plus_plugins/actions/workflows/share_plus.yaml) [![pub package](https://img.shields.io/pub/v/share_plus.svg)](https://pub.dev/packages/share_plus) -

-

build
-

-# Share plugin +build A Flutter plugin to share content from your Flutter app via the platform's share dialog. -Wraps the ACTION_SEND Intent on Android and UIActivityViewController +Wraps the `ACTION_SEND` Intent on Android and `UIActivityViewController` on iOS. ## Platform Support @@ -62,6 +62,3 @@ Check out our documentation website to learn more. [Plus plugins documentation]( ### Mobile platforms (Android and iOS) Due to restrictions set up by Facebook this plugin isn't capable of sharing data reliably to Facebook related apps on Android and iOS. This includes eg. sharing text to the Facebook Messenger. If you require this functionality please check the native Facebook Sharing SDK ([https://developers.facebook.com/docs/sharing](https://developers.facebook.com/docs/sharing)) or search for other Flutter plugins implementing this SDK. More information can be found in [this issue](https://github.com/fluttercommunity/plus_plugins/issues/413). - - -**Important:** As of January 2021, the Flutter team is no longer accepting non-critical PRs for the original set of plugins in `flutter/plugins`, and instead they should be submitted in this project. [You can read more about this announcement here.](https://github.com/flutter/plugins/blob/master/CONTRIBUTING.md#important-note) as well as [in the Flutter 2 announcement blog post.](https://medium.com/flutter/whats-new-in-flutter-2-0-fe8e95ecc65) diff --git a/packages/share_plus/share_plus/android/build.gradle b/packages/share_plus/share_plus/android/build.gradle index 1ffb74eb60..4f9ee5dee7 100644 --- a/packages/share_plus/share_plus/android/build.gradle +++ b/packages/share_plus/share_plus/android/build.gradle @@ -2,38 +2,42 @@ group 'dev.fluttercommunity.plus.share' version '1.0-SNAPSHOT' buildscript { - repositories { - google() - mavenCentral() - } + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } - dependencies { - classpath 'com.android.tools.build:gradle:7.0.2' - } + dependencies { + classpath 'com.android.tools.build:gradle:7.1.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } rootProject.allprojects { - repositories { - google() - mavenCentral() - } + repositories { + google() + mavenCentral() + } } apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { - compileSdkVersion 31 + compileSdkVersion 31 - defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } - dependencies { - implementation 'androidx.core:core:1.6.0' - implementation 'androidx.annotation:annotation:1.2.0' - } + dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.annotation:annotation:1.3.0' + } } diff --git a/packages/share_plus/share_plus/android/gradle/wrapper/gradle-wrapper.properties b/packages/share_plus/share_plus/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..41dfb87909 --- /dev/null +++ b/packages/share_plus/share_plus/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/share_plus/share_plus/android/settings.gradle b/packages/share_plus/share_plus/android/settings.gradle index 64350ae697..82e4e954fa 100644 --- a/packages/share_plus/share_plus/android/settings.gradle +++ b/packages/share_plus/share_plus/android/settings.gradle @@ -1 +1 @@ -rootProject.name = 'share' +rootProject.name = 'share_plus' diff --git a/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/MethodCallHandler.java b/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/MethodCallHandler.java deleted file mode 100644 index d06a87f832..0000000000 --- a/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/MethodCallHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package dev.fluttercommunity.plus.share; - -import androidx.annotation.NonNull; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import java.io.*; -import java.util.List; -import java.util.Map; - -/** Handles the method calls for the plugin. */ -class MethodCallHandler implements MethodChannel.MethodCallHandler { - - private final Share share; - - MethodCallHandler(Share share) { - this.share = share; - } - - @Override - @SuppressWarnings("unchecked") - public void onMethodCall(MethodCall call, @NonNull MethodChannel.Result result) { - switch (call.method) { - case "share": - expectMapArguments(call); - // Android does not support showing the share sheet at a particular point on screen. - share.share((String) call.argument("text"), (String) call.argument("subject")); - result.success(null); - break; - case "shareFiles": - expectMapArguments(call); - - // Android does not support showing the share sheet at a particular point on screen. - try { - share.shareFiles( - (List) call.argument("paths"), - (List) call.argument("mimeTypes"), - (String) call.argument("text"), - (String) call.argument("subject")); - 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"); - } - } -} diff --git a/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/Share.java b/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/Share.java deleted file mode 100644 index 44990f14b5..0000000000 --- a/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/Share.java +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package dev.fluttercommunity.plus.share; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.core.content.FileProvider; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -/** Handles share intent. */ -class Share { - - private final Context context; - private Activity activity; - - private final String providerAuthority; - - /** - * Constructs a Share object. The {@code context} and {@code activity} are used to start the share - * intent. The {@code activity} might be null when constructing the {@link Share} object and set - * to non-null when an activity is available using {@link #setActivity(Activity)}. - */ - Share(Context context, Activity activity) { - this.context = context; - this.activity = activity; - - this.providerAuthority = getContext().getPackageName() + ".flutter.share_provider"; - } - - /** - * Sets the activity when an activity is available. When the activity becomes unavailable, use - * this method to set it to null. - */ - void setActivity(Activity activity) { - this.activity = activity; - } - - void share(String text, String subject) { - if (text == null || text.isEmpty()) { - throw new IllegalArgumentException("Non-empty text expected"); - } - - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, text); - shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject); - shareIntent.setType("text/plain"); - Intent chooserIntent = Intent.createChooser(shareIntent, null /* dialog title optional */); - startActivity(chooserIntent); - } - - void shareFiles(List paths, List mimeTypes, String text, String subject) - throws IOException { - if (paths == null || paths.isEmpty()) { - throw new IllegalArgumentException("Non-empty path expected"); - } - - clearShareCacheFolder(); - ArrayList fileUris = getUrisForPaths(paths); - - Intent shareIntent = new Intent(); - if (fileUris.isEmpty()) { - share(text, subject); - return; - } else if (fileUris.size() == 1) { - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, fileUris.get(0)); - shareIntent.setType( - !mimeTypes.isEmpty() && mimeTypes.get(0) != null ? mimeTypes.get(0) : "*/*"); - } else { - shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE); - shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, fileUris); - shareIntent.setType(reduceMimeTypes(mimeTypes)); - } - if (text != null) shareIntent.putExtra(Intent.EXTRA_TEXT, text); - if (subject != null) shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - Intent chooserIntent = Intent.createChooser(shareIntent, null /* dialog title optional */); - - List resInfoList = - getContext() - .getPackageManager() - .queryIntentActivities(chooserIntent, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - for (Uri fileUri : fileUris) { - getContext() - .grantUriPermission( - packageName, - fileUri, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - } - - startActivity(chooserIntent); - } - - private void startActivity(Intent intent) { - if (activity != null) { - activity.startActivity(intent); - } else if (context != null) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } else { - throw new IllegalStateException("Both context and activity are null"); - } - } - - private ArrayList getUrisForPaths(List paths) throws IOException { - ArrayList uris = new ArrayList<>(paths.size()); - - for (String path : paths) { - File file = new File(path); - if (fileIsInShareCache(file)) { - // If file was saved in '.../caches/share_plus' it will have been erased by 'clearShareCacheFolder();' - throw new IOException( - "File to share not allowed to be located in '" - + getShareCacheFolder().getCanonicalPath() - + "'"); - } - file = copyToShareCacheFolder(file); - - uris.add(FileProvider.getUriForFile(getContext(), providerAuthority, file)); - } - - return uris; - } - - private String reduceMimeTypes(List mimeTypes) { - if (mimeTypes.size() > 1) { - String reducedMimeType = mimeTypes.get(0); - for (int i = 1; i < mimeTypes.size(); i++) { - String mimeType = mimeTypes.get(i); - if (!reducedMimeType.equals(mimeType)) { - if (getMimeTypeBase(mimeType).equals(getMimeTypeBase(reducedMimeType))) { - reducedMimeType = getMimeTypeBase(mimeType) + "/*"; - } else { - reducedMimeType = "*/*"; - break; - } - } - } - return reducedMimeType; - } else if (mimeTypes.size() == 1) { - return mimeTypes.get(0); - } else { - return "*/*"; - } - } - - @NonNull - private String getMimeTypeBase(String mimeType) { - if (mimeType == null || !mimeType.contains("/")) { - return "*"; - } - - return mimeType.substring(0, mimeType.indexOf("/")); - } - - private boolean fileIsInShareCache(File file) { - try { - String filePath = file.getCanonicalPath(); - return filePath.startsWith(getShareCacheFolder().getCanonicalPath()); - } catch (IOException e) { - return false; - } - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private void clearShareCacheFolder() { - File folder = getShareCacheFolder(); - final File[] files = folder.listFiles(); - if (folder.exists() && files != null) { - for (File file : files) { - file.delete(); - } - folder.delete(); - } - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - private File copyToShareCacheFolder(File file) throws IOException { - File folder = getShareCacheFolder(); - if (!folder.exists()) { - folder.mkdirs(); - } - - File newFile = new File(folder, file.getName()); - copy(file, newFile); - return newFile; - } - - @NonNull - private File getShareCacheFolder() { - return new File(getContext().getCacheDir(), "share_plus"); - } - - private Context getContext() { - if (activity != null) { - return activity; - } - if (context != null) { - return context; - } - - throw new IllegalStateException("Both context and activity are null"); - } - - private static void copy(File src, File dst) throws IOException { - try (InputStream in = new FileInputStream(src)) { - try (OutputStream out = new FileOutputStream(dst)) { - // Transfer bytes from in to out - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - } - } - } -} diff --git a/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/ShareFileProvider.java b/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/ShareFileProvider.java deleted file mode 100644 index cea5ae018e..0000000000 --- a/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/ShareFileProvider.java +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package dev.fluttercommunity.plus.share; - -import androidx.core.content.FileProvider; - -/** - * Providing a custom {@code FileProvider} prevents manifest {@code } name collisions. - * - *

See https://developer.android.com/guide/topics/manifest/provider-element.html for details. - */ -public class ShareFileProvider extends FileProvider {} diff --git a/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/SharePlusPlugin.java b/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/SharePlusPlugin.java deleted file mode 100644 index d10509ae47..0000000000 --- a/packages/share_plus/share_plus/android/src/main/java/dev/fluttercommunity/plus/share/SharePlusPlugin.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2019 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package dev.fluttercommunity.plus.share; - -import androidx.annotation.NonNull; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.MethodChannel; - -/** Plugin method host for presenting a share sheet via Intent */ -public class SharePlusPlugin implements FlutterPlugin, ActivityAware { - - private static final String CHANNEL = "dev.fluttercommunity.plus/share"; - private Share share; - private MethodChannel methodChannel; - - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - methodChannel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL); - share = new Share(binding.getApplicationContext(), null); - MethodCallHandler handler = new MethodCallHandler(share); - methodChannel.setMethodCallHandler(handler); - } - - @Override - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - methodChannel.setMethodCallHandler(null); - methodChannel = null; - share = null; - } - - @Override - public void onAttachedToActivity(ActivityPluginBinding binding) { - share.setActivity(binding.getActivity()); - } - - @Override - public void onDetachedFromActivity() { - share.setActivity(null); - } - - @Override - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - onAttachedToActivity(binding); - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - onDetachedFromActivity(); - } -} diff --git a/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/MethodCallHandler.kt b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/MethodCallHandler.kt new file mode 100644 index 0000000000..331a201b21 --- /dev/null +++ b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/MethodCallHandler.kt @@ -0,0 +1,45 @@ +package dev.fluttercommunity.plus.share + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import java.io.IOException + +/** Handles the method calls for the plugin. */ +internal class MethodCallHandler(private val share: Share) : MethodChannel.MethodCallHandler { + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "share" -> { + expectMapArguments(call) + // Android does not support showing the share sheet at a particular point on screen. + share.share( + call.argument("text") as String, + call.argument("subject") as String?, + ) + result.success(null) + } + "shareFiles" -> { + expectMapArguments(call) + + // Android does not support showing the share sheet at a particular point on screen. + try { + share.shareFiles( + call.argument>("paths")!!, + call.argument?>("mimeTypes"), + call.argument("text"), + call.argument("subject"), + ) + result.success(null) + } catch (e: IOException) { + result.error(e.message, null, null) + } + } + else -> result.notImplemented() + } + } + + @Throws(IllegalArgumentException::class) + private fun expectMapArguments(call: MethodCall) { + require(call.arguments is Map<*, *>) { "Map arguments expected" } + } +} diff --git a/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/Share.kt b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/Share.kt new file mode 100644 index 0000000000..539dbc37d5 --- /dev/null +++ b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/Share.kt @@ -0,0 +1,209 @@ +package dev.fluttercommunity.plus.share + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import androidx.core.content.FileProvider +import java.io.File +import java.io.IOException + +/** + * Handles share intent. The `context` and `activity` are used to start the share + * intent. The `activity` might be null when constructing the [Share] object and set + * to non-null when an activity is available using [.setActivity]. + */ +internal class Share(private val context: Context?, private var activity: Activity?) { + private val providerAuthority: String by lazy { + getContext().packageName + ".flutter.share_provider" + } + + private val shareCacheFolder: File + get() = File(getContext().cacheDir, "share_plus") + + private fun getContext(): Context { + if (activity != null) { + return activity!! + } + if (context != null) { + return context + } + throw IllegalStateException("Both context and activity are null") + } + + /** + * Sets the activity when an activity is available. When the activity becomes unavailable, use + * this method to set it to null. + */ + fun setActivity(activity: Activity?) { + this.activity = activity + } + + fun share(text: String, subject: String?) { + val shareIntent = Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, text) + putExtra(Intent.EXTRA_SUBJECT, subject) + } + + val chooserIntent = Intent.createChooser(shareIntent, null /* dialog title optional */) + startActivity(chooserIntent) + } + + @Throws(IOException::class) + fun shareFiles( + paths: List, + mimeTypes: List?, + text: String?, + subject: String? + ) { + clearShareCacheFolder() + val fileUris = getUrisForPaths(paths) + val shareIntent = Intent() + when { + (fileUris.isEmpty() && !text.isNullOrBlank()) -> { + share(text, subject) + return + } + fileUris.size == 1 -> { + val mimeType = if (!mimeTypes.isNullOrEmpty()) { + mimeTypes.first() + } else { + "*/*" + } + shareIntent.apply { + action = Intent.ACTION_SEND + type = mimeType + putExtra(Intent.EXTRA_STREAM, fileUris.first()) + } + } + else -> { + shareIntent.apply { + action = Intent.ACTION_SEND_MULTIPLE + type = reduceMimeTypes(mimeTypes) + putParcelableArrayListExtra(Intent.EXTRA_STREAM, fileUris) + } + } + } + if (text != null) shareIntent.putExtra(Intent.EXTRA_TEXT, text) + if (subject != null) shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject) + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + val chooserIntent = Intent.createChooser(shareIntent, null /* dialog title optional */) + val resInfoList = getContext().packageManager.queryIntentActivities( + chooserIntent, PackageManager.MATCH_DEFAULT_ONLY + ) + resInfoList.forEach { resolveInfo -> + val packageName = resolveInfo.activityInfo.packageName + fileUris.forEach { fileUri -> + getContext().grantUriPermission( + packageName, + fileUri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION, + ) + } + } + startActivity(chooserIntent) + } + + private fun startActivity(intent: Intent) { + when { + activity != null -> { + activity!!.startActivity(intent) + } + context != null -> { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } + else -> { + throw IllegalStateException("Both context and activity are null") + } + } + } + + @Throws(IOException::class) + private fun getUrisForPaths(paths: List): ArrayList { + val uris = ArrayList(paths.size) + paths.forEach { path -> + var file = File(path) + if (fileIsInShareCache(file)) { + // If file is saved in '.../caches/share_plus' it will be erased by 'clearShareCacheFolder()' + throw IOException("Shared file can not be located in '${shareCacheFolder.canonicalPath}'") + } + file = copyToShareCacheFolder(file) + uris.add(FileProvider.getUriForFile(getContext(), providerAuthority, file)) + } + return uris + } + + /** + * Reduces provided MIME types to a common one to provide [Intent] with a correct type + * to share multiple files + */ + private fun reduceMimeTypes(mimeTypes: List?): String { + var reducedMimeType = "*/*" + + mimeTypes?.let { types -> + { + if (types.size == 1) { + reducedMimeType = types.first() + } else if (types.size > 1) { + var commonMimeType = types.first() + for (i in 1..types.lastIndex) { + if (commonMimeType != types[i]) { + if (getMimeTypeBase(commonMimeType) == getMimeTypeBase(types[i])) { + commonMimeType = getMimeTypeBase(types[i]) + "/*" + } else { + commonMimeType = "*/*" + break + } + } + } + reducedMimeType = commonMimeType + } + } + } + return reducedMimeType + } + + /** + * Returns the first part of provided MIME type, which comes before '/' symbol + */ + private fun getMimeTypeBase(mimeType: String?): String { + return if (mimeType == null || !mimeType.contains("/")) { + "*" + } else { + mimeType.substring(0, mimeType.indexOf("/")) + } + } + + private fun fileIsInShareCache(file: File): Boolean { + return try { + val filePath = file.canonicalPath + filePath.startsWith(shareCacheFolder.canonicalPath) + } catch (e: IOException) { + false + } + } + + private fun clearShareCacheFolder() { + val folder = shareCacheFolder + val files = folder.listFiles() + if (folder.exists() && !files.isNullOrEmpty()) { + files.forEach { it.delete() } + folder.delete() + } + } + + @Throws(IOException::class) + private fun copyToShareCacheFolder(file: File): File { + val folder = shareCacheFolder + if (!folder.exists()) { + folder.mkdirs() + } + val newFile = File(folder, file.name) + file.copyTo(newFile, true) + return newFile + } +} diff --git a/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/ShareFileProvider.kt b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/ShareFileProvider.kt new file mode 100644 index 0000000000..4c35925519 --- /dev/null +++ b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/ShareFileProvider.kt @@ -0,0 +1,10 @@ +package dev.fluttercommunity.plus.share + +import androidx.core.content.FileProvider + +/** + * Providing a custom `FileProvider` prevents manifest `` name collisions. + * + * See https://developer.android.com/guide/topics/manifest/provider-element.html for details. + */ +class ShareFileProvider : FileProvider() diff --git a/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/SharePlusPlugin.kt b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/SharePlusPlugin.kt new file mode 100644 index 0000000000..e62703c599 --- /dev/null +++ b/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/SharePlusPlugin.kt @@ -0,0 +1,44 @@ +package dev.fluttercommunity.plus.share + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.MethodChannel + +/** Plugin method host for presenting a share sheet via Intent */ +class SharePlusPlugin : FlutterPlugin, ActivityAware { + private lateinit var share: Share + private lateinit var methodChannel: MethodChannel + + override fun onAttachedToEngine(binding: FlutterPluginBinding) { + methodChannel = MethodChannel(binding.binaryMessenger, CHANNEL) + share = Share(context = binding.applicationContext, activity = null) + val handler = MethodCallHandler(share) + methodChannel.setMethodCallHandler(handler) + } + + override fun onDetachedFromEngine(binding: FlutterPluginBinding) { + methodChannel.setMethodCallHandler(null) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + share.setActivity(binding.activity) + } + + override fun onDetachedFromActivity() { + share.setActivity(null) + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + onAttachedToActivity(binding) + } + + override fun onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity() + } + + companion object { + private const val CHANNEL = "dev.fluttercommunity.plus/share" + } +} diff --git a/packages/share_plus/share_plus/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/share_plus/share_plus/example/android/gradle/wrapper/gradle-wrapper.properties index 6dd0229556..c17f829cd3 100644 --- a/packages/share_plus/share_plus/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/share_plus/share_plus/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Oct 05 15:14:03 EEST 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/packages/share_plus/share_plus/example/lib/main.dart b/packages/share_plus/share_plus/example/lib/main.dart index 045f596caf..e9823cf4cf 100644 --- a/packages/share_plus/share_plus/example/lib/main.dart +++ b/packages/share_plus/share_plus/example/lib/main.dart @@ -29,10 +29,10 @@ class DemoAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Share Plugin Demo', + title: 'Share Plus Plugin Demo', home: Scaffold( appBar: AppBar( - title: const Text('Share Plugin Demo'), + title: const Text('Share Plus Plugin Demo'), ), body: SingleChildScrollView( child: Padding( diff --git a/packages/share_plus/share_plus/example/pubspec.yaml b/packages/share_plus/share_plus/example/pubspec.yaml index 363511c3a3..0d843bb2ec 100644 --- a/packages/share_plus/share_plus/example/pubspec.yaml +++ b/packages/share_plus/share_plus/example/pubspec.yaml @@ -6,7 +6,7 @@ dependencies: sdk: flutter share_plus: path: ../ - image_picker: ^0.8.4+2 + image_picker: ^0.8.4 dependency_overrides: share_plus_linux: @@ -32,4 +32,4 @@ flutter: environment: sdk: '>=2.12.0 <3.0.0' - flutter: ">=1.9.1+hotfix.2" + flutter: ">=1.20.0" diff --git a/packages/share_plus/share_plus/pubspec.yaml b/packages/share_plus/share_plus/pubspec.yaml index 9ff2c4fe0c..858a9ce9b5 100644 --- a/packages/share_plus/share_plus/pubspec.yaml +++ b/packages/share_plus/share_plus/pubspec.yaml @@ -1,6 +1,6 @@ name: share_plus description: Flutter plugin for sharing content via the platform share UI, using the ACTION_SEND intent on Android and UIActivityViewController on iOS. -version: 3.0.5 +version: 3.1.0 homepage: https://plus.fluttercommunity.dev/ repository: https://github.com/fluttercommunity/plus_plugins/tree/main/packages/