refactor(app-intents): Replace flutter_app_intents with method channel
This commit is contained in:
@@ -43,8 +43,6 @@ PODS:
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_app_intents (0.1.0):
|
||||
- Flutter
|
||||
- flutter_callkit_incoming (0.0.1):
|
||||
- CryptoSwift
|
||||
- Flutter
|
||||
@@ -112,7 +110,6 @@ DEPENDENCIES:
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_app_intents (from `.symlinks/plugins/flutter_app_intents/ios`)
|
||||
- flutter_callkit_incoming (from `.symlinks/plugins/flutter_callkit_incoming/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
@@ -155,8 +152,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_app_intents:
|
||||
:path: ".symlinks/plugins/flutter_app_intents/ios"
|
||||
flutter_callkit_incoming:
|
||||
:path: ".symlinks/plugins/flutter_callkit_incoming/ios"
|
||||
flutter_local_notifications:
|
||||
@@ -208,7 +203,6 @@ SPEC CHECKSUMS:
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_app_intents: e77f999f398c841ab584a1925dbce33ee0168fb5
|
||||
flutter_callkit_incoming: cb8138af67cda6dd981f7101a5d709003af21502
|
||||
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
|
||||
@@ -2,7 +2,6 @@ import AVFoundation
|
||||
import BackgroundTasks
|
||||
import Flutter
|
||||
import AppIntents
|
||||
import flutter_app_intents
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
@@ -311,6 +310,48 @@ class BackgroundStreamingHandler: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
/// Manages the method channel for App Intent invocations to Flutter.
|
||||
/// Native Swift intents call this to invoke Flutter-side business logic.
|
||||
final class AppIntentMethodChannel {
|
||||
static var shared: AppIntentMethodChannel?
|
||||
|
||||
private let channel: FlutterMethodChannel
|
||||
|
||||
init(messenger: FlutterBinaryMessenger) {
|
||||
channel = FlutterMethodChannel(
|
||||
name: "conduit/app_intents",
|
||||
binaryMessenger: messenger
|
||||
)
|
||||
}
|
||||
|
||||
/// Invokes a Flutter handler for the given intent identifier.
|
||||
func invokeIntent(
|
||||
identifier: String,
|
||||
parameters: [String: Any]
|
||||
) async -> [String: Any] {
|
||||
// No [weak self] needed here - the closure executes immediately on the
|
||||
// main queue and there's no retain cycle risk. Using weak self would
|
||||
// risk the continuation never resuming if self became nil.
|
||||
return await withCheckedContinuation { continuation in
|
||||
DispatchQueue.main.async {
|
||||
self.channel.invokeMethod(
|
||||
identifier,
|
||||
arguments: parameters
|
||||
) { result in
|
||||
if let dict = result as? [String: Any] {
|
||||
continuation.resume(returning: dict)
|
||||
} else {
|
||||
continuation.resume(returning: [
|
||||
"success": false,
|
||||
"error": "Invalid response from Flutter"
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
enum AppIntentError: Error {
|
||||
case executionFailed(String)
|
||||
@@ -340,11 +381,14 @@ struct AskConduitIntent: AppIntent {
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
guard let channel = AppIntentMethodChannel.shared else {
|
||||
throw AppIntentError.executionFailed("App not ready")
|
||||
}
|
||||
|
||||
let parameters: [String: Any] = prompt?.isEmpty == false
|
||||
? ["prompt": prompt ?? ""]
|
||||
: [:]
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
let result = await channel.invokeIntent(
|
||||
identifier: "app.cogwheel.conduit.ask_chat",
|
||||
parameters: parameters
|
||||
)
|
||||
@@ -372,8 +416,11 @@ struct StartVoiceCallIntent: AppIntent {
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
guard let channel = AppIntentMethodChannel.shared else {
|
||||
throw AppIntentError.executionFailed("App not ready")
|
||||
}
|
||||
|
||||
let result = await channel.invokeIntent(
|
||||
identifier: "app.cogwheel.conduit.start_voice_call",
|
||||
parameters: [:]
|
||||
)
|
||||
@@ -407,9 +454,12 @@ struct ConduitSendTextIntent: AppIntent {
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
guard let channel = AppIntentMethodChannel.shared else {
|
||||
throw AppIntentError.executionFailed("App not ready")
|
||||
}
|
||||
|
||||
let trimmed = text?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
let result = await channel.invokeIntent(
|
||||
identifier: "app.cogwheel.conduit.send_text",
|
||||
parameters: ["text": trimmed ?? ""]
|
||||
)
|
||||
@@ -442,8 +492,11 @@ struct ConduitSendUrlIntent: AppIntent {
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
guard let channel = AppIntentMethodChannel.shared else {
|
||||
throw AppIntentError.executionFailed("App not ready")
|
||||
}
|
||||
|
||||
let result = await channel.invokeIntent(
|
||||
identifier: "app.cogwheel.conduit.send_url",
|
||||
parameters: ["url": url.absoluteString]
|
||||
)
|
||||
@@ -476,6 +529,10 @@ struct ConduitSendImageIntent: AppIntent {
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
guard let channel = AppIntentMethodChannel.shared else {
|
||||
throw AppIntentError.executionFailed("App not ready")
|
||||
}
|
||||
|
||||
if let type = image.type, !type.conforms(to: .image) {
|
||||
throw AppIntentError.executionFailed(
|
||||
"Only image files are supported."
|
||||
@@ -486,8 +543,7 @@ struct ConduitSendImageIntent: AppIntent {
|
||||
let base64 = data.base64EncodedString()
|
||||
let name = image.filename ?? "shared_image.jpg"
|
||||
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
let result = await channel.invokeIntent(
|
||||
identifier: "app.cogwheel.conduit.send_image",
|
||||
parameters: [
|
||||
"filename": name,
|
||||
@@ -563,6 +619,13 @@ struct AppShortcuts: AppShortcutsProvider {
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
// Setup App Intents method channel for native -> Flutter communication
|
||||
if let registrar = self.registrar(forPlugin: "AppIntentMethodChannel") {
|
||||
AppIntentMethodChannel.shared = AppIntentMethodChannel(
|
||||
messenger: registrar.messenger()
|
||||
)
|
||||
}
|
||||
|
||||
// Setup background streaming handler using the plugin registry messenger
|
||||
if let registrar = self.registrar(forPlugin: "BackgroundStreamingHandler") {
|
||||
let channel = FlutterMethodChannel(
|
||||
|
||||
Reference in New Issue
Block a user