From 86981998e8f2eecfa963f49b16819b6d1d5422b1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:08:07 +0000 Subject: [PATCH 1/2] feat: Add support for EXTRA_SUBJECT This change adds support for the `EXTRA_SUBJECT` field from Android intents. The `SharedMedia` class has been updated to include a `subject` field, and the Android native code has been modified to extract the `EXTRA_SUBJECT` from the intent and populate this new field. --- .../com/shoutsocial/share_handler/Messages.java | 15 +++++++++++++++ .../share_handler/ShareHandlerPlugin.kt | 6 ++++++ .../lib/src/data/messages.dart | 6 ++++++ 3 files changed, 27 insertions(+) diff --git a/share_handler_android/android/src/main/java/com/shoutsocial/share_handler/Messages.java b/share_handler_android/android/src/main/java/com/shoutsocial/share_handler/Messages.java index 818fe67..56fa003 100644 --- a/share_handler_android/android/src/main/java/com/shoutsocial/share_handler/Messages.java +++ b/share_handler_android/android/src/main/java/com/shoutsocial/share_handler/Messages.java @@ -110,6 +110,12 @@ public void setContent(@Nullable String setterArg) { this.content = setterArg; } + private @Nullable String subject; + public @Nullable String getSubject() { return subject; } + public void setSubject(@Nullable String setterArg) { + this.subject = setterArg; + } + private @Nullable String speakableGroupName; public @Nullable String getSpeakableGroupName() { return speakableGroupName; } public void setSpeakableGroupName(@Nullable String setterArg) { @@ -150,6 +156,11 @@ public static final class Builder { this.content = setterArg; return this; } + private @Nullable String subject; + public @NonNull Builder setSubject(@Nullable String setterArg) { + this.subject = setterArg; + return this; + } private @Nullable String speakableGroupName; public @NonNull Builder setSpeakableGroupName(@Nullable String setterArg) { this.speakableGroupName = setterArg; @@ -175,6 +186,7 @@ public static final class Builder { pigeonReturn.setAttachments(attachments); pigeonReturn.setConversationIdentifier(conversationIdentifier); pigeonReturn.setContent(content); + pigeonReturn.setSubject(subject); pigeonReturn.setSpeakableGroupName(speakableGroupName); pigeonReturn.setServiceName(serviceName); pigeonReturn.setSenderIdentifier(senderIdentifier); @@ -191,6 +203,7 @@ public static final class Builder { toMapResult.put("attachments", list.isEmpty() ? null : list); toMapResult.put("conversationIdentifier", conversationIdentifier); toMapResult.put("content", content); + toMapResult.put("subject", subject); toMapResult.put("speakableGroupName", speakableGroupName); toMapResult.put("serviceName", serviceName); toMapResult.put("senderIdentifier", senderIdentifier); @@ -209,6 +222,8 @@ public static final class Builder { pigeonResult.setConversationIdentifier((String)conversationIdentifier); Object content = map.get("content"); pigeonResult.setContent((String)content); + Object subject = map.get("subject"); + pigeonResult.setSubject((String)subject); Object speakableGroupName = map.get("speakableGroupName"); pigeonResult.setSpeakableGroupName((String)speakableGroupName); Object serviceName = map.get("serviceName"); diff --git a/share_handler_android/android/src/main/kotlin/com/shoutsocial/share_handler/ShareHandlerPlugin.kt b/share_handler_android/android/src/main/kotlin/com/shoutsocial/share_handler/ShareHandlerPlugin.kt index aac3823..8240379 100644 --- a/share_handler_android/android/src/main/kotlin/com/shoutsocial/share_handler/ShareHandlerPlugin.kt +++ b/share_handler_android/android/src/main/kotlin/com/shoutsocial/share_handler/ShareHandlerPlugin.kt @@ -178,6 +178,11 @@ class ShareHandlerPlugin : FlutterPlugin, Messages.ShareHandlerApi, EventChannel else -> null } + val subject: String? = when (intent.action) { + Intent.ACTION_SEND, Intent.ACTION_SEND_MULTIPLE -> intent.getStringExtra(Intent.EXTRA_SUBJECT) + else -> null + } + val conversationIdentifier = intent.getStringExtra("android.intent.extra.shortcut.ID") ?: intent.getStringExtra("conversationIdentifier") @@ -185,6 +190,7 @@ class ShareHandlerPlugin : FlutterPlugin, Messages.ShareHandlerApi, EventChannel val mediaBuilder = Messages.SharedMedia.Builder() attachments?.let { mediaBuilder.setAttachments(it) } text?.let { mediaBuilder.setContent(it) } + subject?.let { mediaBuilder.setSubject(it) } conversationIdentifier?.let { mediaBuilder.setConversationIdentifier(it) } val media = mediaBuilder.build() diff --git a/share_handler_platform_interface/lib/src/data/messages.dart b/share_handler_platform_interface/lib/src/data/messages.dart index ee7b0a8..e415207 100644 --- a/share_handler_platform_interface/lib/src/data/messages.dart +++ b/share_handler_platform_interface/lib/src/data/messages.dart @@ -50,6 +50,7 @@ class SharedMedia { this.recipientIdentifiers, this.conversationIdentifier, this.content, + this.subject, this.speakableGroupName, this.serviceName, this.senderIdentifier, @@ -68,6 +69,9 @@ class SharedMedia { /// Text content that was shared if any. Could be a url as well. String? content; + /// The subject of the shared content. + String? subject; + /// The name of the recipient the content was shared to if specified. String? speakableGroupName; @@ -86,6 +90,7 @@ class SharedMedia { pigeonMap['recipientIdentifiers'] = recipientIdentifiers; pigeonMap['conversationIdentifier'] = conversationIdentifier; pigeonMap['content'] = content; + pigeonMap['subject'] = subject; pigeonMap['speakableGroupName'] = speakableGroupName; pigeonMap['serviceName'] = serviceName; pigeonMap['senderIdentifier'] = senderIdentifier; @@ -103,6 +108,7 @@ class SharedMedia { recipientIdentifiers: (pigeonMap['recipientIdentifiers'] as List?)?.cast(), conversationIdentifier: pigeonMap['conversationIdentifier'] as String?, content: pigeonMap['content'] as String?, + subject: pigeonMap['subject'] as String?, speakableGroupName: pigeonMap['speakableGroupName'] as String?, serviceName: pigeonMap['serviceName'] as String?, senderIdentifier: pigeonMap['senderIdentifier'] as String?, From c389a7035270b8c72b843b62fdaf6fffa847c070 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:28:45 +0000 Subject: [PATCH 2/2] feat: Add support for subject on iOS This change adds support for extracting the subject from shared data on iOS. The `SharedMedia` class has been updated to include a `subject` field, and the iOS native code has been modified to extract the subject from the `NSExtensionItem` and populate this new field. --- .../Models/Classes/ShareHandlerIosViewController.swift | 7 ++++--- .../ios/Models/Classes/SharedModels.swift | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/share_handler_ios/ios/Models/Classes/ShareHandlerIosViewController.swift b/share_handler_ios/ios/Models/Classes/ShareHandlerIosViewController.swift index ffb0432..b2cc3ba 100644 --- a/share_handler_ios/ios/Models/Classes/ShareHandlerIosViewController.swift +++ b/share_handler_ios/ios/Models/Classes/ShareHandlerIosViewController.swift @@ -87,7 +87,7 @@ open class ShareHandlerIosViewController: UIViewController { } } - redirectToHostApp() + redirectToHostApp(content: content) } } @@ -249,7 +249,7 @@ open class ShareHandlerIosViewController: UIViewController { extensionContext!.completeRequest(returningItems: [], completionHandler: nil) } - public func redirectToHostApp() { + public func redirectToHostApp(content: NSExtensionItem) { // ids may not loaded yet so we need loadIds here too loadIds(); let url = URL(string: "ShareMedia-\(ShareHandlerIosViewController.hostAppBundleIdentifier)://\(ShareHandlerIosViewController.hostAppBundleIdentifier)?key=\(sharedKey)") @@ -262,8 +262,9 @@ open class ShareHandlerIosViewController: UIViewController { let sender = intent?.sender let serviceName = intent?.serviceName let speakableGroupName = intent?.speakableGroupName + let subject = content.userInfo?[NSItemProvider.ItemKey.title] as? String - let sharedMedia = SharedMedia.init(attachments: sharedAttachments, conversationIdentifier: conversationIdentifier, content: sharedText.joined(separator: "\n"), speakableGroupName: speakableGroupName?.spokenPhrase, serviceName: serviceName, senderIdentifier: sender?.contactIdentifier ?? sender?.customIdentifier, imageFilePath: nil) + let sharedMedia = SharedMedia.init(attachments: sharedAttachments, conversationIdentifier: conversationIdentifier, content: sharedText.joined(separator: "\n"), subject: subject, speakableGroupName: speakableGroupName?.spokenPhrase, serviceName: serviceName, senderIdentifier: sender?.contactIdentifier ?? sender?.customIdentifier, imageFilePath: nil) let json = sharedMedia.toJson() diff --git a/share_handler_ios/ios/Models/Classes/SharedModels.swift b/share_handler_ios/ios/Models/Classes/SharedModels.swift index e885d96..4a1aae0 100644 --- a/share_handler_ios/ios/Models/Classes/SharedModels.swift +++ b/share_handler_ios/ios/Models/Classes/SharedModels.swift @@ -31,15 +31,17 @@ open class SharedMedia: Codable { public var attachments: [SharedAttachment]? public var conversationIdentifier: String? public var content: String? + public var subject: String? public var speakableGroupName: String? public var serviceName: String? public var senderIdentifier: String? public var imageFilePath: String? - public init(attachments: [SharedAttachment]?, conversationIdentifier: String?, content: String?, speakableGroupName: String?, serviceName: String?, senderIdentifier: String?, imageFilePath: String?) { + public init(attachments: [SharedAttachment]?, conversationIdentifier: String?, content: String?, subject: String?, speakableGroupName: String?, serviceName: String?, senderIdentifier: String?, imageFilePath: String?) { self.attachments = attachments self.conversationIdentifier = conversationIdentifier self.content = content + self.subject = subject self.speakableGroupName = speakableGroupName self.serviceName = serviceName self.senderIdentifier = senderIdentifier @@ -48,7 +50,7 @@ open class SharedMedia: Codable { public class func fromMap(map: Dictionary?) -> SharedMedia? { if let _map = map { - return SharedMedia(attachments: (_map["attachments"] as? Array>)?.compactMap{ SharedAttachment.fromMap(map: $0)}, conversationIdentifier: _map["conversationIdentifier"] as? String, content: _map["content"] as? String, speakableGroupName: _map["speakableGroupName"] as? String, serviceName: _map["serviceName"]as? String, senderIdentifier: _map["senderIdentifier"] as? String, imageFilePath: _map["imageFilePath"] as? String) + return SharedMedia(attachments: (_map["attachments"] as? Array>)?.compactMap{ SharedAttachment.fromMap(map: $0)}, conversationIdentifier: _map["conversationIdentifier"] as? String, content: _map["content"] as? String, subject: _map["subject"] as? String, speakableGroupName: _map["speakableGroupName"] as? String, serviceName: _map["serviceName"]as? String, senderIdentifier: _map["senderIdentifier"] as? String, imageFilePath: _map["imageFilePath"] as? String) } else { return nil } @@ -58,14 +60,14 @@ open class SharedMedia: Codable { if let _json = data { let map = try? JSONSerialization.jsonObject(with: _json) as? Dictionary if let _map = map { - return SharedMedia(attachments: (_map["attachments"] as? Array>)?.map{ SharedAttachment(path: $0["path"] as! String, type: SharedAttachmentType(rawValue: $0["type"] as! Int? ?? SharedAttachmentType.file.rawValue) ?? SharedAttachmentType.file )}, conversationIdentifier: _map["conversationIdentifier"] as? String, content: _map["content"] as? String, speakableGroupName: _map["speakableGroupName"] as? String, serviceName: _map["serviceName"]as? String, senderIdentifier: _map["senderIdentifier"] as? String, imageFilePath: _map["imageFilePath"] as? String) + return SharedMedia(attachments: (_map["attachments"] as? Array>)?.map{ SharedAttachment(path: $0["path"] as! String, type: SharedAttachmentType(rawValue: $0["type"] as! Int? ?? SharedAttachmentType.file.rawValue) ?? SharedAttachmentType.file )}, conversationIdentifier: _map["conversationIdentifier"] as? String, content: _map["content"] as? String, subject: _map["subject"] as? String, speakableGroupName: _map["speakableGroupName"] as? String, serviceName: _map["serviceName"]as? String, senderIdentifier: _map["senderIdentifier"] as? String, imageFilePath: _map["imageFilePath"] as? String) } } return nil } public func toDictionary() -> Dictionary { - return ["attachments": attachments?.map {$0.toDictionary()}, "conversationIdentifier": conversationIdentifier, "content": content, "speakableGroupName": speakableGroupName, "serviceName": serviceName, "senderIdentifier": senderIdentifier, "imageFilePath": imageFilePath] + return ["attachments": attachments?.map {$0.toDictionary()}, "conversationIdentifier": conversationIdentifier, "content": content, "subject": subject, "speakableGroupName": speakableGroupName, "serviceName": serviceName, "senderIdentifier": senderIdentifier, "imageFilePath": imageFilePath] } public func toJson() -> Data {