Add AppIntent service + post to Mastodon intent

This commit is contained in:
Thomas Ricouard 2024-05-02 09:32:19 +02:00
parent abcd4cc321
commit 4e4d903c44
11 changed files with 109 additions and 7 deletions

View file

@ -42,6 +42,8 @@
9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4629506F6600B3281A /* NotificationTab.swift */; };
9F35DB4A29506FA100B3281A /* Notifications in Frameworks */ = {isa = PBXBuildFile; productRef = 9F35DB4929506FA100B3281A /* Notifications */; };
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F35DB4B2952005C00B3281A /* MessagesTab.swift */; };
9F37BDDB2BE36E22007F28AD /* PostIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37BDDA2BE36E22007F28AD /* PostIntent.swift */; };
9F37BDDD2BE37193007F28AD /* AppIntentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F37BDDC2BE37193007F28AD /* AppIntentService.swift */; };
9F38A7332ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
9F38A7342ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
9F38A7352ACEA26100DBCD66 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */; };
@ -196,6 +198,8 @@
9F35DB4629506F6600B3281A /* NotificationTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTab.swift; sourceTree = "<group>"; };
9F35DB4829506F7F00B3281A /* Notifications */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Notifications; path = Packages/Notifications; sourceTree = "<group>"; };
9F35DB4B2952005C00B3281A /* MessagesTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesTab.swift; sourceTree = "<group>"; };
9F37BDDA2BE36E22007F28AD /* PostIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostIntent.swift; sourceTree = "<group>"; };
9F37BDDC2BE37193007F28AD /* AppIntentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntentService.swift; sourceTree = "<group>"; };
9F38A7322ACEA26100DBCD66 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
9F398AA32935F90100A889F2 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = "<group>"; };
9F398AA52935FE8A00A889F2 /* AppRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRegistry.swift; sourceTree = "<group>"; };
@ -349,6 +353,15 @@
path = IceCubesNotifications;
sourceTree = "<group>";
};
9F37BDD92BE36E08007F28AD /* IceCubesAppIntents */ = {
isa = PBXGroup;
children = (
9F37BDDA2BE36E22007F28AD /* PostIntent.swift */,
9F37BDDC2BE37193007F28AD /* AppIntentService.swift */,
);
path = IceCubesAppIntents;
sourceTree = "<group>";
};
9F398AB429360A5800A889F2 /* App */ = {
isa = PBXGroup;
children = (
@ -454,6 +467,7 @@
DD31E2E5297FB68B00A4BE29 /* IceCubesApp.xcconfig */,
9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */,
9FBFE63B292A715500C250E9 /* IceCubesApp */,
9F37BDD92BE36E08007F28AD /* IceCubesAppIntents */,
E9DF41FD29830FEC0003AAD2 /* IceCubesActionExtension */,
9F2A5417296AB631009B2D7C /* IceCubesNotifications */,
9FAD858929743F7400496AB1 /* IceCubesShareExtension */,
@ -828,6 +842,8 @@
9F15D6042B3DC2180008C220 /* NavigationSheet.swift in Sources */,
9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */,
9F35DB4C2952005C00B3281A /* MessagesTab.swift in Sources */,
9F37BDDB2BE36E22007F28AD /* PostIntent.swift in Sources */,
9F37BDDD2BE37193007F28AD /* AppIntentService.swift in Sources */,
9FAD85CF2975B68900496AB1 /* SideBarView.swift in Sources */,
9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */,
9FC14EF42B494D940006CEE1 /* RemoteTimelinesSettingView.swift in Sources */,

View file

@ -85,7 +85,10 @@ extension View {
StatusEditor.MainView(mode: .replyTo(status: status))
.withEnvironments()
case let .newStatusEditor(visibility):
StatusEditor.MainView(mode: .new(visibility: visibility))
StatusEditor.MainView(mode: .new(text: nil, visibility: visibility))
.withEnvironments()
case let .prefilledStatusEditor(text, visibility):
StatusEditor.MainView(mode: .new(text: text, visibility: visibility))
.withEnvironments()
case let .editStatusEditor(status):
StatusEditor.MainView(mode: .edit(status: status))

View file

@ -2,6 +2,7 @@ import Env
import MediaUI
import StatusKit
import SwiftUI
import AppIntents
extension IceCubesApp {
var appScene: some Scene {
@ -22,6 +23,7 @@ extension IceCubesApp {
.environment(theme)
.environment(watcher)
.environment(pushNotificationsService)
.environment(appIntentService)
.environment(\.isSupporter, isSupporter)
.sheet(item: $quickLook.selectedMediaAttachment) { selectedMediaAttachment in
MediaUIView(selectedAttachment: selectedMediaAttachment,
@ -47,6 +49,12 @@ extension IceCubesApp {
}
}
}
.onChange(of: appIntentService.handledIntent) { _, _ in
if let intent = appIntentService.handledIntent?.intent {
handleIntent(intent)
appIntentService.handledIntent = nil
}
}
.withModelContainer()
}
#if targetEnvironment(macCatalyst)
@ -74,7 +82,9 @@ extension IceCubesApp {
Group {
switch destination.wrappedValue {
case let .newStatusEditor(visibility):
StatusEditor.MainView(mode: .new(visibility: visibility))
StatusEditor.MainView(mode: .new(text: nil, visibility: visibility))
case let .prefilledStatusEditor(text, visibility):
StatusEditor.MainView(mode: .new(text: text, visibility: visibility))
case let .editStatusEditor(status):
StatusEditor.MainView(mode: .edit(status: status))
case let .quoteStatusEditor(status):
@ -115,4 +125,16 @@ extension IceCubesApp {
.defaultSize(width: 1200, height: 1000)
.windowResizability(.contentMinSize)
}
private func handleIntent(_ intent: any AppIntent) {
if let postIntent = appIntentService.handledIntent?.intent as? PostIntent {
#if os(visionOS) || os(macOS)
openWindow(value: WindowDestinationEditor.prefilledStatusEditor(text: postIntent.content ?? "",
visibility: userPreferences.postVisibility))
#else
appRouterPath.presentedSheet = .prefilledStatusEditor(text: postIntent.content ?? "",
visibility: userPreferences.postVisibility)
#endif
}
}
}

View file

@ -23,6 +23,7 @@ struct IceCubesApp: App {
@State var currentAccount = CurrentAccount.shared
@State var userPreferences = UserPreferences.shared
@State var pushNotificationsService = PushNotificationsService.shared
@State var appIntentService = AppIntentService.shared
@State var watcher = StreamWatcher.shared
@State var quickLook = QuickLook.shared
@State var theme = Theme.shared

View file

@ -39936,6 +39936,12 @@
}
}
}
},
"Post content" : {
},
"Post to Mastodon" : {
},
"report.action.send" : {
"localizations" : {
@ -80466,6 +80472,9 @@
}
}
}
},
"Use Ice Cubes to post text to Mastodon" : {
}
},
"version" : "1.0"

View file

@ -0,0 +1,25 @@
import SwiftUI
import AppIntents
@Observable
public class AppIntentService: @unchecked Sendable {
struct HandledIntent: Equatable {
static func == (lhs: AppIntentService.HandledIntent, rhs: AppIntentService.HandledIntent) -> Bool {
lhs.id == rhs.id
}
let id: String
let intent: any AppIntent
init(intent: any AppIntent) {
self.id = UUID().uuidString
self.intent = intent
}
}
public static let shared = AppIntentService()
var handledIntent: HandledIntent?
private init() { }
}

View file

@ -0,0 +1,20 @@
import Foundation
import AppIntents
struct PostIntent: AppIntent {
static let title: LocalizedStringResource = "Post to Mastodon"
static var description: IntentDescription {
get {
"Use Ice Cubes to post text to Mastodon"
}
}
static let openAppWhenRun: Bool = true
@Parameter(title: "Post content")
var content: String?
func perform() async throws -> some IntentResult {
AppIntentService.shared.handledIntent = .init(intent: self)
return .result()
}
}

View file

@ -31,6 +31,7 @@ public enum RouterDestination: Hashable {
public enum WindowDestinationEditor: Hashable, Codable {
case newStatusEditor(visibility: Models.Visibility)
case prefilledStatusEditor(text: String, visibility: Models.Visibility)
case editStatusEditor(status: Status)
case replyToStatusEditor(status: Status)
case quoteStatusEditor(status: Status)
@ -52,6 +53,7 @@ public enum SheetDestination: Identifiable, Hashable {
}
case newStatusEditor(visibility: Models.Visibility)
case prefilledStatusEditor(text: String, visibility: Models.Visibility)
case editStatusEditor(status: Status)
case replyToStatusEditor(status: Status)
case quoteStatusEditor(status: Status)
@ -78,7 +80,7 @@ public enum SheetDestination: Identifiable, Hashable {
public var id: String {
switch self {
case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor,
.mentionStatusEditor, .quoteLinkStatusEditor:
.mentionStatusEditor, .quoteLinkStatusEditor, .prefilledStatusEditor:
"statusEditor"
case .listCreate:
"listCreate"

View file

@ -161,7 +161,7 @@ extension StatusEditor {
Button {
// all SEVM have the same visibility value
followUpSEVMs.append(ViewModel(mode: .new(visibility: focusedSEVM.visibility)))
followUpSEVMs.append(ViewModel(mode: .new(text: nil, visibility: focusedSEVM.visibility)))
} label: {
Image(systemName: "arrowshape.turn.up.left.circle.fill")
}

View file

@ -5,7 +5,7 @@ import UIKit
public extension StatusEditor.ViewModel {
enum Mode {
case replyTo(status: Status)
case new(visibility: Models.Visibility)
case new(text: String?, visibility: Models.Visibility)
case edit(status: Status)
case quote(status: Status)
case quoteLink(link: URL)

View file

@ -301,7 +301,11 @@ public extension StatusEditor {
func prepareStatusText() {
switch mode {
case let .new(visibility):
case let .new(text, visibility):
if let text {
statusText = .init(string: text)
selectedRange = .init(location: text.utf16.count, length: 0)
}
self.visibility = visibility
case let .shareExtension(items):
itemsProvider = items
@ -557,7 +561,7 @@ public extension StatusEditor {
!statusText.string.contains(url.absoluteString)
{
embeddedStatus = nil
mode = .new(visibility: visibility)
mode = .new(text: nil, visibility: visibility)
}
}