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)