diff --git a/.gitignore b/.gitignore index c19285e..9f4be94 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/ios/Podfile b/ios/Podfile index d97f17e..a6a8bde 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -35,6 +35,10 @@ target 'Runner' do target 'RunnerTests' do inherit! :search_paths end + + target 'Share Extension' do + inherit! :search_paths + end end post_install do |installer| diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f26718a..d0166d4 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2,9 +2,9 @@ PODS: - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) - - flutter_custom_tabs_ios (2.0.0): + - flutter_custom_tabs_ios (2.2.0): - Flutter - - flutter_native_splash (0.0.1): + - flutter_native_splash (2.4.3): - Flutter - irondash_engine_context (0.0.1): - Flutter @@ -13,13 +13,13 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - receive_sharing_intent_plus (1.0.1): + - receive_sharing_intent (1.8.1): - Flutter - - Sentry/HybridSDK (8.30.1) - - sentry_flutter (8.4.0): + - Sentry/HybridSDK (8.46.0) + - sentry_flutter (8.14.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.30.1) + - Sentry/HybridSDK (= 8.46.0) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -31,6 +31,7 @@ PODS: - Flutter - webview_flutter_wkwebview (0.0.1): - Flutter + - FlutterMacOS DEPENDENCIES: - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) @@ -40,13 +41,13 @@ DEPENDENCIES: - irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - receive_sharing_intent_plus (from `.symlinks/plugins/receive_sharing_intent_plus/ios`) + - receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: @@ -67,8 +68,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" - receive_sharing_intent_plus: - :path: ".symlinks/plugins/receive_sharing_intent_plus/ios" + receive_sharing_intent: + :path: ".symlinks/plugins/receive_sharing_intent/ios" sentry_flutter: :path: ".symlinks/plugins/sentry_flutter/ios" share_plus: @@ -80,25 +81,25 @@ EXTERNAL SOURCES: url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_custom_tabs_ios: a651b18786388923b62de8c0537607de87c2eccf - flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 + flutter_custom_tabs_ios: 194897f423eb72b086009d34136ada2fa321cda2 + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9 - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - receive_sharing_intent_plus: f02db0a23c17f7aeef8de465cb2d8705a46143a6 - Sentry: 514a3ea653886e9a48c6287d8b7bf05ec24bf3be - sentry_flutter: edc037f7af0dc1512d6c33a5c2c7c838bd0d6806 + receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1 + Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854 + sentry_flutter: 6a134f9d381e49f22ea25a67736cf0cf4d02ec9c share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1 + webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188 -PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 +PODFILE CHECKSUM: 34ecb9fa7bc683e441dba3fa6c29261a8d697f71 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 49d7508..357fb51 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 0DD98D4F2BAE797F00804BE7 /* Base in Resources */ = {isa = PBXBuildFile; fileRef = 0DD98D4E2BAE797F00804BE7 /* Base */; }; 0DD98D532BAE797F00804BE7 /* Share Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0DD98D492BAE797F00804BE7 /* Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 1E57812B8C12FEC8D79AD975 /* Pods_Share_Extension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D82812D4EFCC75C7B0FDB517 /* Pods_Share_Extension.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; @@ -67,6 +68,7 @@ /* Begin PBXFileReference section */ 028E6C730A588E1E84511D36 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 033AEC9585A60167C7A7CE55 /* Pods-Share Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.release.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.release.xcconfig"; sourceTree = ""; }; 0D88524F2BB0F36E0049FE7A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 0DD98D492BAE797F00804BE7 /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 0DD98D4B2BAE797F00804BE7 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; @@ -79,6 +81,7 @@ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 67C02B529825EA1C87E41529 /* Pods-Share Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.debug.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -92,9 +95,11 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; ABB81CF75DD6CCAA2824780A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D7D2E01FF2421427934DCB2A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + D82812D4EFCC75C7B0FDB517 /* Pods_Share_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Share_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E4533A4D5154C6705434104C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F0C2D0B1825F8EE28A1E5C13 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; F7EBE0A0B68411B7E3F62A40 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + FAF7552BBEA34A8D0259E3C6 /* Pods-Share Extension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Share Extension.profile.xcconfig"; path = "Target Support Files/Pods-Share Extension/Pods-Share Extension.profile.xcconfig"; sourceTree = ""; }; FE730FDF3A8382D8C7AE8CCB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -103,6 +108,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1E57812B8C12FEC8D79AD975 /* Pods_Share_Extension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -149,6 +155,7 @@ children = ( E4533A4D5154C6705434104C /* Pods_Runner.framework */, ABB81CF75DD6CCAA2824780A /* Pods_RunnerTests.framework */, + D82812D4EFCC75C7B0FDB517 /* Pods_Share_Extension.framework */, ); name = Frameworks; sourceTree = ""; @@ -162,6 +169,9 @@ D7D2E01FF2421427934DCB2A /* Pods-RunnerTests.debug.xcconfig */, FE730FDF3A8382D8C7AE8CCB /* Pods-RunnerTests.release.xcconfig */, 8E8607A85826990422E9A93E /* Pods-RunnerTests.profile.xcconfig */, + 67C02B529825EA1C87E41529 /* Pods-Share Extension.debug.xcconfig */, + 033AEC9585A60167C7A7CE55 /* Pods-Share Extension.release.xcconfig */, + FAF7552BBEA34A8D0259E3C6 /* Pods-Share Extension.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -224,6 +234,7 @@ isa = PBXNativeTarget; buildConfigurationList = 0DD98D582BAE797F00804BE7 /* Build configuration list for PBXNativeTarget "Share Extension" */; buildPhases = ( + 1CD2B5D70FA0C78B9CD70358 /* [CP] Check Pods Manifest.lock */, 0DD98D452BAE797F00804BE7 /* Sources */, 0DD98D462BAE797F00804BE7 /* Frameworks */, 0DD98D472BAE797F00804BE7 /* Resources */, @@ -357,6 +368,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1CD2B5D70FA0C78B9CD70358 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Share Extension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3A41E757C007027010B2D4CC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -522,6 +555,7 @@ /* Begin XCBuildConfiguration section */ 0DD98D552BAE797F00804BE7 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 67C02B529825EA1C87E41529 /* Pods-Share Extension.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -564,6 +598,7 @@ }; 0DD98D562BAE797F00804BE7 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 033AEC9585A60167C7A7CE55 /* Pods-Share Extension.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -603,6 +638,7 @@ }; 0DD98D572BAE797F00804BE7 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FAF7552BBEA34A8D0259E3C6 /* Pods-Share Extension.profile.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5d..15cada4 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/ios/Share Extension/Info.plist b/ios/Share Extension/Info.plist index c2c6ff9..9223385 100644 --- a/ios/Share Extension/Info.plist +++ b/ios/Share Extension/Info.plist @@ -12,7 +12,7 @@ NSExtensionActivationSupportsText - + NSExtensionActivationSupportsWebURLWithMaxCount 1 @@ -24,4 +24,4 @@ com.apple.share-services - + \ No newline at end of file diff --git a/ios/Share Extension/ShareViewController.swift b/ios/Share Extension/ShareViewController.swift index 7245587..f9364fc 100644 --- a/ios/Share Extension/ShareViewController.swift +++ b/ios/Share Extension/ShareViewController.swift @@ -1,351 +1,14 @@ -import UIKit -import Social -import MobileCoreServices -import Photos +// If you get no such module 'receive_sharing_intent' error. +// Go to Build Phases of your Runner target and +// move `Embed Foundation Extension` to the top of `Thin Binary`. +import receive_sharing_intent -class ShareViewController: SLComposeServiceViewController { - var hostAppBundleIdentifier = "com.jgeek00.linkdy" - var appGroupId = "group.com.jgeek00.linkdy" - 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 +class ShareViewController: RSIShareViewController { + + // Use this method to return false if you don't want to redirect to host app automatically. + // Default is true + override func shouldAutoRedirect() -> Bool { + return false } - private func loadIds() { - // loading Share extension App Id - let shareExtensionAppBundleIdentifier = Bundle.main.bundleIdentifier! - - // convert ShareExtension id to host app id - // By default it is removed the last part of id after the last point - // For example: com.test.ShareExtension -> com.test - if let lastIndexOfPoint = shareExtensionAppBundleIdentifier.lastIndex(of: ".") { - hostAppBundleIdentifier = String(shareExtensionAppBundleIdentifier[.. - appGroupId = (Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String) ?? "group.\(hostAppBundleIdentifier)" - } - - override func viewDidLoad() { - super.viewDidLoad() - - // load group and app id from build info - loadIds() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. - if let content = extensionContext?.inputItems[0] as? NSExtensionItem { - if let contents = content.attachments { - for (index, attachment) in contents.enumerated() { - if attachment.hasItemConformingToTypeIdentifier(imageContentType) { - handleImages(content: content, attachment: attachment, index: index) - } else if attachment.hasItemConformingToTypeIdentifier(textContentType) { - handleText(content: content, attachment: attachment, index: index) - } else if attachment.hasItemConformingToTypeIdentifier(fileURLType) { - handleFiles(content: content, attachment: attachment, index: index) - } else if attachment.hasItemConformingToTypeIdentifier(urlContentType) { - handleUrl(content: content, attachment: attachment, index: index) - } else if attachment.hasItemConformingToTypeIdentifier(videoContentType) { - handleVideos(content: content, attachment: attachment, index: index) - } - } - } - } - } - - override func didSelectPost() { - print("didSelectPost") - } - - override func configurationItems() -> [Any]! { - // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. - return [] - } - - private func handleText(content: NSExtensionItem, attachment: NSItemProvider, index: Int) { - attachment.loadItem(forTypeIdentifier: textContentType, options: nil) { [weak self] data, error in - - if error == nil, let item = data as? String, let this = self { - - this.sharedText.append(item) - - // If this is the last item, save imagesData in userDefaults and redirect to the host app - if index == (content.attachments?.count)! - 1 { - let userDefaults = UserDefaults(suiteName: this.appGroupId) - userDefaults?.set(this.sharedText, forKey: this.sharedKey) - userDefaults?.synchronize() - this.redirectToHostApp(type: .text) - } - - } else { - self?.dismissWithError() - } - } - } - - private func handleUrl(content: NSExtensionItem, attachment: NSItemProvider, index: Int) { - attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in - - if error == nil, let item = data as? URL, let this = self { - - this.sharedText.append(item.absoluteString) - - // If this is the last item, save imagesData in userDefaults and redirect to the host app - if index == (content.attachments?.count)! - 1 { - let userDefaults = UserDefaults(suiteName: this.appGroupId) - userDefaults?.set(this.sharedText, forKey: this.sharedKey) - userDefaults?.synchronize() - this.redirectToHostApp(type: .text) - } - - } else { - self?.dismissWithError() - } - } - } - - private func handleImages(content: NSExtensionItem, attachment: NSItemProvider, index: Int) { - attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in - - if error == nil, let url = data as? URL, let this = self { - - // Always copy - let fileName = this.getFileName(from: url, type: .image) - let newPath = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: this.appGroupId)! - .appendingPathComponent(fileName) - let copied = this.copyFile(at: url, to: newPath) - if copied { - this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image)) - } - - // If this is the last item, save imagesData in userDefaults and redirect to the host app - if index == (content.attachments?.count)! - 1 { - let userDefaults = UserDefaults(suiteName: this.appGroupId) - userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey) - userDefaults?.synchronize() - this.redirectToHostApp(type: .media) - } - - } else { - self?.dismissWithError() - } - } - } - - private func handleVideos(content: NSExtensionItem, attachment: NSItemProvider, index: Int) { - attachment.loadItem(forTypeIdentifier: videoContentType, options: nil) { [weak self] data, error in - - if error == nil, let url = data as? URL, let this = self { - - // Always copy - let fileName = this.getFileName(from: url, type: .video) - let newPath = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: this.appGroupId)! - .appendingPathComponent(fileName) - let copied = this.copyFile(at: url, to: newPath) - if copied { - guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else { - return - } - this.sharedMedia.append(sharedFile) - } - - // If this is the last item, save imagesData in userDefaults and redirect to the host app - if index == (content.attachments?.count)! - 1 { - let userDefaults = UserDefaults(suiteName: this.appGroupId) - userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey) - userDefaults?.synchronize() - this.redirectToHostApp(type: .media) - } - - } else { - self?.dismissWithError() - } - } - } - - private func handleFiles(content: NSExtensionItem, attachment: NSItemProvider, index: Int) { - attachment.loadItem(forTypeIdentifier: fileURLType, options: nil) { [weak self] data, error in - - if error == nil, let url = data as? URL, let this = self { - - // Always copy - let fileName = this.getFileName(from: url, type: .file) - let newPath = FileManager.default - .containerURL(forSecurityApplicationGroupIdentifier: this.appGroupId)! - .appendingPathComponent(fileName) - let copied = this.copyFile(at: url, to: newPath) - if copied { - this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .file)) - } - - if index == (content.attachments?.count)! - 1 { - let userDefaults = UserDefaults(suiteName: this.appGroupId) - userDefaults?.set(this.toData(data: this.sharedMedia), forKey: this.sharedKey) - userDefaults?.synchronize() - this.redirectToHostApp(type: .file) - } - - } else { - self?.dismissWithError() - } - } - } - - private func dismissWithError() { - print("[ERROR] Error loading data!") - let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert) - - let action = UIAlertAction(title: "Error", style: .cancel) { _ in - self.dismiss(animated: true, completion: nil) - } - - alert.addAction(action) - present(alert, animated: true, completion: nil) - extensionContext?.completeRequest(returningItems: [], completionHandler: nil) - } - - private func redirectToHostApp(type: RedirectType) { - // ids may not be loaded yet so we need loadIds here too - loadIds() - let url = URL(string: "ShareMedia-\(hostAppBundleIdentifier)://dataUrl=\(sharedKey)#\(type)") - var responder = self as UIResponder? - let selectorOpenURL = sel_registerName("openURL:") - - while (responder != nil) { - if (responder?.responds(to: selectorOpenURL))! { - let _ = responder?.perform(selectorOpenURL, with: url) - } - responder = responder?.next - } - extensionContext?.completeRequest(returningItems: [], completionHandler: nil) - } - - enum RedirectType { - case media - case text - case file - } - - func getExtension(from url: URL, type: SharedMediaType) -> 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 - 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 - var thumbnail: String? - var duration: Double? - var type: SharedMediaType - - init(path: String, thumbnail: String?, duration: Double?, type: SharedMediaType) { - self.path = path - self.thumbnail = thumbnail - self.duration = duration - self.type = type - } - - 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 ?? Data() - } -} - -extension Array { - subscript (safe index: UInt) -> Element? { - return Int(index) < count ? self[Int(index)] : nil - } -} +} \ No newline at end of file