From 4a524d404ed09604b1ae2dacfc10de533cb2df15 Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:59:48 +0530 Subject: [PATCH] feat(share_extension): integrate share_handler package and implement share functionality with updated permissions and entitlements --- android/app/src/main/AndroidManifest.xml | 28 +- ios/Podfile | 28 +- ios/Podfile.lock | 18 +- ios/Runner.xcodeproj/project.pbxproj | 290 +++++++++++++++++- ios/Runner/Info.plist | 13 + ios/Runner/Runner.entitlements | 10 + ios/ShareExtension/Info.plist | 47 +++ .../ShareExtension.entitlements | 4 +- ios/ShareExtension/ShareViewController.swift | 3 + lib/core/services/share_receiver_service.dart | 172 +++++++++++ .../chat/providers/chat_providers.dart | 13 + .../chat/widgets/modern_chat_input.dart | 39 ++- lib/main.dart | 4 + pubspec.lock | 32 ++ pubspec.yaml | 1 + 15 files changed, 676 insertions(+), 26 deletions(-) create mode 100644 ios/Runner/Runner.entitlements create mode 100644 ios/ShareExtension/Info.plist create mode 100644 ios/ShareExtension/ShareViewController.swift create mode 100644 lib/core/services/share_receiver_service.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 62d7792..3f000ba 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,7 +3,9 @@ - + + + @@ -44,6 +46,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -61,4 +85,4 @@ - \ No newline at end of file + diff --git a/ios/Podfile b/ios/Podfile index 620e46e..e3b3517 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,9 +1,12 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' +# Work around Xcode cycle false-positives by letting CocoaPods not declare IO paths +install! 'cocoapods', :disable_input_output_paths => true + project 'Runner', { 'Debug' => :debug, 'Profile' => :release, @@ -28,9 +31,19 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do - use_frameworks! + # Prefer static frameworks to avoid Xcode embed-cycles with app extensions + use_frameworks! :linkage => :static + use_modular_headers! flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + + # share_handler addition start + target 'ShareExtension' do + inherit! :search_paths + # Only models are needed in extension to avoid app-only APIs + pod "share_handler_ios_models", :path => ".symlinks/plugins/share_handler_ios/ios/Models" + end + # share_handler addition end target 'RunnerTests' do inherit! :search_paths end @@ -40,4 +53,15 @@ post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end + + # Reduce chances of embed/cycle: don't embed Swift stdlib explicitly + installer.aggregate_targets.each do |aggregate_target| + aggregate_target.user_project.native_targets.each do |native_target| + if ['Runner', 'ShareExtension'].include?(native_target.name) + native_target.build_configurations.each do |config| + config.build_settings['ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES'] = 'NO' + end + end + end + end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index cddd6e8..57f9f17 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -50,6 +50,14 @@ PODS: - SDWebImage (5.21.1): - SDWebImage/Core (= 5.21.1) - SDWebImage/Core (5.21.1) + - share_handler_ios (0.0.14): + - Flutter + - share_handler_ios/share_handler_ios_models (= 0.0.14) + - share_handler_ios_models + - share_handler_ios/share_handler_ios_models (0.0.14): + - Flutter + - share_handler_ios_models + - share_handler_ios_models (0.0.9) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -75,6 +83,8 @@ DEPENDENCIES: - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - record_ios (from `.symlinks/plugins/record_ios/ios`) + - share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`) + - share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) @@ -106,6 +116,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" record_ios: :path: ".symlinks/plugins/record_ios/ios" + share_handler_ios: + :path: ".symlinks/plugins/share_handler_ios/ios" + share_handler_ios_models: + :path: ".symlinks/plugins/share_handler_ios/ios/Models" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: @@ -131,6 +145,8 @@ SPEC CHECKSUMS: path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b SDWebImage: f29024626962457f3470184232766516dee8dfea + share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb + share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871 share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 @@ -139,6 +155,6 @@ SPEC CHECKSUMS: url_launcher_ios: 694010445543906933d732453a59da0a173ae33d wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 -PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e +PODFILE CHECKSUM: df88575cf61e98a1a3edf2f8c887dad2c18c2079 COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index a6fcfa5..b51e57a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,11 +11,13 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 4A7698E95402502ED3A27D07 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E21C32C282E5A003D094A6E9 /* Pods_RunnerTests.framework */; }; + 5318C41BD6012F73B0A81F8D /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F5EAC162214F437B9D5305 /* Pods_ShareExtension.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; B26D75F4FE6FFABCFAC07E43 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BA04FBA3F37D0A688CFEFA7 /* Pods_Runner.framework */; }; + F1DBCF1D2E601A39004C2540 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F1DBCF132E601A39004C2540 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -26,6 +28,13 @@ remoteGlobalIDString = 97C146ED1CF9000F007C117D; remoteInfo = Runner; }; + F1DBCF1B2E601A39004C2540 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = F1DBCF122E601A39004C2540; + remoteInfo = ShareExtension; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -39,19 +48,34 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + F1DBCF1E2E601A39004C2540 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + F1DBCF1D2E601A39004C2540 /* ShareExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0BA04FBA3F37D0A688CFEFA7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; + 15F5EAC162214F437B9D5305 /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1EB0E6D3737F83B935118E8E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 20A55D0FD9E7CFD1B9BBBEED /* 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 = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 69671B6B1959FCF16D21E200 /* 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 = ""; }; + 71EEBC62F9F81EB3FD345FA2 /* 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + F1DBCF292E602000004C2540 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 837A0EFB28C7ACD7FF14EC67 /* 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 = ""; }; 838ED4921F9222D50ECF498B /* 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 = ""; }; @@ -64,9 +88,37 @@ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B60F6CBA44A303DE5389649E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; B6CA85D4EC683E94C2FB2A88 /* 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 = ""; }; + C9F5BA8D70297B37D581AE50 /* 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 = ""; }; E21C32C282E5A003D094A6E9 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F1DBCF132E601A39004C2540 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + F1DBCF242E601A7C004C2540 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + F1DBCF222E601A39004C2540 /* Exceptions for "ShareExtension" folder in "ShareExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = F1DBCF122E601A39004C2540 /* ShareExtension */; + }; +/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + F1DBCF142E601A39004C2540 /* ShareExtension */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + F1DBCF222E601A39004C2540 /* Exceptions for "ShareExtension" folder in "ShareExtension" target */, + ); + explicitFileTypes = { + }; + explicitFolders = ( + ); + path = ShareExtension; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 3F0B71E2AB4D863902C09B3E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -84,6 +136,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F1DBCF102E601A39004C2540 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5318C41BD6012F73B0A81F8D /* Pods_ShareExtension.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -100,6 +160,7 @@ children = ( 0BA04FBA3F37D0A688CFEFA7 /* Pods_Runner.framework */, E21C32C282E5A003D094A6E9 /* Pods_RunnerTests.framework */, + 15F5EAC162214F437B9D5305 /* Pods_ShareExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -113,6 +174,9 @@ B6CA85D4EC683E94C2FB2A88 /* Pods-RunnerTests.debug.xcconfig */, 837A0EFB28C7ACD7FF14EC67 /* Pods-RunnerTests.release.xcconfig */, 1EB0E6D3737F83B935118E8E /* Pods-RunnerTests.profile.xcconfig */, + 71EEBC62F9F81EB3FD345FA2 /* Pods-ShareExtension.debug.xcconfig */, + 69671B6B1959FCF16D21E200 /* Pods-ShareExtension.release.xcconfig */, + C9F5BA8D70297B37D581AE50 /* Pods-ShareExtension.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -133,6 +197,7 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, + F1DBCF142E601A39004C2540 /* ShareExtension */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 8C43905FA2E52A883F49D605 /* Pods */, @@ -145,6 +210,7 @@ children = ( 97C146EE1CF9000F007C117D /* Runner.app */, 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + F1DBCF132E601A39004C2540 /* ShareExtension.appex */, ); name = Products; sourceTree = ""; @@ -152,6 +218,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + F1DBCF242E601A7C004C2540 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -194,20 +261,43 @@ 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 4F34859EAFDCA9A40F09ECFF /* [CP] Embed Pods Frameworks */, + F1DBCF1E2E601A39004C2540 /* Embed Foundation Extensions */, + AA915538CD255B5C833E6AB7 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( + F1DBCF1C2E601A39004C2540 /* PBXTargetDependency */, ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; + F1DBCF122E601A39004C2540 /* ShareExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = F1DBCF232E601A39004C2540 /* Build configuration list for PBXNativeTarget "ShareExtension" */; + buildPhases = ( + 5A0A281EDF7C33D1547603FC /* [CP] Check Pods Manifest.lock */, + F1DBCF0F2E601A39004C2540 /* Sources */, + F1DBCF102E601A39004C2540 /* Frameworks */, + F1DBCF112E601A39004C2540 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + F1DBCF142E601A39004C2540 /* ShareExtension */, + ); + name = ShareExtension; + productName = ShareExtension; + productReference = F1DBCF132E601A39004C2540 /* ShareExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -215,6 +305,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1640; LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -226,6 +317,9 @@ CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; }; + F1DBCF122E601A39004C2540 = { + CreatedOnToolsVersion = 16.4; + }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -243,6 +337,7 @@ targets = ( 97C146ED1CF9000F007C117D /* Runner */, 331C8080294A63A400263BE5 /* RunnerTests */, + F1DBCF122E601A39004C2540 /* ShareExtension */, ); }; /* End PBXProject section */ @@ -260,12 +355,18 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; + F1DBCF112E601A39004C2540 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -276,7 +377,7 @@ files = ( ); inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); name = "Thin Binary"; outputPaths = ( @@ -285,21 +386,26 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 4F34859EAFDCA9A40F09ECFF /* [CP] Embed Pods Frameworks */ = { + 5A0A281EDF7C33D1547603FC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ShareExtension-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + 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 */ = { @@ -317,6 +423,21 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + AA915538CD255B5C833E6AB7 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; B2C308F843502D753F643BC6 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -381,6 +502,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F1DBCF0F2E601A39004C2540 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -389,6 +517,11 @@ target = 97C146ED1CF9000F007C117D /* Runner */; targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; }; + F1DBCF1C2E601A39004C2540 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F1DBCF122E601A39004C2540 /* ShareExtension */; + targetProxy = F1DBCF1B2E601A39004C2540 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -467,8 +600,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -655,8 +790,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -683,8 +820,10 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -706,6 +845,129 @@ }; name = Release; }; + F1DBCF1F2E601A39004C2540 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 71EEBC62F9F81EB3FD345FA2 /* Pods-ShareExtension.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X2662V5DT2; + 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 = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = app.cogwheel.conduit.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F1DBCF202E601A39004C2540 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 69671B6B1959FCF16D21E200 /* Pods-ShareExtension.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X2662V5DT2; + 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 = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = app.cogwheel.conduit.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + F1DBCF212E601A39004C2540 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C9F5BA8D70297B37D581AE50 /* Pods-ShareExtension.profile.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = X2662V5DT2; + 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 = 14.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = app.cogwheel.conduit.ShareExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Profile; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -739,6 +1001,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F1DBCF232E601A39004C2540 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F1DBCF1F2E601A39004C2540 /* Debug */, + F1DBCF202E601A39004C2540 /* Release */, + F1DBCF212E601A39004C2540 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 587522b..3e398a9 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -73,5 +73,18 @@ UIStatusBarHidden + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + app.cogwheel.conduit.share + CFBundleURLSchemes + + ShareMedia-app.cogwheel.conduit + + + diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..69c95a5 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.app.cogwheel.conduit + + + diff --git a/ios/ShareExtension/Info.plist b/ios/ShareExtension/Info.plist new file mode 100644 index 0000000..c9cdb4a --- /dev/null +++ b/ios/ShareExtension/Info.plist @@ -0,0 +1,47 @@ + + + + + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + NSExtension + + NSExtensionAttributes + + + IntentsSupported + + INSendMessageIntent + + + NSExtensionActivationRule + + + + + NSExtensionActivationSupportsImageWithMaxCount + 10 + NSExtensionActivationSupportsMovieWithMaxCount + 10 + NSExtensionActivationSupportsWebURLWithMaxCount + 10 + NSExtensionActivationSupportsText + + + PHSupportedMediaTypes + + Video + Image + + + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).ShareViewController + NSExtensionPointIdentifier + com.apple.share-services + + + \ No newline at end of file diff --git a/ios/ShareExtension/ShareExtension.entitlements b/ios/ShareExtension/ShareExtension.entitlements index 2eb7e33..69c95a5 100644 --- a/ios/ShareExtension/ShareExtension.entitlements +++ b/ios/ShareExtension/ShareExtension.entitlements @@ -3,6 +3,8 @@ com.apple.security.application-groups - + + group.app.cogwheel.conduit + diff --git a/ios/ShareExtension/ShareViewController.swift b/ios/ShareExtension/ShareViewController.swift new file mode 100644 index 0000000..b1b38ef --- /dev/null +++ b/ios/ShareExtension/ShareViewController.swift @@ -0,0 +1,3 @@ +import share_handler_ios_models + +class ShareViewController: ShareHandlerIosViewController {} \ No newline at end of file diff --git a/lib/core/services/share_receiver_service.dart b/lib/core/services/share_receiver_service.dart new file mode 100644 index 0000000..314b0f1 --- /dev/null +++ b/lib/core/services/share_receiver_service.dart @@ -0,0 +1,172 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:share_handler/share_handler.dart' as sh; + +import '../../features/auth/providers/unified_auth_providers.dart'; +import '../../features/chat/providers/chat_providers.dart'; +import '../../features/chat/services/file_attachment_service.dart'; +import '../../core/providers/app_providers.dart'; + +/// Lightweight payload for a share event +class SharedPayload { + final String? text; + final List filePaths; + const SharedPayload({this.text, this.filePaths = const []}); + + bool get hasAnything => + (text != null && text!.trim().isNotEmpty) || filePaths.isNotEmpty; +} + +/// Holds a pending shared payload until the app is ready (e.g., authed + model loaded) +final pendingSharedPayloadProvider = StateProvider((_) => null); + +/// Initializes listening to OS share intents and handles them +final shareReceiverInitializerProvider = Provider((ref) { + // Do nothing on web/desktop + if (kIsWeb) return; + + final sub = StreamController.broadcast(); + + // Listen for app readiness: authenticated and model available + void maybeProcessPending() { + final navState = ref.read(authNavigationStateProvider); + final model = ref.read(selectedModelProvider); + final pending = ref.read(pendingSharedPayloadProvider); + if (pending != null && + pending.hasAnything && + navState == AuthNavigationState.authenticated && + model != null) { + _processPayload(ref, pending); + ref.read(pendingSharedPayloadProvider.notifier).state = null; + } + } + + // React when auth/model changes to process a queued share + ref.listen( + authNavigationStateProvider, + (_, __) => maybeProcessPending(), + ); + ref.listen(selectedModelProvider, (_, __) => maybeProcessPending()); + + // Hook into share_handler + final handler = sh.ShareHandler.instance; + + // Handle initial share when app is cold-started via Share + Future.microtask(() async { + try { + final dynamic media = await handler.getInitialSharedMedia(); + final payload = _toPayload(media); + if (payload.hasAnything) { + ref.read(pendingSharedPayloadProvider.notifier).state = payload; + maybeProcessPending(); + } + } catch (e) { + debugPrint('ShareReceiver: failed to get initial shared media: $e'); + } + }); + + // Handle subsequent shares while app is alive + final streamSub = handler.sharedMediaStream.listen((dynamic media) { + try { + final payload = _toPayload(media); + if (payload.hasAnything) { + ref.read(pendingSharedPayloadProvider.notifier).state = payload; + maybeProcessPending(); + } + } catch (e) { + debugPrint('ShareReceiver: failed to parse shared media: $e'); + } + }); + + // Ensure cleanup + ref.onDispose(() async { + await streamSub.cancel(); + await sub.close(); + }); +}); + +SharedPayload _toPayload(dynamic media) { + if (media == null) return const SharedPayload(); + + String? text; + final filePaths = []; + + try { + // Common field in share_handler: `content` (String?) + text = (media as dynamic).content as String?; + } catch (_) { + try { + // Some plugins use `text` + text = (media as dynamic).text as String?; + } catch (_) {} + } + + try { + final list = (media as dynamic).attachments as List?; + if (list != null) { + for (final att in list) { + try { + final p = (att as dynamic).path as String?; + if (p != null && p.isNotEmpty) filePaths.add(p); + } catch (_) { + // Ignore a malformed entry + } + } + } + } catch (_) { + // Older plugins may call it files + try { + final list = (media as dynamic).files as List?; + if (list != null) { + for (final att in list) { + try { + final p = (att as dynamic).path as String?; + if (p != null && p.isNotEmpty) filePaths.add(p); + } catch (_) {} + } + } + } catch (_) {} + } + + return SharedPayload(text: text, filePaths: filePaths); +} + +Future _processPayload(Ref ref, SharedPayload payload) async { + try { + // Start a fresh chat context but do NOT auto-send + startNewChat(ref); + + // Prefer attaching files to the composer so user can add text before sending + if (payload.filePaths.isNotEmpty) { + final svc = ref.read(fileAttachmentServiceProvider); + if (svc != null) { + // Add files to attachment list and kick off uploads, mirroring UI flow + final files = payload.filePaths.map((p) => File(p)).toList(); + if (files.isNotEmpty) { + ref.read(attachedFilesProvider.notifier).addFiles(files); + + for (final file in files) { + final uploadStream = svc.uploadFile(file); + uploadStream.listen((state) { + ref + .read(attachedFilesProvider.notifier) + .updateFileState(file.path, state); + }, onError: (_) {}); + } + } + } + } + + // Prefill text in the composer (do not auto-send) + final text = payload.text?.trim(); + if (text != null && text.isNotEmpty) { + ref.read(prefilledInputTextProvider.notifier).state = text; + } + // This allows the user to add a caption before sending + } catch (e) { + debugPrint('ShareReceiver: failed to process payload: $e'); + } +} diff --git a/lib/features/chat/providers/chat_providers.dart b/lib/features/chat/providers/chat_providers.dart index fff55cd..3fc65b2 100644 --- a/lib/features/chat/providers/chat_providers.dart +++ b/lib/features/chat/providers/chat_providers.dart @@ -21,6 +21,9 @@ final chatMessagesProvider = // Loading state for conversation (used to show chat skeletons during fetch) final isLoadingConversationProvider = StateProvider((ref) => false); +// Prefilled input text (e.g., when sharing text from other apps) +final prefilledInputTextProvider = StateProvider((ref) => null); + class ChatMessagesNotifier extends StateNotifier> { final Ref _ref; StreamSubscription? _messageStream; @@ -453,6 +456,16 @@ Future sendMessage( await _sendMessageInternal(ref, message, attachments, toolIds); } +// Service-friendly wrapper (accepts generic Ref) +Future sendMessageFromService( + Ref ref, + String message, + List? attachments, [ + List? toolIds, +]) async { + await _sendMessageInternal(ref, message, attachments, toolIds); +} + // Internal send message implementation Future _sendMessageInternal( dynamic ref, diff --git a/lib/features/chat/widgets/modern_chat_input.dart b/lib/features/chat/widgets/modern_chat_input.dart index 755c488..c557791 100644 --- a/lib/features/chat/widgets/modern_chat_input.dart +++ b/lib/features/chat/widgets/modern_chat_input.dart @@ -185,6 +185,20 @@ class _ModernChatInputState extends ConsumerState vsync: this, ); + // Listen for prefilled text updates (e.g., from share intent) + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + final text = ref.read(prefilledInputTextProvider); + if (text != null && text.isNotEmpty) { + _controller.text = text; + _controller.selection = TextSelection.collapsed(offset: text.length); + // Clear after applying so it doesn't re-apply on rebuilds + ref.read(prefilledInputTextProvider.notifier).state = null; + _ensureFocusedIfEnabled(); + if (!_isExpanded) _setExpanded(true); + } + }); + // Listen for text changes and update only when emptiness flips _controller.addListener(() { final has = _controller.text.trim().isNotEmpty; @@ -878,8 +892,8 @@ class _ModernChatInputState extends ConsumerState color: isActive ? context.conduitTheme.buttonPrimary : showBackground - ? context.conduitTheme.cardBorder - : Colors.transparent, + ? context.conduitTheme.cardBorder + : Colors.transparent, width: BorderWidth.regular, ), ), @@ -898,8 +912,8 @@ class _ModernChatInputState extends ConsumerState color: isActive ? context.conduitTheme.buttonPrimary : showBackground - ? context.conduitTheme.cardBackground - : Colors.transparent, + ? context.conduitTheme.cardBackground + : Colors.transparent, borderRadius: BorderRadius.circular(AppBorderRadius.xl), boxShadow: (isActive || showBackground) ? ConduitShadows.button @@ -910,11 +924,13 @@ class _ModernChatInputState extends ConsumerState size: iconSize ?? IconSize.medium, color: widget.enabled ? (isActive - ? context.conduitTheme.buttonPrimaryText - : context.conduitTheme.textPrimary - .withValues(alpha: Alpha.strong)) - : context.conduitTheme.textPrimary - .withValues(alpha: Alpha.disabled), + ? context.conduitTheme.buttonPrimaryText + : context.conduitTheme.textPrimary.withValues( + alpha: Alpha.strong, + )) + : context.conduitTheme.textPrimary.withValues( + alpha: Alpha.disabled, + ), ), ), ), @@ -954,8 +970,9 @@ class _ModernChatInputState extends ConsumerState decoration: BoxDecoration( // Subtle primary tint when active for clearer affordance color: isActive - ? context.conduitTheme.buttonPrimary - .withValues(alpha: Alpha.buttonHover + 0.04) + ? context.conduitTheme.buttonPrimary.withValues( + alpha: Alpha.buttonHover + 0.04, + ) : context.conduitTheme.cardBackground, borderRadius: BorderRadius.circular(AppBorderRadius.xl), // No elevation to match modal chips diff --git a/lib/main.dart b/lib/main.dart index 9010e74..ea179b8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,6 +18,7 @@ import 'features/onboarding/views/onboarding_sheet.dart'; import 'package:conduit/l10n/app_localizations.dart'; import 'features/chat/views/chat_page.dart'; import 'features/navigation/views/splash_launcher_page.dart'; +import 'core/services/share_receiver_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -66,6 +67,9 @@ class _ConduitAppState extends ConsumerState { // Ensure API service auth integration is active ref.read(authApiIntegrationProvider); + + // Initialize OS share receiver so users can share text/files to Conduit + ref.read(shareReceiverInitializerProvider); } @override diff --git a/pubspec.lock b/pubspec.lock index 19ecc11..a8cfd6c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -973,6 +973,38 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + share_handler: + dependency: "direct main" + description: + name: share_handler + sha256: "0a6d007f0e44fbee27164adcd159ecbc88238864313f4e5c58161cae2180328d" + url: "https://pub.dev" + source: hosted + version: "0.0.25" + share_handler_android: + dependency: transitive + description: + name: share_handler_android + sha256: caf555b933dc72783aa37fef75688c7b86bd6f7bc17d80fbf585bc42f123cc8d + url: "https://pub.dev" + source: hosted + version: "0.0.11" + share_handler_ios: + dependency: transitive + description: + name: share_handler_ios + sha256: cdc21f88f336a944157a8e9ceb191525cee3b082d6eb6c2082488e4f09dc3ece + url: "https://pub.dev" + source: hosted + version: "0.0.15" + share_handler_platform_interface: + dependency: transitive + description: + name: share_handler_platform_interface + sha256: "7a4df95a87b326b2f07458d937f2281874567c364b7b7ebe4e7d50efaae5f106" + url: "https://pub.dev" + source: hosted + version: "0.0.6" share_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index fea73e7..2532d22 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dependencies: freezed_annotation: ^3.0.0 wakelock_plus: ^1.2.10 share_plus: ^11.1.0 + share_handler: ^0.0.19 # Clipboard functionality is available through flutter/services (part of Flutter SDK)