fixes #340 and continue with #333
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run

This commit is contained in:
otsmr 2025-12-27 15:09:36 +01:00
parent 0984eaf347
commit 230809290a
18 changed files with 1219 additions and 167 deletions

View file

@ -33,6 +33,16 @@
<data android:scheme="http" android:host="me.twonly.eu" />
<data android:scheme="https" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
@ -46,19 +56,11 @@
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
tools:node="remove">
</service>
<!-- <service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="dataSync|remoteMessaging"
android:exported="false" /> -->
<meta-data
android:name="eu.twonly.service.TWONLY_LOGO"
android:resource="@drawable/ic_launcher_foreground" />
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA"/>

@ -1 +1 @@
Subproject commit 83475a912851acb6a718ea32a6f0f754d64a50d8
Subproject commit 7930d9727019344238297d810661bc3e8f724c37

View file

@ -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|

View file

@ -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

View file

@ -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 = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
16FBC6F5B58E1C6646F5D447 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
@ -87,11 +99,15 @@
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
@ -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 = "<group>";
};
D25D4D712EFF41DB0029F805 /* ShareExtension */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
D25D4D7E2EFF41DB0029F805 /* Exceptions for "ShareExtension" folder in "ShareExtension" target */,
);
path = ShareExtension;
sourceTree = "<group>";
};
/* 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 = "<group>";
@ -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 = "<group>";
@ -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 = "<group>";
@ -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 */;

View file

@ -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)
}

View file

@ -87,9 +87,21 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<!--Disable Firebase Telemetry-->
<key>firebase_performance_collection_deactivated</key>
<true/>
<!--...-->
<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>SharingMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:me.twonly.eu</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.eu.twonly.shareIntent</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)eu.twonly.shared</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Share View Controller-->
<scene sceneID="ceB-am-kn3">
<objects>
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View file

@ -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[..<idx])
} else {
hostAppBundleIdentifier = shareExtId
}
let custom = Bundle.main.object(forInfoDictionaryKey: kAppGroupIdKey) as? String
appGroupId = custom ?? "group.\(hostAppBundleIdentifier)"
log("loaded host=\(hostAppBundleIdentifier) group=\(appGroupId)")
}
// MARK: - Attachment processing (clean RSI style, preserve FSI features)
private func processAttachments() {
guard let content = extensionContext?.inputItems.first as? NSExtensionItem else {
completeAndExit()
return
}
guard let attachments = content.attachments, !attachments.isEmpty else {
completeAndExit()
return
}
// Use DispatchGroup to wait for async loads
let group = DispatchGroup()
for (index, provider) in attachments.enumerated() {
group.enter()
// Try all SharedMediaType options similar to RSI but preserve explicit FSI order
if provider.isImage {
provider.loadItem(forTypeIdentifier: UType.image, options: nil) { [weak self] data, error in
defer { group.leave() }
guard let self = self, error == nil else { self?.dismissWithError(); return }
self.handleImageItem(data: data, index: index, total: attachments.count)
}
continue
}
if provider.isMovie {
provider.loadItem(forTypeIdentifier: UType.movie, options: nil) { [weak self] data, error in
defer { group.leave() }
guard let self = self, error == nil else { self?.dismissWithError(); return }
self.handleVideoItem(data: data, index: index, total: attachments.count)
}
continue
}
if provider.isFile {
provider.loadItem(forTypeIdentifier: UType.fileURL, options: nil) { [weak self] data, error in
defer { group.leave() }
guard let self = self, error == nil else { self?.dismissWithError(); return }
self.handleFileItem(data: data, index: index, total: attachments.count)
}
continue
}
if provider.isURL {
provider.loadItem(forTypeIdentifier: UType.url, options: nil) { [weak self] data, error in
defer { group.leave() }
guard let self = self, error == nil else { self?.dismissWithError(); return }
self.handleUrlItem(data: data, index: index, total: attachments.count)
}
continue
}
if provider.isText {
let id = provider.hasItemConformingToTypeIdentifier(UType.plainText)
? UType.plainText
: UType.text
provider.loadItem(forTypeIdentifier: id, options: nil) { [weak self] data, error in
defer { group.leave() }
guard let self = self, error == nil else { self?.dismissWithError(); return }
self.handleTextItem(data: data, index: index, total: attachments.count)
}
continue
}
if provider.isData {
provider.loadItem(forTypeIdentifier: UType.data, options: nil) { [weak self] data, error in
defer { group.leave() }
guard let self = self, error == nil else { self?.dismissWithError(); return }
self.handleFileItem(data: data, index: index, total: attachments.count)
}
continue
}
if provider.isItem {
provider.loadItem(forTypeIdentifier: UType.item, options: nil) { [weak self] data, error in
defer { group.leave() }
guard let self = self, error == nil else { self?.dismissWithError(); return }
self.handleFileItem(data: data, index: index, total: attachments.count)
}
continue
}
log("Unknown provider type: \(provider.registeredTypeIdentifiers)")
// Unknown type: just leave
group.leave()
}
group.notify(queue: .main) { [weak self] in
guard let self = self else { return }
// if we have media -> 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 1118
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
}
}
}

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AppGroupId</key>
<string>$(CUSTOM_GROUP_ID)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
<key>NSExtensionAttributes</key>
<dict>
<key>PHSupportedMediaTypes</key>
<array>
<string>Video</string>
<string>Image</string>
</array>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.eu.twonly.shareIntent</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,3 @@
class ShareViewController: FSIShareViewController {
}

View file

@ -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<void> 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<void> 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<void> handleIntentSharedFile(
BuildContext context,
List<SharedFile> 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...
}
}

View file

@ -227,7 +227,8 @@ class MainCameraController {
content: Text(
globalRootScaffoldMessengerKey.currentContext?.lang
.verifiedPublicKey(
getContactDisplayName(contact)) ??
getContactDisplayName(contact),
) ??
'',
),
duration: const Duration(seconds: 6),

View file

@ -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<HomeView> {
final MainCameraController _mainCameraController = MainCameraController();
final PageController homeViewPageController = PageController(initialPage: 1);
late StreamSubscription<List<SharedFile>> _intentStreamSub;
late StreamSubscription<Uri> _deepLinkSub;
double buttonDiameter = 100;
@ -121,99 +115,21 @@ class HomeViewState extends State<HomeView> {
// 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];
if (!mounted) return;
if (username == gUser.username) {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return const PublicProfileView();
_intentStreamSub = FlutterSharingIntent.instance.getMediaStream().listen(
(f) {
if (mounted) handleIntentSharedFile(context, f);
},
// ignore: inference_failure_on_untyped_parameter
onError: (err) {
Log.error('getIntentDataStream error: $err');
},
),
);
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<HomeView> {
unawaited(selectNotificationStream.close());
disableCameraTimer?.cancel();
_mainCameraController.closeCamera();
_intentStreamSub.cancel();
_deepLinkSub.cancel();
super.dispose();
}

View file

@ -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:

View file

@ -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: