diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 44a350d..bc0b3a7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -33,6 +33,16 @@ + + + + + + + + + + @@ -46,19 +56,11 @@ android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService" tools:node="remove"> - - - - - diff --git a/dependencies b/dependencies index 83475a9..7930d97 160000 --- a/dependencies +++ b/dependencies @@ -1 +1 @@ -Subproject commit 83475a912851acb6a718ea32a6f0f754d64a50d8 +Subproject commit 7930d9727019344238297d810661bc3e8f724c37 diff --git a/ios/Podfile b/ios/Podfile index a5993b4..f30e30f 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -43,6 +43,13 @@ target 'Runner' do target 'RunnerTests' do inherit! :search_paths end + + # Share Extension is name of Extension which you created which is in this case 'Share Extension' + target 'ShareExtension' do + inherit! :search_paths + # flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + end + end post_install do |installer| diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a6414db..301b4b9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -122,6 +122,8 @@ PODS: - flutter_secure_storage_darwin (10.0.0): - Flutter - FlutterMacOS + - flutter_sharing_intent (1.0.1): + - Flutter - flutter_volume_controller (0.0.1): - Flutter - gal (1.0.0): @@ -260,7 +262,7 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) - - "no_screenshot (0.0.1+4)": + - no_screenshot (0.3.2-beta.3): - Flutter - ScreenProtectorKit (~> 1.3.1) - objective_c (0.0.1): @@ -350,6 +352,7 @@ DEPENDENCIES: - flutter_keyboard_visibility_temp_fork (from `.symlinks/plugins/flutter_keyboard_visibility_temp_fork/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) + - flutter_sharing_intent (from `.symlinks/plugins/flutter_sharing_intent/ios`) - flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`) - gal (from `.symlinks/plugins/gal/darwin`) - google_mlkit_barcode_scanning (from `.symlinks/plugins/google_mlkit_barcode_scanning/ios`) @@ -441,6 +444,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage_darwin: :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" + flutter_sharing_intent: + :path: ".symlinks/plugins/flutter_sharing_intent/ios" flutter_volume_controller: :path: ".symlinks/plugins/flutter_volume_controller/ios" gal: @@ -507,7 +512,8 @@ SPEC CHECKSUMS: flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 flutter_keyboard_visibility_temp_fork: 95b2d534bacf6ac62e7fcbe5c2a9e2c2a17ce06f flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468 + flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 + flutter_sharing_intent: 0c1e53949f09fa8df8ac2268505687bde8ff264c flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb gal: baecd024ebfd13c441269ca7404792a7152fde89 google_mlkit_barcode_scanning: 8f5987f244a43fe1167689c548342a5174108159 @@ -529,7 +535,7 @@ SPEC CHECKSUMS: MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - no_screenshot: 6d183496405a3ab709a67a54e5cd0f639e94729e + no_screenshot: 89e778ede9f1e39cc3fb9404d782a42712f2a0b2 objective_c: 89e720c30d716b036faf9c9684022048eee1eee2 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 @@ -551,6 +557,6 @@ SPEC CHECKSUMS: url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a -PODFILE CHECKSUM: c0c524475498435108850efecde62ba98e081c25 +PODFILE CHECKSUM: ae041999f13ba7b2285ff9ad9bc688ed647bbcb7 COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index fb716f3..5739f2b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 05CF222065FC24670B05B6D0 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */; }; 06AA21445BEAF2C45DC9DCDF /* Pods_NotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 30EBDD0F93DC44E774F3B785 /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E190E82D9973B318A389650B /* Pods_ShareExtension.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 */; }; @@ -19,6 +20,7 @@ CA4FDF5DD8F229C30DE512AF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */; }; D21FCEAB2D9F2B750088701D /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D21FCEA42D9F2B750088701D /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; D25D4D1E2EF626E30029F805 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D25D4D1D2EF626E30029F805 /* StoreKit.framework */; }; + D25D4D7A2EFF41DB0029F805 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D25D4D702EFF41DB0029F805 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; F3C66D726A2EB28484DF0B10 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */; }; /* End PBXBuildFile section */ @@ -37,6 +39,13 @@ remoteGlobalIDString = D21FCEA32D9F2B750088701D; remoteInfo = NotificationService; }; + D25D4D782EFF41DB0029F805 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = D25D4D6F2EFF41DB0029F805; + remoteInfo = ShareExtension; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -56,6 +65,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + D25D4D7A2EFF41DB0029F805 /* ShareExtension.appex in Embed Foundation Extensions */, D21FCEAB2D9F2B750088701D /* NotificationService.appex in Embed Foundation Extensions */, ); name = "Embed Foundation Extensions"; @@ -67,10 +77,12 @@ 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 = ""; }; 1581CC44342D555EFB889768 /* 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 = ""; }; + 15CEF849B61A620CFB2DC5F1 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; 16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; 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; }; 35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig"; sourceTree = ""; }; + 39FB86A38393489D58A01B0B /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig"; sourceTree = ""; }; 6EB462F87F0A23758713308F /* 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 = ""; }; @@ -87,11 +99,15 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A22BD564F16069E5FCB60767 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; }; B3B27B7FBEEA31DB7793A0C2 /* 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 = ""; }; D21FCEA42D9F2B750088701D /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; D2265DD42D920142000D99BB /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; D25D4D1D2EF626E30029F805 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + D25D4D702EFF41DB0029F805 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + D25D4D802EFF437F0029F805 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E190E82D9973B318A389650B /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E96A5ACA32A7118204F050A5 /* 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 = ""; }; EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NotificationService.profile.xcconfig"; path = "Target Support Files/Pods-NotificationService/Pods-NotificationService.profile.xcconfig"; sourceTree = ""; }; @@ -105,6 +121,13 @@ ); target = D21FCEA32D9F2B750088701D /* NotificationService */; }; + D25D4D7E2EFF41DB0029F805 /* Exceptions for "ShareExtension" folder in "ShareExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = D25D4D6F2EFF41DB0029F805 /* ShareExtension */; + }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -116,6 +139,14 @@ path = NotificationService; sourceTree = ""; }; + D25D4D712EFF41DB0029F805 /* ShareExtension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + D25D4D7E2EFF41DB0029F805 /* Exceptions for "ShareExtension" folder in "ShareExtension" target */, + ); + path = ShareExtension; + sourceTree = ""; + }; /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ @@ -144,6 +175,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D25D4D6D2EFF41DB0029F805 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 30EBDD0F93DC44E774F3B785 /* Pods_ShareExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -172,6 +211,7 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, D21FCEA52D9F2B750088701D /* NotificationService */, + D25D4D712EFF41DB0029F805 /* ShareExtension */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */, @@ -186,6 +226,7 @@ 97C146EE1CF9000F007C117D /* Runner.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, D21FCEA42D9F2B750088701D /* NotificationService.appex */, + D25D4D702EFF41DB0029F805 /* ShareExtension.appex */, ); name = Products; sourceTree = ""; @@ -193,6 +234,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + D25D4D802EFF437F0029F805 /* RunnerDebug.entitlements */, D2265DD42D920142000D99BB /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -213,6 +255,7 @@ A198C9B5D90584C4F96206B2 /* Pods_NotificationService.framework */, EE2CCFEE4ABECF33852F7735 /* Pods_Runner.framework */, DC1EE71614E1B4F84D6FDC2D /* Pods_RunnerTests.framework */, + E190E82D9973B318A389650B /* Pods_ShareExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -229,6 +272,9 @@ 4D78471482626812FE2468E9 /* Pods-NotificationService.debug.xcconfig */, 35366FD433E0EFC6EF19A452 /* Pods-NotificationService.release.xcconfig */, F02F7A1D63544AA9F23A1085 /* Pods-NotificationService.profile.xcconfig */, + 15CEF849B61A620CFB2DC5F1 /* Pods-ShareExtension.debug.xcconfig */, + A22BD564F16069E5FCB60767 /* Pods-ShareExtension.release.xcconfig */, + 39FB86A38393489D58A01B0B /* Pods-ShareExtension.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -274,6 +320,7 @@ ); dependencies = ( D21FCEAA2D9F2B750088701D /* PBXTargetDependency */, + D25D4D792EFF41DB0029F805 /* PBXTargetDependency */, ); name = Runner; productName = Runner; @@ -301,6 +348,27 @@ productReference = D21FCEA42D9F2B750088701D /* NotificationService.appex */; productType = "com.apple.product-type.app-extension"; }; + D25D4D6F2EFF41DB0029F805 /* ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = D25D4D7F2EFF41DB0029F805 /* Build configuration list for PBXNativeTarget "ShareExtension" */; + buildPhases = ( + 627F39EA1643E08048D23996 /* [CP] Check Pods Manifest.lock */, + D25D4D6C2EFF41DB0029F805 /* Sources */, + D25D4D6D2EFF41DB0029F805 /* Frameworks */, + D25D4D6E2EFF41DB0029F805 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + D25D4D712EFF41DB0029F805 /* ShareExtension */, + ); + name = ShareExtension; + productName = ShareExtension; + productReference = D25D4D702EFF41DB0029F805 /* ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -308,7 +376,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1620; + LastSwiftUpdateCheck = 2610; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -323,6 +391,9 @@ D21FCEA32D9F2B750088701D = { CreatedOnToolsVersion = 16.2; }; + D25D4D6F2EFF41DB0029F805 = { + CreatedOnToolsVersion = 26.1.1; + }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -341,6 +412,7 @@ 97C146ED1CF9000F007C117D /* Runner */, 331C8080294A63A400263BE5 /* RunnerTests */, D21FCEA32D9F2B750088701D /* NotificationService */, + D25D4D6F2EFF41DB0029F805 /* ShareExtension */, ); }; /* End PBXProject section */ @@ -372,6 +444,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D25D4D6E2EFF41DB0029F805 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -452,6 +531,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; + 627F39EA1643E08048D23996 /* [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-ShareExtension-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; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -533,6 +634,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D25D4D6C2EFF41DB0029F805 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -546,6 +654,11 @@ target = D21FCEA32D9F2B750088701D /* NotificationService */; targetProxy = D21FCEA92D9F2B750088701D /* PBXContainerItemProxy */; }; + D25D4D792EFF41DB0029F805 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D25D4D6F2EFF41DB0029F805 /* ShareExtension */; + targetProxy = D25D4D782EFF41DB0029F805 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -631,6 +744,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.eu.twonly.shareIntent; DEVELOPMENT_TEAM = CN332ZUGRP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -827,8 +941,9 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.eu.twonly.shareIntent; DEVELOPMENT_TEAM = CN332ZUGRP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -863,6 +978,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + CUSTOM_GROUP_ID = group.eu.twonly.shareIntent; DEVELOPMENT_TEAM = CN332ZUGRP; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -1004,6 +1120,133 @@ }; name = Profile; }; + D25D4D7B2EFF41DB0029F805 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 15CEF849B61A620CFB2DC5F1 /* Pods-ShareExtension.debug.xcconfig */; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtensionDebug.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.eu.twonly.shareIntent; + DEVELOPMENT_TEAM = CN332ZUGRP; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + 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 = eu.twonly.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D25D4D7C2EFF41DB0029F805 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A22BD564F16069E5FCB60767 /* Pods-ShareExtension.release.xcconfig */; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.eu.twonly.shareIntent; + DEVELOPMENT_TEAM = CN332ZUGRP; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + 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 = eu.twonly.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + D25D4D7D2EFF41DB0029F805 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 39FB86A38393489D58A01B0B /* Pods-ShareExtension.profile.xcconfig */; + 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_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + CUSTOM_GROUP_ID = group.eu.twonly.shareIntent; + DEVELOPMENT_TEAM = CN332ZUGRP; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ShareExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 26.1; + 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 = eu.twonly.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1047,6 +1290,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D25D4D7F2EFF41DB0029F805 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D25D4D7B2EFF41DB0029F805 /* Debug */, + D25D4D7C2EFF41DB0029F805 /* Release */, + D25D4D7D2EFF41DB0029F805 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index ae7cbe1..b36ae26 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -3,6 +3,7 @@ import Flutter import Foundation import UIKit import UserNotifications +import flutter_sharing_intent @main @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { @@ -14,6 +15,17 @@ import UserNotifications return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + + let sharingIntent = SwiftFlutterSharingIntentPlugin.instance + if sharingIntent.hasSameSchemePrefix(url: url) { + return sharingIntent.application(app, open: url, options: options) + } + + // Proceed url handling for other Flutter libraries like app_links + return super.application(app, open: url, options:options) + } + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d00d31e..8b4c63d 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -87,9 +87,21 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - firebase_performance_collection_deactivated - + + AppGroupId + $(CUSTOM_GROUP_ID) + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + SharingMedia-$(PRODUCT_BUNDLE_IDENTIFIER) + + + diff --git a/ios/Runner/RunnerDebug.entitlements b/ios/Runner/RunnerDebug.entitlements new file mode 100644 index 0000000..dc9d4cc --- /dev/null +++ b/ios/Runner/RunnerDebug.entitlements @@ -0,0 +1,20 @@ + + + + + aps-environment + development + com.apple.developer.associated-domains + + applinks:me.twonly.eu + + com.apple.security.application-groups + + group.eu.twonly.shareIntent + + keychain-access-groups + + $(AppIdentifierPrefix)eu.twonly.shared + + + diff --git a/ios/ShareExtension/Base.lproj/MainInterface.storyboard b/ios/ShareExtension/Base.lproj/MainInterface.storyboard new file mode 100644 index 0000000..286a508 --- /dev/null +++ b/ios/ShareExtension/Base.lproj/MainInterface.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/ShareExtension/FSIShareViewController.swift b/ios/ShareExtension/FSIShareViewController.swift new file mode 100644 index 0000000..9a2e198 --- /dev/null +++ b/ios/ShareExtension/FSIShareViewController.swift @@ -0,0 +1,553 @@ +// SOURCE: https://github.com/bhagat-techind/flutter_sharing_intent/blob/main/example/ios/Share%20Extension/FSIShareViewController.swift + +// FSIShareViewController.swift +// Merged, optimized controller: uses RSI architecture with all FSI features preserved +// Uses model name `SharingFile` (same fields as SharedMediaFile) where `value` = path + +import AVFoundation +import MobileCoreServices +import Social +import UIKit +import UniformTypeIdentifiers + +public let kSchemePrefix = "SharingMedia" +public let kUserDefaultsKey = "SharingKey" +public let kUserDefaultsMessageKey = "SharingMessageKey" +public let kAppGroupIdKey = "AppGroupId" +public let kAppChannel = "flutter_sharing_intent" + +@available(swift, introduced: 5.0) +open class FSIShareViewController: SLComposeServiceViewController { + // MARK: - Config + private(set) var hostAppBundleIdentifier: String = "" + private(set) var appGroupId: String = "" + + // Results + private var sharedMedia: [SharingFile] = [] + + // Debug + private let debugLogs = false + + // MARK: - Lifecycle + open override func viewDidLoad() { + super.viewDidLoad() + loadIds() + } + + open override func isContentValid() -> Bool { + return true + } + + open override func didSelectPost() { + if self.sharedMedia.isEmpty { + if let text = self.contentText, !text.isEmpty { + self.sharedMedia.append( + SharingFile(value: text, thumbnail: nil, duration: nil, type: .text) + ) + self.saveAndRedirect(message: text) + return + } + self.completeAndExit() + } else { + self.saveAndRedirect() + } + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + // Process attachments automatically on appear like original FSI + processAttachments() + } + + // MARK: - Load Ids + private func loadIds() { + let shareExtId = Bundle.main.bundleIdentifier ?? "" + if let idx = shareExtId.lastIndex(of: ".") { + hostAppBundleIdentifier = String(shareExtId[.. media, else fallback to complete + if !self.sharedMedia.isEmpty { + self.saveAndRedirect() + } else { + print("FSIShare: No shared media → stopping.") + self.completeAndExit() + } + } + } + + // MARK: - Individual handlers (preserve FSI behavior) + private func handleTextItem(data: NSSecureCoding?, index: Int, total: Int) { + if let s = data as? String { + sharedMedia.append(SharingFile(value: s, thumbnail: nil, duration: nil, type: .text)) + } else if let url = data as? URL { + sharedMedia.append(SharingFile(value: url.absoluteString, thumbnail: nil, duration: nil, type: .url)) + } + + } + + private func handleUrlItem(data: NSSecureCoding?, index: Int, total: Int) { + if let url = data as? URL { + sharedMedia.append(SharingFile(value: url.absoluteString, thumbnail: nil, duration: nil, type: .url)) + } else if let s = data as? String { + sharedMedia.append(SharingFile(value: s, thumbnail: nil, duration: nil, type: .text)) + } + + } + + private func handleImageItem(data: NSSecureCoding?, index: Int, total: Int) { + // data can be URL, UIImage, or Data + if let url = data as? URL { + let filename = getFileName(from: url, type: .image) + if let dst = containerURL()?.appendingPathComponent(filename) { + if copyFile(at: url, to: dst) { + sharedMedia.append(SharingFile(value: dst.absoluteString, mimeType: url.mimeType(), thumbnail: nil, duration: nil, type: .image)) + } + } + } else if let img = data as? UIImage { + if let saved = writeTempImage(img) { + sharedMedia.append(saved) + } + } else if let raw = data as? Data, let img = UIImage(data: raw) { + if let saved = writeTempImage(img) { + sharedMedia.append(saved) + } + } + + } + + private func handleVideoItem(data: NSSecureCoding?, index: Int, total: Int) { + if let url = data as? URL { + let filename = getFileName(from: url, type: .video) + if let dst = containerURL()?.appendingPathComponent(filename) { + if copyFile(at: url, to: dst) { + if let m = getSharedMediaFile(forVideo: dst) { + sharedMedia.append(m) + } + } + } + } + + } + + private func handleFileItem(data: NSSecureCoding?, index: Int, total: Int) { + if let url = data as? URL { + let filename = getFileName(from: url, type: .file) + if let dst = containerURL()?.appendingPathComponent(filename) { + if copyFile(at: url, to: dst) { + sharedMedia.append(SharingFile(value: dst.absoluteString, mimeType: url.mimeType(), thumbnail: nil, duration: nil, type: .file)) + } + } + } + else if let raw = data as? Data { + let filename = "File_\(UUID().uuidString)" + if let dst = containerURL()?.appendingPathComponent(filename) { + do { + try raw.write(to: dst) + sharedMedia.append(SharingFile(value: dst.absoluteString, mimeType: "application/octet-stream", thumbnail: nil, duration: nil, type: .file)) + } catch {} + } + } + + + } + + // MARK: - Helpers: write temp image + private func writeTempImage(_ image: UIImage) -> SharingFile? { + guard let container = containerURL() else { return nil } + let tempName = "TempImage_\(UUID().uuidString).png" + let dst = container.appendingPathComponent(tempName) + do { + if let d = image.pngData() { + try d.write(to: dst) + let decoded = dst.absoluteString.removingPercentEncoding ?? dst.absoluteString + return SharingFile(value: decoded, mimeType: "image/png", thumbnail: nil, duration: nil, type: .image) + } + } catch { + log("writeTempImage error: \(error)") + } + return nil + } + + + private func saveAndRedirect(message: String? = nil) { + let ud = UserDefaults(suiteName: appGroupId) + if !sharedMedia.isEmpty { + if let data = try? JSONEncoder().encode(sharedMedia) { + ud?.set(data, forKey: kUserDefaultsKey) + } + } + ud?.set(message, forKey: kUserDefaultsMessageKey) + ud?.synchronize() + redirectToHostApp() + } + + + private func redirectToHostApp() { + // kept for compatibility (RSI style) + loadIds() + // let raw = "\(kSchemePrefix)-\(hostAppBundleIdentifier):share" + let raw = "\(kSchemePrefix)-\(hostAppBundleIdentifier)://dataUrl=\(kUserDefaultsKey)" + guard let url = URL(string: raw.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? raw) else { completeAndExit(); return } + + var responder: UIResponder? = self + if #available(iOS 18.0, *) { + while responder != nil { + if let app = responder as? UIApplication { app.open(url, options: [:], completionHandler: nil) } + responder = responder?.next + } + } else { + let sel = sel_registerName("openURL:") + while responder != nil { + if responder?.responds(to: sel) ?? false { _ = responder?.perform(sel, with: url) } + responder = responder?.next + } + } + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } + + // MARK: - File / thumbnail / metadata helpers + func getExtension(from url: URL, type: SharingFileType) -> 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" + case .text: ex = "txt" + case .url: ex = "txt" + } + } + return ex ?? "bin" + } + + func getFileName(from url: URL, type: SharingFileType) -> 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) + return true + } catch { + log("copyFile error: \(error)") + return false + } + } + + private func getSharedMediaFile(forVideo: URL) -> SharingFile? { + 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 SharingFile(value: forVideo.absoluteString, mimeType: forVideo.mimeType(), thumbnail: thumbnailPath.absoluteString, duration: Int(duration), type: .video) + } + + let gen = AVAssetImageGenerator(asset: asset) + gen.appliesPreferredTrackTransform = true + gen.maximumSize = CGSize(width: 360, height: 360) + + // Use first second or zero + let time = CMTime(seconds: min(1.0, CMTimeGetSeconds(asset.duration)), preferredTimescale: 600) + do { + let cg = try gen.copyCGImage(at: time, actualTime: nil) + if let data = UIImage(cgImage: cg).jpegData(compressionQuality: 0.8) { + try data.write(to: thumbnailPath) + return SharingFile(value: forVideo.absoluteString, mimeType: forVideo.mimeType(), thumbnail: thumbnailPath.absoluteString, duration: Int(duration), type: .video) + } + } catch { + log("getSharedMediaFile thumbnail error: \(error)") + } + + // fallback + return SharingFile(value: forVideo.absoluteString, mimeType: forVideo.mimeType(), thumbnail: nil, duration: Int(duration), type: .video) + } + + private func getThumbnailPath(for url: URL) -> URL { + guard let container = containerURL() else { fatalError("App group not configured or missing") } + let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "=", with: "") + return container.appendingPathComponent("\(fileName).jpg") + } + + private func containerURL() -> URL? { + FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupId) + } + + private func completeAndExit() { + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } + + private func dismissWithError() { + log("[ERROR] Error loading data!") + let alert = UIAlertController(title: "Error", message: "Error loading data", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .cancel) { _ in self.dismiss(animated: true, completion: nil) }) + present(alert, animated: true, completion: nil) + extensionContext?.completeRequest(returningItems: [], completionHandler: nil) + } + + private func writeTempFile(_ image: UIImage, to dstURL: URL) -> Bool { + do { + if FileManager.default.fileExists(atPath: dstURL.path) { try FileManager.default.removeItem(at: dstURL) } + let pngData = image.pngData() + try pngData?.write(to: dstURL) + return true + } catch (let error) { + log("writeTempFile error: \(error)") + return false + } + } + + private func saveToUserDefaults(data: [SharingFile]) { + let ud = UserDefaults(suiteName: appGroupId) + if let enc = try? JSONEncoder().encode(data) { ud?.set(enc, forKey: kUserDefaultsKey); ud?.synchronize() } + } + + // MARK: - Logging + private func log(_ s: String) { if debugLogs { print("[FSIShareVC] \(s)") } } + +} + +// MARK: - Extensions +extension URL { + func mimeType() -> String { + if #available(iOS 14.0, *) { + if let ut = UTType(filenameExtension: self.pathExtension), let m = ut.preferredMIMEType { return m } + } else { + if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, self.pathExtension as NSString, nil)?.takeRetainedValue() { + if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() { return mimetype as String } + } + } + return "application/octet-stream" + } +} + +extension NSItemProvider { + var isImage: Bool { return hasItemConformingToTypeIdentifier(UType.image) } + var isMovie: Bool { return hasItemConformingToTypeIdentifier(UType.movie) } + var isText: Bool { + hasItemConformingToTypeIdentifier(UType.plainText) || hasItemConformingToTypeIdentifier(UType.text) + } + var isURL: Bool { return hasItemConformingToTypeIdentifier(UType.url) } + var isFile: Bool { return hasItemConformingToTypeIdentifier(UType.fileURL) } + var isData:Bool { return hasItemConformingToTypeIdentifier(UType.data) } + var isItem: Bool { hasItemConformingToTypeIdentifier(UType.item) } + +} + +extension Array { + subscript(safe index: UInt) -> Element? { return Int(index) < count ? self[Int(index)] : nil } +} + + +class SharingFile: Codable { + var value: String + var mimeType: String? + var thumbnail: String?; // video thumbnail + var duration: Int?; // video duration in milliseconds + var type: SharingFileType; + var message: String? // post message + + enum CodingKeys: String, CodingKey { + case value + case mimeType + case thumbnail + case duration + case type + case message + } + + init(value: String, mimeType: String? = nil, thumbnail: String?, duration: Int?, + type: SharingFileType, message: String?=nil) { + self.value = value + self.mimeType = mimeType + self.thumbnail = thumbnail + self.duration = duration + self.type = type + self.message = message + } + + // Debug method to print out SharedMediaFile details in the console + func toString() { + print("[SharingFile] \n\tvalue: \(self.value)\n\tthumbnail: \(self.thumbnail ?? "--" )\n\tduration: \(self.duration ?? 0)\n\ttype: \(self.type)\n\tmimeType: \(String(describing: self.mimeType))\n\tmessage: \(String(describing: self.message))") + } +} + + +enum SharingFileType: Int, Codable { + case text + case url + case image + case video + case file +} + +// Unified UTType → works on iOS 11–18 +enum UType { + static var image: String { + if #available(iOS 14.0, *) { + return UTType.image.identifier + } else { + return kUTTypeImage as String // old API + } + } + + static var movie: String { + if #available(iOS 14.0, *) { + return UTType.movie.identifier + } else { + return kUTTypeMovie as String + } + } + + + static var url: String { + if #available(iOS 14.0, *) { + return UTType.url.identifier + } else { + return kUTTypeURL as String + } + } + + static var fileURL: String { + if #available(iOS 14.0, *) { + return UTType.fileURL.identifier + } else { + return kUTTypeFileURL as String + } + } + + static var text: String { + if #available(iOS 14.0, *) { + return UTType.text.identifier + } else { + return kUTTypeText as String + } + } + + static var plainText: String { + if #available(iOS 14.0, *) { + return UTType.plainText.identifier + } else { + return kUTTypePlainText as String + } + } + + static var data: String { + if #available(iOS 14.0, *) { + return UTType.data.identifier + } else { + return kUTTypeData as String + } + } + + static var item: String { + if #available(iOS 14.0, *) { + return UTType.item.identifier + } else { + return kUTTypeItem as String + } + } +} diff --git a/ios/ShareExtension/Info.plist b/ios/ShareExtension/Info.plist new file mode 100644 index 0000000..3e8521b --- /dev/null +++ b/ios/ShareExtension/Info.plist @@ -0,0 +1,35 @@ + + + + + AppGroupId + $(CUSTOM_GROUP_ID) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + NSExtension + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + NSExtensionAttributes + + PHSupportedMediaTypes + + Video + Image + + + NSExtensionActivationRule + + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + NSExtensionActivationSupportsImageWithMaxCount + 1 + NSExtensionActivationSupportsMovieWithMaxCount + 1 + + + + + diff --git a/ios/ShareExtension/ShareExtensionDebug.entitlements b/ios/ShareExtension/ShareExtensionDebug.entitlements new file mode 100644 index 0000000..bb03fc7 --- /dev/null +++ b/ios/ShareExtension/ShareExtensionDebug.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.eu.twonly.shareIntent + + + diff --git a/ios/ShareExtension/ShareViewController.swift b/ios/ShareExtension/ShareViewController.swift new file mode 100644 index 0000000..668bd61 --- /dev/null +++ b/ios/ShareExtension/ShareViewController.swift @@ -0,0 +1,3 @@ +class ShareViewController: FSIShareViewController { + +} \ No newline at end of file diff --git a/lib/src/services/intent/links.intent.dart b/lib/src/services/intent/links.intent.dart new file mode 100644 index 0000000..f951695 --- /dev/null +++ b/lib/src/services/intent/links.intent.dart @@ -0,0 +1,180 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:collection/collection.dart'; +import 'package:drift/drift.dart' show Value; +import 'package:flutter/material.dart'; +import 'package:flutter_sharing_intent/model/sharing_file.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/signal/session.signal.dart'; +import 'package:twonly/src/utils/log.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/camera/share_image_editor_view.dart'; +import 'package:twonly/src/views/chats/add_new_user.view.dart'; +import 'package:twonly/src/views/components/alert_dialog.dart'; +import 'package:twonly/src/views/contact/contact.view.dart'; +import 'package:twonly/src/views/public_profile.view.dart'; + +Future handleIntentUrl(BuildContext context, Uri uri) async { + if (!uri.scheme.startsWith('http')) return; + if (uri.host != 'me.twonly.eu') return; + if (uri.hasEmptyPath) return; + + final publicKey = uri.hasFragment ? uri.fragment : null; + final userPaths = uri.path.split('/'); + if (userPaths.length != 2) return; + final username = userPaths[1]; + + if (!context.mounted) return; + + if (username == gUser.username) { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const PublicProfileView(); + }, + ), + ); + return; + } + + Log.info( + 'Opened via deep link!: username = $username public_key = ${uri.fragment}', + ); + final contacts = await twonlyDB.contactsDao.getContactsByUsername(username); + if (contacts.isEmpty) { + if (!context.mounted) return; + Uint8List? publicKeyBytes; + if (publicKey != null) { + publicKeyBytes = base64Url.decode(publicKey); + } + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return AddNewUserView( + username: username, + publicKey: publicKeyBytes, + ); + }, + ), + ); + } else if (publicKey != null) { + try { + final contact = contacts.first; + final storedPublicKey = await getPublicKeyFromContact(contact.userId); + final receivedPublicKey = base64Url.decode(publicKey); + if (storedPublicKey == null || + receivedPublicKey.isEmpty || + !context.mounted) { + return; + } + if (storedPublicKey.equals(receivedPublicKey)) { + if (!contact.verified) { + final markAsVerified = await showAlertDialog( + context, + context.lang.linkFromUsername(contact.username), + context.lang.linkFromUsernameLong, + customOk: context.lang.gotLinkFromFriend, + ); + if (markAsVerified) { + await twonlyDB.contactsDao.updateContact( + contact.userId, + const ContactsCompanion( + verified: Value(true), + ), + ); + } + } else { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return ContactView(contact.userId); + }, + ), + ); + } + } else { + await showAlertDialog( + context, + context.lang.couldNotVerifyUsername(contact.username), + context.lang.linkPubkeyDoesNotMatch, + customCancel: '', + ); + } + } catch (e) { + Log.warn(e); + } + } +} + +Future handleIntentMediaFile( + BuildContext context, + String filePath, + MediaType type, +) async { + final file = File(filePath); + if (!file.existsSync()) { + Log.error('The shared intent file does not exits.'); + return; + } + + final newMediaService = await initializeMediaUpload( + type, + gUser.defaultShowTime, + ); + if (newMediaService == null) { + Log.error('Could not create new media file for intent shared file'); + return; + } + + file.copySync(newMediaService.originalPath.path); + if (!context.mounted) return; + + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ShareImageEditorView( + mediaFileService: newMediaService, + sharedFromGallery: true, + ), + ), + ); +} + +Future handleIntentSharedFile( + BuildContext context, + List files, +) async { + for (final file in files) { + if (file.value == null) { + Log.error( + 'Got shared media, but value is empty: getMediaStream ${file.mimeType}', + ); + continue; + } + Log.info('got file via intent ${file.type} ${file.value}'); + + switch (file.type) { + case SharedMediaType.URL: + await handleIntentUrl(context, Uri.parse(file.value!)); + case SharedMediaType.IMAGE: + var type = MediaType.image; + if (file.value!.endsWith('.gif')) { + type = MediaType.gif; + } + await handleIntentMediaFile(context, file.value!, type); + case SharedMediaType.VIDEO: + await handleIntentMediaFile(context, file.value!, MediaType.video); + // ignore: no_default_cases + default: + } + break; // only handle one file... + } +} diff --git a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart b/lib/src/views/camera/camera_preview_components/main_camera_controller.dart index 6c30d5a..c6543a7 100644 --- a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart +++ b/lib/src/views/camera/camera_preview_components/main_camera_controller.dart @@ -227,7 +227,8 @@ class MainCameraController { content: Text( globalRootScaffoldMessengerKey.currentContext?.lang .verifiedPublicKey( - getContactDisplayName(contact)) ?? + getContactDisplayName(contact), + ) ?? '', ), duration: const Duration(seconds: 6), diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index bb065a4..b87ab11 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -1,29 +1,22 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; import 'package:app_links/app_links.dart'; -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; +import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/intent/links.intent.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart'; -import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart'; import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/share_image_editor_view.dart'; -import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/chats/chat_list.view.dart'; -import 'package:twonly/src/views/components/alert_dialog.dart'; -import 'package:twonly/src/views/contact/contact.view.dart'; import 'package:twonly/src/views/memories/memories.view.dart'; -import 'package:twonly/src/views/public_profile.view.dart'; void Function(int) globalUpdateOfHomeViewPageIndex = (a) {}; @@ -61,6 +54,7 @@ class HomeViewState extends State { final MainCameraController _mainCameraController = MainCameraController(); final PageController homeViewPageController = PageController(initialPage: 1); + late StreamSubscription> _intentStreamSub; late StreamSubscription _deepLinkSub; double buttonDiameter = 100; @@ -121,99 +115,21 @@ class HomeViewState extends State { // Subscribe to all events (initial link and further) _deepLinkSub = AppLinks().uriLinkStream.listen((uri) async { - if (!uri.scheme.startsWith('http')) return; - if (uri.host != 'me.twonly.eu') return; - if (uri.hasEmptyPath) return; + if (mounted) await handleIntentUrl(context, uri); + }); - final publicKey = uri.hasFragment ? uri.fragment : null; - final userPaths = uri.path.split('/'); - if (userPaths.length != 2) return; - final username = userPaths[1]; + _intentStreamSub = FlutterSharingIntent.instance.getMediaStream().listen( + (f) { + if (mounted) handleIntentSharedFile(context, f); + }, + // ignore: inference_failure_on_untyped_parameter + onError: (err) { + Log.error('getIntentDataStream error: $err'); + }, + ); - if (!mounted) return; - - if (username == gUser.username) { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const PublicProfileView(); - }, - ), - ); - return; - } - - Log.info( - 'Opened via deep link!: username = $username public_key = ${uri.fragment}', - ); - final contacts = - await twonlyDB.contactsDao.getContactsByUsername(username); - if (contacts.isEmpty) { - if (!mounted) return; - Uint8List? publicKeyBytes; - if (publicKey != null) { - publicKeyBytes = base64Url.decode(publicKey); - } - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return AddNewUserView( - username: username, - publicKey: publicKeyBytes, - ); - }, - ), - ); - } else if (publicKey != null) { - try { - final contact = contacts.first; - final storedPublicKey = await getPublicKeyFromContact(contact.userId); - final receivedPublicKey = base64Url.decode(publicKey); - if (storedPublicKey == null || - receivedPublicKey.isEmpty || - !mounted) { - return; - } - if (storedPublicKey.equals(receivedPublicKey)) { - if (!contact.verified) { - final markAsVerified = await showAlertDialog( - context, - context.lang.linkFromUsername(contact.username), - context.lang.linkFromUsernameLong, - customOk: context.lang.gotLinkFromFriend, - ); - if (markAsVerified) { - await twonlyDB.contactsDao.updateContact( - contact.userId, - const ContactsCompanion( - verified: Value(true), - ), - ); - } - } else { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ContactView(contact.userId); - }, - ), - ); - } - } else { - await showAlertDialog( - context, - context.lang.couldNotVerifyUsername(contact.username), - context.lang.linkPubkeyDoesNotMatch, - customCancel: '', - ); - } - } catch (e) { - Log.warn(e); - } - } + FlutterSharingIntent.instance.getInitialSharing().then((f) { + if (mounted) handleIntentSharedFile(context, f); }); } @@ -222,6 +138,7 @@ class HomeViewState extends State { unawaited(selectNotificationStream.close()); disableCameraTimer?.cancel(); _mainCameraController.closeCamera(); + _intentStreamSub.cancel(); _deepLinkSub.cancel(); super.dispose(); } diff --git a/pubspec.lock b/pubspec.lock index f8a222b..ea1d1dc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -742,29 +742,27 @@ packages: flutter_secure_storage: dependency: "direct main" description: - path: flutter_secure_storage - ref: a06ead81809c900e7fc421a30db0adf3b5919139 - resolved-ref: a06ead81809c900e7fc421a30db0adf3b5919139 - url: "https://github.com/juliansteenbakker/flutter_secure_storage.git" - source: git - version: "10.0.0-beta.4" + name: flutter_secure_storage + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 + url: "https://pub.dev" + source: hosted + version: "10.0.0" flutter_secure_storage_darwin: - dependency: "direct overridden" + dependency: transitive description: - path: flutter_secure_storage_darwin - ref: a06ead81809c900e7fc421a30db0adf3b5919139 - resolved-ref: a06ead81809c900e7fc421a30db0adf3b5919139 - url: "https://github.com/juliansteenbakker/flutter_secure_storage.git" - source: git - version: "0.1.0" + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" + url: "https://pub.dev" + source: hosted + version: "0.2.0" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "9b4b73127e857cd3117d43a70fa3dddadb6e0b253be62e6a6ab85caa0742182c" + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" flutter_secure_storage_platform_interface: dependency: transitive description: @@ -777,18 +775,25 @@ packages: dependency: transitive description: name: flutter_secure_storage_web - sha256: "4c3f233e739545c6cb09286eeec1cc4744138372b985113acc904f7263bef517" + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.1.0" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: ff32af20f70a8d0e59b2938fc92de35b54a74671041c814275afd80e27df9f21 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.0" + flutter_sharing_intent: + dependency: "direct main" + description: + path: "dependencies/flutter_sharing_intent" + relative: true + source: path + version: "2.0.4" flutter_svg: dependency: "direct main" description: @@ -1243,11 +1248,10 @@ packages: no_screenshot: dependency: "direct main" description: - name: no_screenshot - sha256: ec3d86d7ee89a09c3a3939c1003012536ba4b3fcb4f8cbd23d87ada595c99258 - url: "https://pub.dev" - source: hosted - version: "0.3.1" + path: "dependencies/no_screenshot" + relative: true + source: path + version: "0.3.2-beta.3" objective_c: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8b16d26..d441e98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: url_launcher: ^6.3.2 vector_graphics: ^1.1.19 video_player: ^2.10.1 + in_app_purchase: ^3.2.3 # Trusted publisher fluttercommunity.dev @@ -54,44 +55,50 @@ dependencies: scrollable_positioned_list: ^0.3.8 # google.dev + # Flutter Favorite + provider: ^6.1.2 + drift: ^2.25.1 + drift_flutter: ^0.2.4 + flutter_local_notifications: ^19.1.0 + sentry_flutter: ^9.8.0 + + + # With high download + app_links: ^7.0.0 # 1.6 mio + image: ^4.3.0 # 3.3 mio + archive: ^4.0.7 # 6.5 mio + file_picker: ^10.3.6 # 2 mio + get: ^4.7.2 # 740 k + flutter_secure_storage: ^10.0.0 # 1.85 mio + permission_handler: ^12.0.0+1 # 2 mio + + # Not yet checked - archive: ^4.0.7 audio_waveforms: ^2.0.0 avatar_maker: ^0.4.0 background_downloader: ^9.4.0 cached_network_image: ^3.4.1 cryptography_flutter_plus: ^2.3.4 cryptography_plus: ^2.7.0 - drift: ^2.25.1 - drift_flutter: ^0.2.4 ffmpeg_kit_flutter_new: ^4.1.0 - file_picker: ^10.3.6 flutter_android_volume_keydown: ^1.0.1 flutter_image_compress: ^2.4.0 - flutter_local_notifications: ^19.1.0 - flutter_secure_storage: - git: - url: https://github.com/juliansteenbakker/flutter_secure_storage.git - ref: a06ead81809c900e7fc421a30db0adf3b5919139 # from develop - path: flutter_secure_storage/ flutter_volume_controller: ^1.3.4 gal: ^2.3.1 - get: ^4.7.2 google_mlkit_barcode_scanning: ^0.14.1 - image: ^4.3.0 - no_screenshot: ^0.3.1 - permission_handler: ^12.0.0+1 - provider: ^6.1.2 - restart_app: ^1.3.2 - sentry_flutter: ^9.8.0 - app_links: ^7.0.0 - in_app_purchase: ^3.2.3 + + # flutter_secure_storage: + # git: + # url: https://github.com/juliansteenbakker/flutter_secure_storage.git + # ref: a06ead81809c900e7fc421a30db0adf3b5919139 # from develop + # path: flutter_secure_storage/ # Overwritten by self-controlled repository emoji_picker_flutter: ^4.3.0 # Packages which got overwritten using the twonly-app-dependencies repository + restart_app: ^1.3.2 photo_view: ^0.15.0 hashlib: ^2.0.0 libsignal_protocol_dart: ^0.7.4 @@ -100,6 +107,8 @@ dependencies: introduction_screen: ^4.0.0 qr_flutter: ^4.1.0 hand_signature: ^3.0.3 + flutter_sharing_intent: ^2.0.4 + no_screenshot: ^0.3.1 dependency_overrides: dots_indicator: @@ -110,6 +119,8 @@ dependency_overrides: path: ./dependencies/introduction_screen libsignal_protocol_dart: path: ./dependencies/libsignal_protocol_dart + flutter_sharing_intent: + path: ./dependencies/flutter_sharing_intent lottie: path: ./dependencies/lottie mutex: @@ -134,6 +145,8 @@ dependency_overrides: path: ./dependencies/x25519 qr_flutter: path: ./dependencies/qr_flutter + no_screenshot: + path: ./dependencies/no_screenshot camera_android_camerax: # path: ../flutter-packages/packages/camera/camera_android_camerax git: @@ -149,11 +162,11 @@ dependency_overrides: git: url: https://github.com/yenchieh/flutter_android_volume_keydown.git branch: fix/lStar-not-found-error - flutter_secure_storage_darwin: - git: - url: https://github.com/juliansteenbakker/flutter_secure_storage.git - ref: a06ead81809c900e7fc421a30db0adf3b5919139 # from develop - path: flutter_secure_storage_darwin/ + # flutter_secure_storage_darwin: + # git: + # url: https://github.com/juliansteenbakker/flutter_secure_storage.git + # ref: a06ead81809c900e7fc421a30db0adf3b5919139 # from develop + # path: flutter_secure_storage_darwin/ # hardcoding the mirror mode of the VideCapture to MIRROR_MODE_ON_FRONT_ONLY dev_dependencies: