Fix share on ipad, improve share file names
This commit is contained in:
parent
2bc97856af
commit
646550575d
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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, 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, 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<ResolveInfo> = 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<files-path name="sites" path="."/>
|
||||
<external-path name="external-sites" path="." />
|
||||
<cache-path name="tmp cache" path="share/" />
|
||||
</paths>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = "<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>"; };
|
||||
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; };
|
||||
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 = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -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, "<hidden>", &err)
|
||||
if (err != nil) {
|
||||
return result(CallFailedError(message: "Error while rendering config", details: err!.localizedDescription))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -160,10 +160,10 @@ class _MainScreenState extends State<MainScreen> {
|
|||
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) {
|
||||
|
|
|
@ -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<SiteLogsScreen> {
|
|||
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(
|
||||
|
|
|
@ -172,7 +172,7 @@ class _AdvancedScreenState extends State<AdvancedScreen> {
|
|||
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);
|
||||
|
|
|
@ -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<CertificateScreen> {
|
|||
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;
|
||||
});
|
||||
|
|
|
@ -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: <Widget>[
|
||||
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),
|
||||
|
|
|
@ -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<bool> 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', <String, dynamic>{
|
||||
'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<bool> 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', <String, dynamic>{
|
||||
'title': title,
|
||||
'filePath': filePath,
|
||||
'filename': filename,
|
||||
});
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue