From 646550575dc2042fbbf06468039a68108fcd0468 Mon Sep 17 00:00:00 2001 From: Nate Brown Date: Mon, 17 Aug 2020 19:12:28 -0500 Subject: [PATCH] Fix share on ipad, improve share file names --- .../net/defined/mobile_nebula/MainActivity.kt | 3 + .../kotlin/net/defined/mobile_nebula/Share.kt | 115 +++++++++++++++++ .../app/src/main/res/xml/provider_paths.xml | 3 +- ios/Podfile.lock | 6 - ios/Runner.xcodeproj/project.pbxproj | 4 + ios/Runner/AppDelegate.swift | 4 +- ios/Runner/Share.swift | 119 ++++++++++++++++++ lib/screens/MainScreen.dart | 12 +- lib/screens/SiteLogsScreen.dart | 5 +- lib/screens/siteConfig/AdvancedScreen.dart | 2 +- lib/screens/siteConfig/CertificateScreen.dart | 4 +- .../siteConfig/RenderedConfigScreen.dart | 12 +- lib/services/share.dart | 56 +++++++++ pubspec.lock | 7 -- pubspec.yaml | 3 +- 15 files changed, 325 insertions(+), 30 deletions(-) create mode 100644 android/app/src/main/kotlin/net/defined/mobile_nebula/Share.kt create mode 100644 ios/Runner/Share.swift create mode 100644 lib/services/share.dart diff --git a/android/app/src/main/kotlin/net/defined/mobile_nebula/MainActivity.kt b/android/app/src/main/kotlin/net/defined/mobile_nebula/MainActivity.kt index a456687..bf944e0 100644 --- a/android/app/src/main/kotlin/net/defined/mobile_nebula/MainActivity.kt +++ b/android/app/src/main/kotlin/net/defined/mobile_nebula/MainActivity.kt @@ -65,6 +65,9 @@ class MainActivity: FlutterActivity() { "active.setRemoteForTunnel" -> activeSetRemoteForTunnel(call, result) "active.closeTunnel" -> activeCloseTunnel(call, result) + "share" -> Share.share(call, result) + "shareFile" -> Share.shareFile(call, result) + else -> result.notImplemented() } } diff --git a/android/app/src/main/kotlin/net/defined/mobile_nebula/Share.kt b/android/app/src/main/kotlin/net/defined/mobile_nebula/Share.kt new file mode 100644 index 0000000..079fe4a --- /dev/null +++ b/android/app/src/main/kotlin/net/defined/mobile_nebula/Share.kt @@ -0,0 +1,115 @@ +package net.defined.mobile_nebula + +import android.content.Intent +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("title") + val text = call.argument("text") + val filename = call.argument("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, null, null) + } + } + + fun shareFile(call: MethodCall, result: MethodChannel.Result) { + val title = call.argument("title") + val filename = call.argument("filename") + val filePath = call.argument("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, 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 = "*/*" + + intent.putExtra(Intent.EXTRA_SUBJECT, title) + intent.putExtra(Intent.EXTRA_STREAM, fileUri) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + + val chooserIntent = Intent.createChooser(intent, title) + chooserIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP + chooserIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + + val resInfoList: List = context.packageManager.queryIntentActivities(chooserIntent, PackageManager.MATCH_DEFAULT_ONLY) + for (resolveInfo in resInfoList) { + print(fileUri) + 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, null, null) + } + + result.success(true) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/res/xml/provider_paths.xml b/android/app/src/main/res/xml/provider_paths.xml index 7f4dbfc..d659161 100644 --- a/android/app/src/main/res/xml/provider_paths.xml +++ b/android/app/src/main/res/xml/provider_paths.xml @@ -1,5 +1,4 @@ - - + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 716bdc5..9f35057 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -41,8 +41,6 @@ PODS: - Flutter (1.0.0) - flutter_plugin_android_lifecycle (0.0.1): - Flutter - - flutter_share (0.0.1): - - Flutter - MMWormhole (2.0.0): - MMWormhole/Core (= 2.0.0) - MMWormhole/Core (2.0.0) @@ -74,7 +72,6 @@ DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`) - - flutter_share (from `.symlinks/plugins/flutter_share/ios`) - MMWormhole (~> 2.0.0) - package_info (from `.symlinks/plugins/package_info/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) @@ -104,8 +101,6 @@ EXTERNAL SOURCES: :path: Flutter flutter_plugin_android_lifecycle: :path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios" - flutter_share: - :path: ".symlinks/plugins/flutter_share/ios" package_info: :path: ".symlinks/plugins/package_info/ios" path_provider: @@ -129,7 +124,6 @@ SPEC CHECKSUMS: FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 Flutter: 0e3d915762c693b495b44d77113d4970485de6ec flutter_plugin_android_lifecycle: dc0b544e129eebb77a6bfb1239d4d1c673a60a35 - flutter_share: 4be0208963c60b537e6255ed2ce1faae61cd9ac2 MMWormhole: 0cd3fd35a9118b2e2d762b499f54eeaace0be791 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8b6accd..e3f1121 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 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, ); }; }; 43AA89622444DAA500EDC39C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 43AA894E2444D8BC00EDC39C /* NetworkExtension.framework */; }; + 43AD63F424EB3802000FB47E /* Share.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AD63F324EB3802000FB47E /* Share.swift */; }; 4CF2F06A02A63B862C9F6F03 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384887B4785D38431E800D3A /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -76,6 +77,7 @@ 43AA89562444DA6500EDC39C /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = ""; }; 43AA89582444DA6500EDC39C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43AA89592444DA6500EDC39C /* NebulaNetworkExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NebulaNetworkExtension.entitlements; sourceTree = ""; }; + 43AD63F324EB3802000FB47E /* Share.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Share.swift; sourceTree = ""; }; 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; }; 43B828DA249C08DC00CA229C /* MMWormhole.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MMWormhole.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -188,6 +190,7 @@ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 43871C9C2444E2EC004F9075 /* Sites.swift */, + 43AD63F324EB3802000FB47E /* Share.swift */, ); path = Runner; sourceTree = ""; @@ -418,6 +421,7 @@ buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 43AD63F424EB3802000FB47E /* Share.swift in Sources */, 43871C9D2444E2EC004F9075 /* Sites.swift in Sources */, 437F725F2469B4B000A0C4B9 /* Site.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 64f1601..5a344a8 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -51,6 +51,9 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError { case "active.setRemoteForTunnel": self.activeSetRemoteForTunnel(call: call, result: result) case "active.closeTunnel": self.activeCloseTunnel(call: call, result: result) + case "share": Share.share(call: call, result: result) + case "shareFile": Share.shareFile(call: call, result: result) + default: result(FlutterMethodNotImplemented) } @@ -86,7 +89,6 @@ func MissingArgumentError(message: String, details: Any?) -> FlutterError { guard let config = call.arguments as? String else { return result(NoArgumentsError()) } var err: NSError? - print(config) let yaml = MobileNebulaRenderConfig(config, "", &err) if (err != nil) { return result(CallFailedError(message: "Error while rendering config", details: err!.localizedDescription)) diff --git a/ios/Runner/Share.swift b/ios/Runner/Share.swift new file mode 100644 index 0000000..89691b6 --- /dev/null +++ b/ios/Runner/Share.swift @@ -0,0 +1,119 @@ +// 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: [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 + } +} diff --git a/lib/screens/MainScreen.dart b/lib/screens/MainScreen.dart index 5f9b72b..9ba8c41 100644 --- a/lib/screens/MainScreen.dart +++ b/lib/screens/MainScreen.dart @@ -160,10 +160,10 @@ class _MainScreenState extends State { var uuid = Uuid(); var cert = '''-----BEGIN NEBULA CERTIFICATE----- -CmMKBnBpeGVsNBIJiYCEUID+//8PKLqMivcFMKTzjoYGOiB4iANINzCjLdlQJSj/ -vJDd080yggfLgW9hT4a/bhGZekog+W+YEJiV36evX4MueQ+npDzJd3zGg5gialu4 -UNGYBP0SQL5bjEyafC0YtETEbrraSfwuFHMvUoi1Kc4XRzTPPvHsEaq3hNNTZtD7 -Pt3sjH83zTMZfnD/Du3ahsvV0rAXUgc= +CmIKBHRlc3QSCoKUoIUMgP7//w8ourrS+QUwjre3iAY6IDbmIX5cwd+UYVhLADLa +A5PwucZPVrNtP0P9NJE0boM2SiBSGzy8bcuFWWK5aVArJGA9VDtLg1HuujBu8lOp +VTgklxJAgbI1Xb1C9JC3a1Cnc6NPqWhnw+3VLoDXE9poBav09+zhw5DPDtgvQmxU +Sbw6cAF4gPS4e/tZ5Kjc8QEvjk3HDQ== -----END NEBULA CERTIFICATE-----'''; var ca = '''-----BEGIN NEBULA CERTIFICATE----- @@ -183,7 +183,9 @@ mUOcsdFcCZiXrj7ryQIG1+WfqA46w71A/lV4nAc= unsafeRoutes: [UnsafeRoute(route: '10.3.3.3/32', via: '10.1.0.1')] ); - s.key = "-----BEGIN NEBULA X25519 PRIVATE KEY-----\ndYgPb04Bb1xzfgdCfVsKGZrCYe+u5tDWNXKipQBVZ44=\n-----END NEBULA X25519 PRIVATE KEY-----"; + s.key = '''-----BEGIN NEBULA X25519 PRIVATE KEY----- +rmXnR1yvDZi1VPVmnNVY8NMsQpEpbbYlq7rul+ByQvg= +-----END NEBULA X25519 PRIVATE KEY-----'''; var err = await s.save(); if (err != null) { diff --git a/lib/screens/SiteLogsScreen.dart b/lib/screens/SiteLogsScreen.dart index faefc8a..19be308 100644 --- a/lib/screens/SiteLogsScreen.dart +++ b/lib/screens/SiteLogsScreen.dart @@ -1,13 +1,12 @@ -import 'dart:async'; import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; -import 'package:flutter_share/flutter_share.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; import 'package:mobile_nebula/models/Site.dart'; +import 'package:mobile_nebula/services/share.dart'; import 'package:mobile_nebula/services/utils.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; @@ -79,7 +78,7 @@ class _SiteLogsScreenState extends State { padding: padding, icon: Icon(context.platformIcons.share, size: 30), onPressed: () { - FlutterShare.shareFile(title: '${widget.site.name} logs', filePath: widget.site.logFile); + Share.shareFile(title: '${widget.site.name} logs', filePath: widget.site.logFile, filename: '${widget.site.name}.log'); }, )), Expanded( diff --git a/lib/screens/siteConfig/AdvancedScreen.dart b/lib/screens/siteConfig/AdvancedScreen.dart index 5e825fa..8911f0b 100644 --- a/lib/screens/siteConfig/AdvancedScreen.dart +++ b/lib/screens/siteConfig/AdvancedScreen.dart @@ -172,7 +172,7 @@ class _AdvancedScreenState extends State { try { var config = await widget.site.renderConfig(); Utils.openPage(context, (context) { - return RenderedConfigScreen(config: config); + return RenderedConfigScreen(config: config, name: widget.site.name); }); } catch (err) { Utils.popError(context, 'Failed to render the site config', err); diff --git a/lib/screens/siteConfig/CertificateScreen.dart b/lib/screens/siteConfig/CertificateScreen.dart index 1e19767..f107182 100644 --- a/lib/screens/siteConfig/CertificateScreen.dart +++ b/lib/screens/siteConfig/CertificateScreen.dart @@ -6,7 +6,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_share/flutter_share.dart'; import 'package:mobile_nebula/components/FormPage.dart'; import 'package:mobile_nebula/components/config/ConfigButtonItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; @@ -15,6 +14,7 @@ import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/components/config/ConfigTextItem.dart'; import 'package:mobile_nebula/models/Certificate.dart'; import 'package:mobile_nebula/screens/siteConfig/CertificateDetailsScreen.dart'; +import 'package:mobile_nebula/services/share.dart'; import 'package:mobile_nebula/services/utils.dart'; class CertificateResult { @@ -140,7 +140,7 @@ class _CertificateScreenState extends State { ConfigButtonItem( content: Text('Share Public Key'), onPressed: () async { - await FlutterShare.share(title: 'Please sign and return a certificate', text: pubKey); + await Share.share(title: 'Please sign and return a certificate', text: pubKey, filename: 'device.pub'); setState(() { shared = true; }); diff --git a/lib/screens/siteConfig/RenderedConfigScreen.dart b/lib/screens/siteConfig/RenderedConfigScreen.dart index 8a11942..0a10a6d 100644 --- a/lib/screens/siteConfig/RenderedConfigScreen.dart +++ b/lib/screens/siteConfig/RenderedConfigScreen.dart @@ -1,17 +1,27 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; +import 'package:mobile_nebula/services/share.dart'; class RenderedConfigScreen extends StatelessWidget { final String config; + final String name; - RenderedConfigScreen({Key key, this.config}) : super(key: key); + RenderedConfigScreen({Key key, this.config, this.name}) : super(key: key); @override Widget build(BuildContext context) { return SimplePage( title: 'Rendered Site Config', scrollable: SimpleScrollable.both, + trailingActions: [ + PlatformIconButton( + padding: EdgeInsets.zero, + icon: Icon(context.platformIcons.share, size: 28.0), + onPressed: () => Share.share(title: '$name.yaml', text: config, filename: '$name.yaml'), + ) + ], child: Container( padding: EdgeInsets.all(5), constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width), diff --git a/lib/services/share.dart b/lib/services/share.dart new file mode 100644 index 0000000..4127d39 --- /dev/null +++ b/lib/services/share.dart @@ -0,0 +1,56 @@ +// This code comes from https://github.com/lubritto/flutter_share with bugfixes for ipad +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class Share { + static const _channel = MethodChannel('net.defined.mobileNebula/NebulaVpnService'); + + /// Shares a string of text + /// - title: Title of message or subject if sending an email + /// - text: The text to share + /// - filename: The filename to use if sending over airdrop for example + static Future share({@required String title, @required String text, @required String filename}) async { + assert(title != null && title.isNotEmpty); + assert(text != null && text.isNotEmpty); + assert(filename != null && filename.isNotEmpty); + + if (title == null || title.isEmpty) { + throw FlutterError('Title cannot be null'); + } + + final bool success = await _channel.invokeMethod('share', { + 'title': title, + 'text': text, + 'filename': filename, + }); + + return success; + } + + /// Shares a local file + /// - title: Title of message or subject if sending an email + /// - filePath: Path to the file to share + /// - filename: An optional filename to override the existing file + static Future shareFile({@required String title, @required String filePath, String filename}) async { + assert(title != null && title.isNotEmpty); + assert(filePath != null && filePath.isNotEmpty); + + if (title == null || title.isEmpty) { + throw FlutterError('Title cannot be null'); + } else if (filePath == null || filePath.isEmpty) { + throw FlutterError('FilePath cannot be null'); + } + + final bool success = + await _channel.invokeMethod('shareFile', { + 'title': title, + 'filePath': filePath, + 'filename': filename, + }); + + return success; + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 6465d2c..5822c50 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -111,13 +111,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.8" - flutter_share: - dependency: "direct main" - description: - name: flutter_share - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2+1" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 62a2c8d..1f3dec6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Mobile Nebula Client # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+31 +version: 0.0.32+4 environment: sdk: ">=2.1.0 <3.0.0" @@ -27,7 +27,6 @@ dependencies: path_provider: ^1.6.0 file_picker: ^1.9.0 barcode_scan: ^3.0.1 - flutter_share: ^1.0.2 uuid: ^2.0.4 package_info: '>=0.4.1 <2.0.0' url_launcher: ^5.4.10