feat(ios): add ios shortcuts support
This commit is contained in:
@@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>15.1</string>
|
||||
<string>16.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '15.1'
|
||||
platform :ios, '16.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
@@ -40,6 +40,8 @@ PODS:
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_app_intents (0.1.0):
|
||||
- Flutter
|
||||
- flutter_callkit_incoming (0.0.1):
|
||||
- CryptoSwift
|
||||
- Flutter
|
||||
@@ -102,6 +104,7 @@ 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`)
|
||||
@@ -141,6 +144,8 @@ 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:
|
||||
@@ -188,6 +193,7 @@ 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
|
||||
@@ -212,6 +218,6 @@ SPEC CHECKSUMS:
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d
|
||||
|
||||
PODFILE CHECKSUM: a6ecbec6401c6461e69650e9ef66360aee70610f
|
||||
PODFILE CHECKSUM: 467be2eaee7e6ac5b7b2d2ef8bea817d4d70b736
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -585,7 +585,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -722,7 +722,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -773,7 +773,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@@ -865,7 +865,7 @@
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -908,7 +908,7 @@
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
@@ -948,7 +948,7 @@
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import AVFoundation
|
||||
import BackgroundTasks
|
||||
import Flutter
|
||||
import AppIntents
|
||||
import flutter_app_intents
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
final class VoiceBackgroundAudioManager {
|
||||
static let shared = VoiceBackgroundAudioManager()
|
||||
@@ -305,6 +308,248 @@ class BackgroundStreamingHandler: NSObject {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
endBackgroundTask()
|
||||
VoiceBackgroundAudioManager.shared.deactivate()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
enum AppIntentError: Error {
|
||||
case executionFailed(String)
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct AskConduitIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Ask Conduit"
|
||||
static var description = IntentDescription(
|
||||
"Start a Conduit chat with an optional prompt."
|
||||
)
|
||||
static var isDiscoverable = true
|
||||
static var openAppWhenRun = true
|
||||
|
||||
@Parameter(
|
||||
title: "Prompt",
|
||||
requestValueDialog: IntentDialog("What should Conduit answer?")
|
||||
)
|
||||
var prompt: String?
|
||||
|
||||
init() {}
|
||||
|
||||
init(prompt: String?) {
|
||||
self.prompt = prompt
|
||||
}
|
||||
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
let parameters: [String: Any] = prompt?.isEmpty == false
|
||||
? ["prompt": prompt ?? ""]
|
||||
: [:]
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
identifier: "app.cogwheel.conduit.ask_chat",
|
||||
parameters: parameters
|
||||
)
|
||||
|
||||
if let success = result["success"] as? Bool, success {
|
||||
let value = result["value"] as? String ?? "Opening chat"
|
||||
return .result(value: value)
|
||||
}
|
||||
|
||||
let message = result["error"] as? String
|
||||
?? "Unable to open Conduit chat"
|
||||
throw AppIntentError.executionFailed(message)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct StartVoiceCallIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Start Voice Call"
|
||||
static var description = IntentDescription(
|
||||
"Start a live voice call with Conduit."
|
||||
)
|
||||
static var isDiscoverable = true
|
||||
static var openAppWhenRun = true
|
||||
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
identifier: "app.cogwheel.conduit.start_voice_call",
|
||||
parameters: [:]
|
||||
)
|
||||
|
||||
if let success = result["success"] as? Bool, success {
|
||||
let value = result["value"] as? String ?? "Starting voice call"
|
||||
return .result(value: value)
|
||||
}
|
||||
|
||||
let message = result["error"] as? String
|
||||
?? "Unable to start voice call"
|
||||
throw AppIntentError.executionFailed(message)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct ConduitSendTextIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Send to Conduit"
|
||||
static var description = IntentDescription(
|
||||
"Start a Conduit chat with provided text."
|
||||
)
|
||||
static var isDiscoverable = true
|
||||
static var openAppWhenRun = true
|
||||
|
||||
@Parameter(
|
||||
title: "Text",
|
||||
requestValueDialog: IntentDialog("What should Conduit process?")
|
||||
)
|
||||
var text: String?
|
||||
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let trimmed = text?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
identifier: "app.cogwheel.conduit.send_text",
|
||||
parameters: ["text": trimmed ?? ""]
|
||||
)
|
||||
|
||||
if let success = result["success"] as? Bool, success {
|
||||
let value = result["value"] as? String ?? "Sent to Conduit"
|
||||
return .result(value: value)
|
||||
}
|
||||
|
||||
let message = result["error"] as? String ?? "Unable to send text"
|
||||
throw AppIntentError.executionFailed(message)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct ConduitSendUrlIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Send Link to Conduit"
|
||||
static var description = IntentDescription(
|
||||
"Send a URL into Conduit for summary or analysis."
|
||||
)
|
||||
static var isDiscoverable = true
|
||||
static var openAppWhenRun = true
|
||||
|
||||
@Parameter(
|
||||
title: "URL",
|
||||
requestValueDialog: IntentDialog("Which link should Conduit analyze?")
|
||||
)
|
||||
var url: URL
|
||||
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
identifier: "app.cogwheel.conduit.send_url",
|
||||
parameters: ["url": url.absoluteString]
|
||||
)
|
||||
|
||||
if let success = result["success"] as? Bool, success {
|
||||
let value = result["value"] as? String ?? "Sent link to Conduit"
|
||||
return .result(value: value)
|
||||
}
|
||||
|
||||
let message = result["error"] as? String ?? "Unable to send link"
|
||||
throw AppIntentError.executionFailed(message)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct ConduitSendImageIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Send Image to Conduit"
|
||||
static var description = IntentDescription(
|
||||
"Send an image into Conduit for analysis."
|
||||
)
|
||||
static var isDiscoverable = true
|
||||
static var openAppWhenRun = true
|
||||
|
||||
@Parameter(
|
||||
title: "Image",
|
||||
requestValueDialog: IntentDialog("Choose an image for Conduit.")
|
||||
)
|
||||
var image: IntentFile
|
||||
|
||||
func perform() async throws
|
||||
-> some IntentResult & ReturnsValue<String> & OpensIntent
|
||||
{
|
||||
if let type = image.type, !type.conforms(to: .image) {
|
||||
throw AppIntentError.executionFailed(
|
||||
"Only image files are supported."
|
||||
)
|
||||
}
|
||||
|
||||
let data = try image.data
|
||||
let base64 = data.base64EncodedString()
|
||||
let name = image.filename ?? "shared_image.jpg"
|
||||
|
||||
let plugin = FlutterAppIntentsPlugin.shared
|
||||
let result = await plugin.handleIntentInvocation(
|
||||
identifier: "app.cogwheel.conduit.send_image",
|
||||
parameters: [
|
||||
"filename": name,
|
||||
"bytes": base64,
|
||||
]
|
||||
)
|
||||
|
||||
if let success = result["success"] as? Bool, success {
|
||||
let value = result["value"] as? String ?? "Sent image to Conduit"
|
||||
return .result(value: value)
|
||||
}
|
||||
|
||||
let message = result["error"] as? String ?? "Unable to send image"
|
||||
throw AppIntentError.executionFailed(message)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
struct AppShortcuts: AppShortcutsProvider {
|
||||
static var appShortcuts: [AppShortcut] {
|
||||
return [
|
||||
AppShortcut(
|
||||
intent: AskConduitIntent(),
|
||||
phrases: [
|
||||
"Ask with \(.applicationName)",
|
||||
"Start chat in \(.applicationName)",
|
||||
"Open composer in \(.applicationName)",
|
||||
]
|
||||
),
|
||||
AppShortcut(
|
||||
intent: StartVoiceCallIntent(),
|
||||
phrases: [
|
||||
"Start voice call in \(.applicationName)",
|
||||
"Call with \(.applicationName)",
|
||||
"Begin voice chat in \(.applicationName)",
|
||||
]
|
||||
),
|
||||
AppShortcut(
|
||||
intent: ConduitSendTextIntent(),
|
||||
phrases: [
|
||||
"Send text to \(.applicationName)",
|
||||
"Share text with \(.applicationName)",
|
||||
"Summarize this in \(.applicationName)",
|
||||
]
|
||||
),
|
||||
AppShortcut(
|
||||
intent: ConduitSendUrlIntent(),
|
||||
phrases: [
|
||||
"Summarize link in \(.applicationName)",
|
||||
"Analyze link with \(.applicationName)",
|
||||
"Send URL to \(.applicationName)",
|
||||
]
|
||||
),
|
||||
AppShortcut(
|
||||
intent: ConduitSendImageIntent(),
|
||||
phrases: [
|
||||
"Send image to \(.applicationName)",
|
||||
"Analyze image with \(.applicationName)",
|
||||
"Share photo to \(.applicationName)",
|
||||
]
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user