Fix share on Android by moving to flutter share lib (#87)
Co-authored-by: Nate Brown <nbrown.us@gmail.com>
This commit is contained in:
parent
c7a53c3905
commit
a5684e1978
|
@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdkVersion 33
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
@ -84,9 +84,6 @@ class MainActivity: FlutterActivity() {
|
||||||
"active.setRemoteForTunnel" -> activeSetRemoteForTunnel(call, result)
|
"active.setRemoteForTunnel" -> activeSetRemoteForTunnel(call, result)
|
||||||
"active.closeTunnel" -> activeCloseTunnel(call, result)
|
"active.closeTunnel" -> activeCloseTunnel(call, result)
|
||||||
|
|
||||||
"share" -> Share.share(call, result)
|
|
||||||
"shareFile" -> Share.shareFile(call, result)
|
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
package net.defined.mobile_nebula
|
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.*
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.pm.ResolveInfo
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import io.flutter.plugin.common.MethodCall
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class Share {
|
|
||||||
companion object {
|
|
||||||
fun share(call: MethodCall, result: MethodChannel.Result) {
|
|
||||||
val title = call.argument<String>("title")
|
|
||||||
val text = call.argument<String>("text")
|
|
||||||
val filename = call.argument<String>("filename")
|
|
||||||
|
|
||||||
if (filename == null || filename.isEmpty()) {
|
|
||||||
return result.error("filename was not provided", null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val context = MainActivity!!.getContext()!!
|
|
||||||
val cacheDir = context.cacheDir.resolve("share")
|
|
||||||
cacheDir.deleteRecursively()
|
|
||||||
cacheDir.mkdir()
|
|
||||||
val newFile = cacheDir.resolve(filename!!)
|
|
||||||
newFile.delete()
|
|
||||||
newFile.writeText(text ?: "")
|
|
||||||
pop(title, newFile, result)
|
|
||||||
|
|
||||||
} catch (err: Exception) {
|
|
||||||
Log.println(Log.ERROR, "", "Share: Error")
|
|
||||||
result.error(err.message ?: "Unknown error", null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shareFile(call: MethodCall, result: MethodChannel.Result) {
|
|
||||||
val title = call.argument<String>("title")
|
|
||||||
val filename = call.argument<String>("filename")
|
|
||||||
val filePath = call.argument<String>("filePath")
|
|
||||||
|
|
||||||
if (filename == null || filename.isEmpty()) {
|
|
||||||
result.error("filename was not provided", null, null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filePath == null || filePath.isEmpty()) {
|
|
||||||
result.error("filePath was not provided", null, null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val file = File(filePath)
|
|
||||||
|
|
||||||
try {
|
|
||||||
val context = MainActivity!!.getContext()!!
|
|
||||||
val cacheDir = context.cacheDir.resolve("share")
|
|
||||||
cacheDir.deleteRecursively()
|
|
||||||
cacheDir.mkdir()
|
|
||||||
val newFile = cacheDir.resolve(filename!!)
|
|
||||||
newFile.delete()
|
|
||||||
file.copyTo(newFile)
|
|
||||||
|
|
||||||
pop(title, newFile, result)
|
|
||||||
|
|
||||||
} catch (err: Exception) {
|
|
||||||
Log.println(Log.ERROR, "", "Share: Error")
|
|
||||||
result.error(err.message ?: "Unknown error", null, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pop(title: String?, file: File, result: MethodChannel.Result) {
|
|
||||||
if (title == null || title.isEmpty()) {
|
|
||||||
result.error("title was not provided", null, null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val context = MainActivity!!.getContext()!!
|
|
||||||
|
|
||||||
val fileUri = FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", file)
|
|
||||||
val intent = Intent()
|
|
||||||
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
intent.action = Intent.ACTION_SEND
|
|
||||||
intent.type = "text/*"
|
|
||||||
|
|
||||||
intent.putExtra(Intent.EXTRA_SUBJECT, title)
|
|
||||||
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
|
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
|
|
||||||
val receiver = Intent(context, ShareReceiver::class.java)
|
|
||||||
receiver.putExtra(Intent.EXTRA_TEXT, file)
|
|
||||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, receiver, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
||||||
|
|
||||||
val chooserIntent = Intent.createChooser(intent, title, pendingIntent.intentSender)
|
|
||||||
val resInfoList: List<ResolveInfo> = context.packageManager.queryIntentActivities(chooserIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
|
||||||
for (resolveInfo in resInfoList) {
|
|
||||||
val packageName: String = resolveInfo.activityInfo.packageName
|
|
||||||
context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
}
|
|
||||||
|
|
||||||
context.startActivity(chooserIntent)
|
|
||||||
|
|
||||||
} catch (err: Exception) {
|
|
||||||
Log.println(Log.ERROR, "", "Share: Error")
|
|
||||||
return result.error(err.message ?: "Unknown error", null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShareReceiver : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
|
||||||
if (intent == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val res = intent.extras!!.get(Intent.EXTRA_CHOSEN_COMPONENT) as? ComponentName ?: return
|
|
||||||
when (res.className) {
|
|
||||||
"org.chromium.arc.intent_helper.SendTextToClipboardActivity" -> {
|
|
||||||
val file = intent.extras!![Intent.EXTRA_TEXT] as? File ?: return
|
|
||||||
val clipboard = context?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
||||||
|
|
||||||
clipboard.setPrimaryClip(ClipData.newPlainText("", file.readText()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -43,6 +43,8 @@ PODS:
|
||||||
- SDWebImage (5.13.3):
|
- SDWebImage (5.13.3):
|
||||||
- SDWebImage/Core (= 5.13.3)
|
- SDWebImage/Core (= 5.13.3)
|
||||||
- SDWebImage/Core (5.13.3)
|
- SDWebImage/Core (5.13.3)
|
||||||
|
- share_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- SwiftyGif (5.4.3)
|
- SwiftyGif (5.4.3)
|
||||||
- SwiftyJSON (5.0.1)
|
- SwiftyJSON (5.0.1)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
|
@ -54,6 +56,7 @@ DEPENDENCIES:
|
||||||
- flutter_barcode_scanner (from `.symlinks/plugins/flutter_barcode_scanner/ios`)
|
- flutter_barcode_scanner (from `.symlinks/plugins/flutter_barcode_scanner/ios`)
|
||||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- SwiftyJSON (~> 5.0)
|
- SwiftyJSON (~> 5.0)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
|
@ -76,6 +79,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/package_info/ios"
|
:path: ".symlinks/plugins/package_info/ios"
|
||||||
path_provider_ios:
|
path_provider_ios:
|
||||||
:path: ".symlinks/plugins/path_provider_ios/ios"
|
:path: ".symlinks/plugins/path_provider_ios/ios"
|
||||||
|
share_plus:
|
||||||
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
|
||||||
|
@ -88,6 +93,7 @@ SPEC CHECKSUMS:
|
||||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||||
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
|
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
|
||||||
SDWebImage: af5bbffef2cde09f148d826f9733dcde1a9414cd
|
SDWebImage: af5bbffef2cde09f148d826f9733dcde1a9414cd
|
||||||
|
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||||
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
|
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
|
||||||
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
|
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
43AA89572444DA6500EDC39C /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA89562444DA6500EDC39C /* PacketTunnelProvider.swift */; };
|
43AA89572444DA6500EDC39C /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA89562444DA6500EDC39C /* PacketTunnelProvider.swift */; };
|
||||||
43AA895C2444DA6500EDC39C /* NebulaNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 43AA89542444DA6500EDC39C /* NebulaNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
43AA895C2444DA6500EDC39C /* NebulaNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 43AA89542444DA6500EDC39C /* NebulaNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
43AA89622444DAA500EDC39C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43AA894E2444D8BC00EDC39C /* NetworkExtension.framework */; };
|
43AA89622444DAA500EDC39C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43AA894E2444D8BC00EDC39C /* NetworkExtension.framework */; };
|
||||||
43AD63F424EB3802000FB47E /* Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AD63F324EB3802000FB47E /* Share.swift */; };
|
|
||||||
43ED87842912D0DD004DAFC5 /* DNUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43ED87832912D0DD004DAFC5 /* DNUpdate.swift */; };
|
43ED87842912D0DD004DAFC5 /* DNUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43ED87832912D0DD004DAFC5 /* DNUpdate.swift */; };
|
||||||
43ED87852912D0DD004DAFC5 /* DNUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43ED87832912D0DD004DAFC5 /* DNUpdate.swift */; };
|
43ED87852912D0DD004DAFC5 /* DNUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43ED87832912D0DD004DAFC5 /* DNUpdate.swift */; };
|
||||||
4CF2F06A02A63B862C9F6F03 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384887B4785D38431E800D3A /* Pods_Runner.framework */; };
|
4CF2F06A02A63B862C9F6F03 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384887B4785D38431E800D3A /* Pods_Runner.framework */; };
|
||||||
|
@ -89,7 +88,6 @@
|
||||||
43AA89562444DA6500EDC39C /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
43AA89562444DA6500EDC39C /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
||||||
43AA89582444DA6500EDC39C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
43AA89582444DA6500EDC39C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
43AA89592444DA6500EDC39C /* NebulaNetworkExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NebulaNetworkExtension.entitlements; sourceTree = "<group>"; };
|
43AA89592444DA6500EDC39C /* NebulaNetworkExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NebulaNetworkExtension.entitlements; sourceTree = "<group>"; };
|
||||||
43AD63F324EB3802000FB47E /* Share.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Share.swift; sourceTree = "<group>"; };
|
|
||||||
43B66ECA245A0C8400B18C36 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
43B66ECA245A0C8400B18C36 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||||
43B66ECC245A146300B18C36 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
43B66ECC245A146300B18C36 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
|
||||||
43ED87832912D0DD004DAFC5 /* DNUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNUpdate.swift; sourceTree = "<group>"; };
|
43ED87832912D0DD004DAFC5 /* DNUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNUpdate.swift; sourceTree = "<group>"; };
|
||||||
|
@ -210,7 +208,6 @@
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
43871C9C2444E2EC004F9075 /* Sites.swift */,
|
43871C9C2444E2EC004F9075 /* Sites.swift */,
|
||||||
43AD63F324EB3802000FB47E /* Share.swift */,
|
|
||||||
43ED87832912D0DD004DAFC5 /* DNUpdate.swift */,
|
43ED87832912D0DD004DAFC5 /* DNUpdate.swift */,
|
||||||
BE45F625291AEAB300902884 /* PackageInfo.swift */,
|
BE45F625291AEAB300902884 /* PackageInfo.swift */,
|
||||||
BE5BC105291C41E600B6FE5B /* APIClient.swift */,
|
BE5BC105291C41E600B6FE5B /* APIClient.swift */,
|
||||||
|
@ -362,6 +359,7 @@
|
||||||
"${BUILT_PRODUCTS_DIR}/flutter_barcode_scanner/flutter_barcode_scanner.framework",
|
"${BUILT_PRODUCTS_DIR}/flutter_barcode_scanner/flutter_barcode_scanner.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/package_info/package_info.framework",
|
"${BUILT_PRODUCTS_DIR}/package_info/package_info.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
|
"${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
|
||||||
|
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
|
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
|
||||||
);
|
);
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
@ -375,6 +373,7 @@
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_barcode_scanner.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_barcode_scanner.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
|
||||||
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -476,7 +475,6 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
43AD63F424EB3802000FB47E /* Share.swift in Sources */,
|
|
||||||
432D0E3E291C562200752563 /* SiteList.swift in Sources */,
|
432D0E3E291C562200752563 /* SiteList.swift in Sources */,
|
||||||
43871C9D2444E2EC004F9075 /* Sites.swift in Sources */,
|
43871C9D2444E2EC004F9075 /* Sites.swift in Sources */,
|
||||||
BE5BC106291C41E600B6FE5B /* APIClient.swift in Sources */,
|
BE5BC106291C41E600B6FE5B /* APIClient.swift in Sources */,
|
||||||
|
|
|
@ -68,9 +68,6 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError {
|
||||||
case "active.setRemoteForTunnel": self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result)
|
case "active.setRemoteForTunnel": self.vpnRequest(command: "setRemoteForTunnel", arguments: call.arguments, result: result)
|
||||||
case "active.closeTunnel": self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result)
|
case "active.closeTunnel": self.vpnRequest(command: "closeTunnel", arguments: call.arguments, result: result)
|
||||||
|
|
||||||
case "share": Share.share(call: call, result: result)
|
|
||||||
case "shareFile": Share.shareFile(call: call, result: result)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
result(FlutterMethodNotImplemented)
|
result(FlutterMethodNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,150 +0,0 @@
|
||||||
// Basis of this code comes from https://github.com/lubritto/flutter_share
|
|
||||||
|
|
||||||
import Flutter
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
public class Share {
|
|
||||||
public static func share(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
||||||
let args = call.arguments as? [String: Any?]
|
|
||||||
|
|
||||||
let title = args!["title"] as? String
|
|
||||||
let text = args!["text"] as? String
|
|
||||||
let filename = args!["filename"] as? String
|
|
||||||
let tmpDirURL = FileManager.default.temporaryDirectory
|
|
||||||
|
|
||||||
if (filename == nil || filename!.isEmpty) {
|
|
||||||
return result(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
let tmpFile = tmpDirURL.appendingPathComponent(filename!)
|
|
||||||
do {
|
|
||||||
try text?.write(to: tmpFile, atomically: true, encoding: .utf8)
|
|
||||||
} catch {
|
|
||||||
//TODO: return error
|
|
||||||
return result(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pop(title: title, file: tmpFile) { pass in
|
|
||||||
let fm = FileManager()
|
|
||||||
do {
|
|
||||||
try fm.removeItem(at: tmpFile)
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
return result(pass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func shareFile(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
|
||||||
let args = call.arguments as? [String: Any?]
|
|
||||||
|
|
||||||
let title = args!["title"] as? String
|
|
||||||
let filePath = args!["filePath"] as? String
|
|
||||||
let filename = args!["filename"] as? String
|
|
||||||
|
|
||||||
if (filePath == nil || filePath!.isEmpty) {
|
|
||||||
return result(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmpFile: URL?
|
|
||||||
let fm = FileManager()
|
|
||||||
var realPath = URL(fileURLWithPath: filePath!)
|
|
||||||
|
|
||||||
if (filename != nil && !filename!.isEmpty) {
|
|
||||||
tmpFile = FileManager.default.temporaryDirectory.appendingPathComponent(filename!)
|
|
||||||
|
|
||||||
do {
|
|
||||||
try fm.linkItem(at: URL(fileURLWithPath: filePath!), to: tmpFile!)
|
|
||||||
} catch {
|
|
||||||
//TODO: return error
|
|
||||||
return result(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
realPath = tmpFile!
|
|
||||||
}
|
|
||||||
|
|
||||||
pop(title: title, file: realPath) { pass in
|
|
||||||
if (tmpFile != nil) {
|
|
||||||
do {
|
|
||||||
try fm.removeItem(at: tmpFile!)
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
result(pass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func pop(title: String?, file: URL, completion: @escaping ((Bool) -> Void)) {
|
|
||||||
if (title == nil || title!.isEmpty) {
|
|
||||||
return completion(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
let activityViewController = UIActivityViewController(activityItems: [ShareCopy(file: file)], applicationActivities: nil)
|
|
||||||
|
|
||||||
activityViewController.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in
|
|
||||||
completion(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subject
|
|
||||||
activityViewController.setValue(title, forKeyPath: "subject")
|
|
||||||
|
|
||||||
// For iPads, fix issue where Exception is thrown by using a popup instead
|
|
||||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
|
||||||
activityViewController.popoverPresentationController?.sourceView = UIApplication.topViewController()?.view
|
|
||||||
if let view = UIApplication.topViewController()?.view {
|
|
||||||
activityViewController.popoverPresentationController?.permittedArrowDirections = []
|
|
||||||
activityViewController.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
UIApplication.topViewController()?.present(activityViewController, animated: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension UIApplication {
|
|
||||||
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
|
|
||||||
if let navigationController = controller as? UINavigationController {
|
|
||||||
return topViewController(controller: navigationController.visibleViewController)
|
|
||||||
}
|
|
||||||
if let tabController = controller as? UITabBarController {
|
|
||||||
if let selected = tabController.selectedViewController {
|
|
||||||
return topViewController(controller: selected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let presented = controller?.presentedViewController {
|
|
||||||
return topViewController(controller: presented)
|
|
||||||
}
|
|
||||||
return controller
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShareCopy: UIActivityItemProvider {
|
|
||||||
private let file: URL
|
|
||||||
private let content: String
|
|
||||||
|
|
||||||
init(file: URL) {
|
|
||||||
self.file = file
|
|
||||||
do {
|
|
||||||
self.content = try String.init(contentsOf: file)
|
|
||||||
} catch {
|
|
||||||
self.content = "Error"
|
|
||||||
}
|
|
||||||
|
|
||||||
// the type of the placeholder item is used to
|
|
||||||
// display correct activity types by UIActivityControler
|
|
||||||
super.init(placeholderItem: self.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
override var item: Any {
|
|
||||||
get {
|
|
||||||
guard let activityType = activityType else {
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
switch activityType {
|
|
||||||
case .copyToPasteboard: return content
|
|
||||||
default: return file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +1,12 @@
|
||||||
// This code comes from https://github.com/lubritto/flutter_share with bugfixes for ipad
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:share_plus/share_plus.dart' as sp;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
class Share {
|
class Share {
|
||||||
static const _channel = MethodChannel('net.defined.mobileNebula/NebulaVpnService');
|
/// Transforms a string of text into a file and shares that file
|
||||||
|
|
||||||
/// Shares a string of text
|
|
||||||
/// - title: Title of message or subject if sending an email
|
/// - title: Title of message or subject if sending an email
|
||||||
/// - text: The text to share
|
/// - text: The text to share
|
||||||
/// - filename: The filename to use if sending over airdrop for example
|
/// - filename: The filename to use if sending over airdrop for example
|
||||||
|
@ -21,17 +19,19 @@ class Share {
|
||||||
assert(text.isNotEmpty);
|
assert(text.isNotEmpty);
|
||||||
assert(filename.isNotEmpty);
|
assert(filename.isNotEmpty);
|
||||||
|
|
||||||
if (title.isEmpty) {
|
final tmpDir = await getTemporaryDirectory();
|
||||||
throw FlutterError('Title cannot be empty');
|
final file = File(p.join(tmpDir.path, filename));
|
||||||
|
var res = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
file.writeAsStringSync(text, flush: true);
|
||||||
|
res = await Share.shareFile(title: title, filePath: file.path);
|
||||||
|
} catch (err) {
|
||||||
|
// Ignoring file write errors
|
||||||
}
|
}
|
||||||
|
|
||||||
final bool success = await _channel.invokeMethod('share', <String, dynamic>{
|
file.delete();
|
||||||
'title': title,
|
return res;
|
||||||
'text': text,
|
|
||||||
'filename': filename,
|
|
||||||
});
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shares a local file
|
/// Shares a local file
|
||||||
|
@ -41,23 +41,16 @@ class Share {
|
||||||
static Future<bool> shareFile({
|
static Future<bool> shareFile({
|
||||||
required String title,
|
required String title,
|
||||||
required String filePath,
|
required String filePath,
|
||||||
String? filename,
|
String? filename
|
||||||
}) async {
|
}) async {
|
||||||
assert(title.isNotEmpty);
|
assert(title.isNotEmpty);
|
||||||
assert(filePath.isNotEmpty);
|
assert(filePath.isNotEmpty);
|
||||||
|
|
||||||
if (title.isEmpty) {
|
//NOTE: the filename used to specify the name of the file in gmail/slack/etc but no longer works that way
|
||||||
throw FlutterError('Title cannot be empty');
|
// If we want to support that again we will need to save the file to a temporary directory, share that,
|
||||||
} else if (filePath.isEmpty) {
|
// and then delete it
|
||||||
throw FlutterError('FilePath cannot be empty');
|
final xFile = sp.XFile(filePath, name: filename);
|
||||||
}
|
final result = await sp.Share.shareXFiles([xFile], subject: title);
|
||||||
|
return result.status == sp.ShareResultStatus.success;
|
||||||
final bool success = await _channel.invokeMethod('shareFile', <String, dynamic>{
|
|
||||||
'title': title,
|
|
||||||
'filePath': filePath,
|
|
||||||
'filename': filename,
|
|
||||||
});
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
40
pubspec.lock
40
pubspec.lock
|
@ -36,6 +36,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.3+2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -77,7 +84,7 @@ packages:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.2.2"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -156,6 +163,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.0"
|
version: "1.8.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
package_info:
|
package_info:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -268,6 +282,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
share_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: share_plus
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.3.0"
|
||||||
|
share_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: share_plus_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -349,7 +377,7 @@ packages:
|
||||||
name: url_launcher_linux
|
name: url_launcher_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "3.0.1"
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -370,21 +398,21 @@ packages:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.13"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_windows
|
name: url_launcher_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "3.0.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "3.0.7"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -398,7 +426,7 @@ packages:
|
||||||
name: win32
|
name: win32
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.7.0"
|
version: "3.1.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -33,6 +33,7 @@ dependencies:
|
||||||
flutter_barcode_scanner: ^2.0.0
|
flutter_barcode_scanner: ^2.0.0
|
||||||
flutter_svg: ^1.1.5
|
flutter_svg: ^1.1.5
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
|
share_plus: ^6.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue