From 168d75e4ba5d71697b3c53b7df2cd7c4f777900a Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sat, 23 Mar 2024 03:54:17 +0100 Subject: [PATCH] Support sharing on ios --- ios/Podfile.lock | 22 +- ios/Runner.xcodeproj/project.pbxproj | 246 +++++++++++- ios/Runner/Info.plist | 15 +- ios/Runner/Runner.entitlements | 20 + .../Base.lproj/MainInterface.storyboard | 24 ++ ios/Share Extension/Info.plist | 27 ++ .../Share Extension.entitlements | 12 + ios/Share Extension/ShareViewController.swift | 351 ++++++++++++++++++ 8 files changed, 706 insertions(+), 11 deletions(-) create mode 100644 ios/Runner/Runner.entitlements create mode 100644 ios/Share Extension/Base.lproj/MainInterface.storyboard create mode 100644 ios/Share Extension/Info.plist create mode 100644 ios/Share Extension/Share Extension.entitlements create mode 100644 ios/Share Extension/ShareViewController.swift diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1aef7f7..aec161e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -13,13 +13,15 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (8.20.0): - - SentryPrivate (= 8.20.0) + - receive_sharing_intent_plus (1.0.1): + - Flutter + - Sentry/HybridSDK (8.21.0): + - SentryPrivate (= 8.21.0) - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.20.0) - - SentryPrivate (8.20.0) + - Sentry/HybridSDK (= 8.21.0) + - SentryPrivate (8.21.0) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -40,6 +42,7 @@ 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`) - 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`) @@ -67,6 +70,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" sentry_flutter: :path: ".symlinks/plugins/sentry_flutter/ios" share_plus: @@ -84,13 +89,14 @@ SPEC CHECKSUMS: device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_custom_tabs_ios: 62439c843b2691aae516fd50119a01eb9755fff7 - flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef + flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c - Sentry: a8d7b373b9f9868442b02a0c425192f693103cbf - sentry_flutter: 03e7660857a8cdb236e71456a7e8447b65c8a788 - SentryPrivate: 006b24af16828441f70e2ab6adf241bd0a8ad130 + receive_sharing_intent_plus: f02db0a23c17f7aeef8de465cb2d8705a46143a6 + Sentry: ebc12276bd17613a114ab359074096b6b3725203 + sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e + SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 5f57e10..ba9dd67 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 0DD98D4C2BAE797F00804BE7 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DD98D4B2BAE797F00804BE7 /* ShareViewController.swift */; }; + 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 */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; @@ -19,6 +22,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 0DD98D512BAE797F00804BE7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0DD98D482BAE797F00804BE7; + remoteInfo = "Share Extension"; + }; 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; @@ -29,6 +39,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 0DD98D542BAE797F00804BE7 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 0DD98D532BAE797F00804BE7 /* Share Extension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -43,6 +64,12 @@ /* 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 = ""; }; + 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 = ""; }; + 0DD98D4E2BAE797F00804BE7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; + 0DD98D502BAE797F00804BE7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 0DD98D592BAE79B700804BE7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; + 0DD98D5A2BAE79BE00804BE7 /* Share Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Share Extension.entitlements"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; @@ -68,6 +95,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 0DD98D462BAE797F00804BE7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 6AD84DA433147BB17C6D6D03 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -87,6 +121,17 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0DD98D4A2BAE797F00804BE7 /* Share Extension */ = { + isa = PBXGroup; + children = ( + 0DD98D5A2BAE79BE00804BE7 /* Share Extension.entitlements */, + 0DD98D4B2BAE797F00804BE7 /* ShareViewController.swift */, + 0DD98D4D2BAE797F00804BE7 /* MainInterface.storyboard */, + 0DD98D502BAE797F00804BE7 /* Info.plist */, + ); + path = "Share Extension"; + sourceTree = ""; + }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -114,7 +159,6 @@ FE730FDF3A8382D8C7AE8CCB /* Pods-RunnerTests.release.xcconfig */, 8E8607A85826990422E9A93E /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -134,6 +178,7 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + 0DD98D4A2BAE797F00804BE7 /* Share Extension */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 7B83F3B4F4DBEDE770813DD7 /* Pods */, @@ -146,6 +191,7 @@ children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + 0DD98D492BAE797F00804BE7 /* Share Extension.appex */, ); name = Products; sourceTree = ""; @@ -153,6 +199,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 0DD98D592BAE79B700804BE7 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -168,6 +215,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 0DD98D482BAE797F00804BE7 /* Share Extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0DD98D582BAE797F00804BE7 /* Build configuration list for PBXNativeTarget "Share Extension" */; + buildPhases = ( + 0DD98D452BAE797F00804BE7 /* Sources */, + 0DD98D462BAE797F00804BE7 /* Frameworks */, + 0DD98D472BAE797F00804BE7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Share Extension"; + productName = "Share Extension"; + productReference = 0DD98D492BAE797F00804BE7 /* Share Extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; 331C8080294A63A400263BE5 /* RunnerTests */ = { isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; @@ -197,12 +261,14 @@ 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 0DD98D542BAE797F00804BE7 /* Embed Foundation Extensions */, 9A635442A56AC99A9E0267CA /* [CP] Embed Pods Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( + 0DD98D522BAE797F00804BE7 /* PBXTargetDependency */, ); name = Runner; productName = Runner; @@ -216,9 +282,13 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { + 0DD98D482BAE797F00804BE7 = { + CreatedOnToolsVersion = 15.3; + }; 331C8080294A63A400263BE5 = { CreatedOnToolsVersion = 14.0; TestTargetID = 97C146ED1CF9000F007C117D; @@ -244,11 +314,20 @@ targets = ( 97C146ED1CF9000F007C117D /* Runner */, 331C8080294A63A400263BE5 /* RunnerTests */, + 0DD98D482BAE797F00804BE7 /* Share Extension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 0DD98D472BAE797F00804BE7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0DD98D4F2BAE797F00804BE7 /* Base in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 331C807F294A63A400263BE5 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -365,6 +444,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 0DD98D452BAE797F00804BE7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0DD98D4C2BAE797F00804BE7 /* ShareViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 331C807D294A63A400263BE5 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -385,6 +472,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 0DD98D522BAE797F00804BE7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0DD98D482BAE797F00804BE7 /* Share Extension */; + targetProxy = 0DD98D512BAE797F00804BE7 /* PBXContainerItemProxy */; + }; 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97C146ED1CF9000F007C117D /* Runner */; @@ -393,6 +485,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 0DD98D4D2BAE797F00804BE7 /* MainInterface.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 0DD98D4E2BAE797F00804BE7 /* Base */, + ); + name = MainInterface.storyboard; + sourceTree = ""; + }; 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -412,6 +512,126 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 0DD98D552BAE797F00804BE7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.com.jgeek00.linkdy; + DEVELOPMENT_TEAM = 38Z3B9TJTR; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Share Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Share Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.jgeek00.linkdy.Share-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 0DD98D562BAE797F00804BE7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.com.jgeek00.linkdy; + DEVELOPMENT_TEAM = 38Z3B9TJTR; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Share Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Share Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.jgeek00.linkdy.Share-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 0DD98D572BAE797F00804BE7 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = "Share Extension/Share Extension.entitlements"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.com.jgeek00.linkdy; + DEVELOPMENT_TEAM = 38Z3B9TJTR; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "Share Extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = "Share Extension"; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.jgeek00.linkdy.Share-Extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { @@ -466,13 +686,17 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.com.jgeek00.linkdy; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Linkdy; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -646,13 +870,17 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.com.jgeek00.linkdy; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Linkdy; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -670,13 +898,17 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.com.jgeek00.linkdy; DEVELOPMENT_TEAM = 38Z3B9TJTR; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Linkdy; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -692,6 +924,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 0DD98D582BAE797F00804BE7 /* Build configuration list for PBXNativeTarget "Share Extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0DD98D552BAE797F00804BE7 /* Debug */, + 0DD98D562BAE797F00804BE7 /* Release */, + 0DD98D572BAE797F00804BE7 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 77869c1..7582bea 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -27,6 +27,19 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? + AppGroupId + $(CUSTOM_GROUP_ID) + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER) + + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS @@ -58,4 +71,4 @@ - \ No newline at end of file + diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..e100544 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,20 @@ + + + + + aps-environment + development + com.apple.developer.associated-domains + + applinks:example.com + + com.apple.developer.associated-domains + + applinks:example.com + + com.apple.security.application-groups + + group.com.jgeek00.linkdy + + + \ No newline at end of file diff --git a/ios/Share Extension/Base.lproj/MainInterface.storyboard b/ios/Share Extension/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000..286a508 --- /dev/null +++ b/ios/Share Extension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Share Extension/Info.plist b/ios/Share Extension/Info.plist new file mode 100644 index 0000000..c2c6ff9 --- /dev/null +++ b/ios/Share Extension/Info.plist @@ -0,0 +1,27 @@ + + + + + AppGroupId + $(CUSTOM_GROUP_ID) + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + + NSExtensionActivationSupportsText + + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + diff --git a/ios/Share Extension/Share Extension.entitlements b/ios/Share Extension/Share Extension.entitlements new file mode 100644 index 0000000..b9766cf --- /dev/null +++ b/ios/Share Extension/Share Extension.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.developer.networking.wifi-info + + com.apple.security.application-groups + + group.com.jgeek00.linkdy + + + diff --git a/ios/Share Extension/ShareViewController.swift b/ios/Share Extension/ShareViewController.swift new file mode 100644 index 0000000..7245587 --- /dev/null +++ b/ios/Share Extension/ShareViewController.swift @@ -0,0 +1,351 @@ +import UIKit +import Social +import MobileCoreServices +import Photos + +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 + } + + 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 + } +}