From 31afbdb60f7b51238bdf04431f835180a63ed444 Mon Sep 17 00:00:00 2001 From: ManhNTX Date: Tue, 2 Aug 2022 10:56:59 +0700 Subject: [PATCH] add share file to TMail on iOS --- ios/Runner/AppDelegate.swift | 40 ++- ios/TeamMailShareExtension/Info.plist | 19 +- .../ShareViewController.swift | 245 +++++++++++++++++- pubspec.yaml | 5 +- 4 files changed, 280 insertions(+), 29 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6754bd46c..3c7d16faf 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,19 +1,37 @@ import UIKit import Flutter import flutter_downloader +import receive_sharing_intent @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - FlutterDownloaderPlugin.setPluginRegistrantCallback { registry in - if (!registry.hasPlugin("FlutterDownloaderPlugin")) { - FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!) - } + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + FlutterDownloaderPlugin.setPluginRegistrantCallback { registry in + if (!registry.hasPlugin("FlutterDownloaderPlugin")) { + FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!) + } + } + return super.application(application, didFinishLaunchingWithOptions: launchOptions) } - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } + + override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + let sharingIntent = SwiftReceiveSharingIntentPlugin.instance + if sharingIntent.hasMatchingSchemePrefix(url: url) { + return sharingIntent.application(app, open: url, options: options) + } + + // For example load MSALPublicClientApplication + // return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[.sourceApplication] as? String) + + // Cancel url handling + // return false + + // Proceed url handling for other Flutter libraries like uni_links + return super.application(app, open: url, options:options) + } + } diff --git a/ios/TeamMailShareExtension/Info.plist b/ios/TeamMailShareExtension/Info.plist index 9a277dcf4..02caa4087 100644 --- a/ios/TeamMailShareExtension/Info.plist +++ b/ios/TeamMailShareExtension/Info.plist @@ -26,11 +26,22 @@ NSExtensionActivationRule + NSExtensionActivationSupportsFileWithMaxCount + 15 + NSExtensionActivationSupportsImageWithMaxCount + 15 + NSExtensionActivationSupportsMovieWithMaxCount + 15 NSExtensionActivationSupportsText - - NSExtensionActivationSupportsWebURLWithMaxCount - 1 + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + PHSupportedMediaTypes + + Video + Image + NSExtensionMainStoryboard MainInterface @@ -38,4 +49,4 @@ com.apple.share-services - \ No newline at end of file + diff --git a/ios/TeamMailShareExtension/ShareViewController.swift b/ios/TeamMailShareExtension/ShareViewController.swift index dbf47151e..776386a35 100644 --- a/ios/TeamMailShareExtension/ShareViewController.swift +++ b/ios/TeamMailShareExtension/ShareViewController.swift @@ -1,28 +1,42 @@ -// -// ShareViewController.swift -// TeamMailShareExtension -// -// Created by dab.dev on 06/04/2022. -// - import UIKit import Social import MobileCoreServices +import Photos class ShareViewController: SLComposeServiceViewController { - - let hostAppBundleIdentifier = "com.linagora.teammail" + var hostAppBundleIdentifier = "" + let appGroupId = "group.com.linagora.teammail" let sharedKey = "ShareKey" + var sharedMedia: [SharedMediaFile] = [] var sharedText: [String] = [] + let imageContentType = kUTTypeImage as String + let videoContentType = kUTTypeMovie as String let textContentType = kUTTypeText as String let urlContentType = kUTTypeURL as String + let fileURLType = kUTTypeFileURL as String; override func isContentValid() -> Bool { return true } + private func loadIds() { + // loading Share extension App Id + let shareExtensionAppBundleIdentifier = Bundle.main.bundleIdentifier!; + + + // convert ShareExtension id to host app id + // By default it is remove last part of id after last point + // For example: com.test.ShareExtension -> com.test + let lastIndexOfPoint = shareExtensionAppBundleIdentifier.lastIndex(of: "."); + hostAppBundleIdentifier = String(shareExtensionAppBundleIdentifier[.. String { + let parts = url.lastPathComponent.components(separatedBy: ".") + var ex: String? = nil + if (parts.count > 1) { + ex = parts.last + } + + if (ex == nil) { + switch type { + case .image: + ex = "PNG" + case .video: + ex = "MP4" + case .file: + ex = "TXT" + } + } + return ex ?? "Unknown" + } + + func getFileName(from url: URL, type: SharedMediaType) -> String { + var name = url.lastPathComponent + + if (name.isEmpty) { + name = UUID().uuidString + "." + getExtension(from: url, type: type) + } + + return name + } + + func copyFile(at srcURL: URL, to dstURL: URL) -> Bool { + do { + if FileManager.default.fileExists(atPath: dstURL.path) { + try FileManager.default.removeItem(at: dstURL) + } + try FileManager.default.copyItem(at: srcURL, to: dstURL) + } catch (let error) { + print("Cannot copy item at \(srcURL) to \(dstURL): \(error)") + return false + } + return true + } + + private func getSharedMediaFile(forVideo: URL) -> SharedMediaFile? { + let asset = AVAsset(url: forVideo) + let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded() + let thumbnailPath = getThumbnailPath(for: forVideo) + + if FileManager.default.fileExists(atPath: thumbnailPath.path) { + return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) + } + + var saved = false + let assetImgGenerate = AVAssetImageGenerator(asset: asset) + assetImgGenerate.appliesPreferredTrackTransform = true + // let scale = UIScreen.main.scale + assetImgGenerate.maximumSize = CGSize(width: 360, height: 360) + do { + let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil) + try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath) + saved = true + } catch { + saved = false + } + + return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil + + } + + private func getThumbnailPath(for url: URL) -> URL { + let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "") + let path = FileManager.default + .containerURL(forSecurityApplicationGroupIdentifier: appGroupId)! + .appendingPathComponent("\(fileName).jpg") + return path + } + + class SharedMediaFile: Codable { + var path: String; // can be image, video or url path. It can also be text content + var thumbnail: String?; // video thumbnail + var duration: Double?; // video duration in milliseconds + var type: SharedMediaType; + + + init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) { + self.path = path + self.thumbnail = thumbnail + self.duration = duration + self.type = type + } + + // Debug method to print out SharedMediaFile details in the console + func toString() { + print("[SharedMediaFile] \n\tpath: \(self.path)\n\tthumbnail: \(self.thumbnail)\n\tduration: \(self.duration)\n\ttype: \(self.type)") + } + } + + enum SharedMediaType: Int, Codable { + case image + case video + case file + } + + func toData(data: [SharedMediaFile]) -> Data { + let encodedData = try? JSONEncoder().encode(data) + return encodedData! + } } extension Array { diff --git a/pubspec.yaml b/pubspec.yaml index b92a85c9c..7b9716028 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -144,7 +144,10 @@ dependencies: package_info_plus: 1.4.1 - receive_sharing_intent: 1.4.5 + receive_sharing_intent: + git: + url: https://github.com/ManhNTX/receive_sharing_intent.git + ref: master dropdown_button2: 1.4.0