From ba4cc899f8b0190539f9812e9ecc12c28293ac53 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Sat, 4 May 2024 13:12:43 +0200 Subject: [PATCH] Add inline post Intent --- IceCubesApp.xcodeproj/project.pbxproj | 4 + .../Localization/Localizable.xcstrings | 58 +++++++++- IceCubesAppIntents/AppShortcuts.swift | 14 ++- IceCubesAppIntents/InlinePostIntent.swift | 101 ++++++++++++++++++ 4 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 IceCubesAppIntents/InlinePostIntent.swift diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index 318216fb..86970016 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -66,6 +66,7 @@ 9F7335EF29674F7100AFF0BA /* QuickLook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F7335EE29674F7100AFF0BA /* QuickLook.framework */; }; 9F7335F22967608F00AFF0BA /* AddRemoteTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */; }; 9F7335F92968576500AFF0BA /* DisplaySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */; }; + 9F7788C02BE63935004E6BEF /* InlinePostIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */; }; 9F7D93942980063100EE6B7A /* AppAccount in Frameworks */ = {isa = PBXBuildFile; productRef = 9F7D93932980063100EE6B7A /* AppAccount */; }; 9F7D939A29805DBD00EE6B7A /* AccountSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */; }; 9FA6FD6229C04A8800E2312C /* TranslationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */; }; @@ -224,6 +225,7 @@ 9F7335EE29674F7100AFF0BA /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.2.sdk/System/Library/Frameworks/QuickLook.framework; sourceTree = DEVELOPER_DIR; }; 9F7335F12967608F00AFF0BA /* AddRemoteTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRemoteTimelineView.swift; sourceTree = ""; }; 9F7335F82968576500AFF0BA /* DisplaySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaySettingsView.swift; sourceTree = ""; }; + 9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlinePostIntent.swift; sourceTree = ""; }; 9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "IceCubesApp-release.xcconfig"; sourceTree = ""; }; 9F7D939929805DBD00EE6B7A /* AccountSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingView.swift; sourceTree = ""; }; 9FA6FD6129C04A8800E2312C /* TranslationSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TranslationSettingsView.swift; sourceTree = ""; }; @@ -367,6 +369,7 @@ 9F37BDDE2BE37C35007F28AD /* TabIntent.swift */, 9F37BDE02BE38646007F28AD /* PostImageIntent.swift */, 9F37BDE22BE393A7007F28AD /* AppShortcuts.swift */, + 9F7788BF2BE63935004E6BEF /* InlinePostIntent.swift */, ); path = IceCubesAppIntents; sourceTree = ""; @@ -855,6 +858,7 @@ 9F37BDDD2BE37193007F28AD /* AppIntentService.swift in Sources */, 9F37BDE12BE38646007F28AD /* PostImageIntent.swift in Sources */, 9F37BDDF2BE37C35007F28AD /* TabIntent.swift in Sources */, + 9F7788C02BE63935004E6BEF /* InlinePostIntent.swift in Sources */, 9FAD85CF2975B68900496AB1 /* SideBarView.swift in Sources */, 9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */, 9FC14EF42B494D940006CEE1 /* RemoteTimelinesSettingView.swift in Sources */, diff --git a/IceCubesApp/Resources/Localization/Localizable.xcstrings b/IceCubesApp/Resources/Localization/Localizable.xcstrings index b5024a06..77eca02c 100644 --- a/IceCubesApp/Resources/Localization/Localizable.xcstrings +++ b/IceCubesApp/Resources/Localization/Localizable.xcstrings @@ -306,6 +306,9 @@ } } } + }, + "%@" : { + }, "%@ add-tag-groups.edit.tags.field.warning.search-results.already-selected" : { "localizations" : { @@ -660,6 +663,9 @@ } } } + }, + "%@ was posted on Mastodon" : { + }, "%@© 2024 Thomas Ricouard" : { "localizations" : { @@ -8853,6 +8859,9 @@ } } } + }, + "Account" : { + }, "account.action.add-remove-list" : { "comment" : "MARK: Package: Account", @@ -20393,6 +20402,9 @@ } } } + }, + "An error occured while posting to Mastodon, please try again." : { + }, "app-account.button.add" : { "comment" : "MARK: Package: AppAccount", @@ -20513,6 +20525,9 @@ } } } + }, + "AppAccount" : { + }, "Bookmarks" : { "localizations" : { @@ -20523,6 +20538,12 @@ } } } + }, + "Compose a status" : { + + }, + "Content of the post to be sent to Mastodon" : { + }, "conversations.action.delete" : { "comment" : "MARK: Package: Conversations", @@ -30758,6 +30779,9 @@ } } } + }, + "Followers Only" : { + }, "Home Timeline" : { "localizations" : { @@ -40098,6 +40122,7 @@ } }, "Post a status" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -40106,6 +40131,9 @@ } } } + }, + "Post a status with an image" : { + }, "Post an image to Mastodon" : { "localizations" : { @@ -40128,6 +40156,7 @@ } }, "Post images" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -40146,6 +40175,12 @@ } } } + }, + "Post visibility" : { + + }, + "Private" : { + }, "Private Messages" : { "localizations" : { @@ -40166,6 +40201,12 @@ } } } + }, + "Public" : { + + }, + "Quiet Public" : { + }, "report.action.send" : { "localizations" : { @@ -40650,6 +40691,15 @@ } } } + }, + "Send a text status" : { + + }, + "Send a text status to Mastodon using Ice Cubes" : { + + }, + "Send text status to Mastodon" : { + }, "Settings" : { "localizations" : { @@ -80766,7 +80816,13 @@ } } } + }, + "Visibility" : { + + }, + "Visibility of your post" : { + } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/IceCubesAppIntents/AppShortcuts.swift b/IceCubesAppIntents/AppShortcuts.swift index 89610263..0d297ab4 100644 --- a/IceCubesAppIntents/AppShortcuts.swift +++ b/IceCubesAppIntents/AppShortcuts.swift @@ -7,9 +7,17 @@ struct AppShortcuts: AppShortcutsProvider { phrases: [ "Post \(\.$content) in \(.applicationName)", "Post a status on Mastodon with \(.applicationName)", - "Write a status in \(.applicationName)", ], - shortTitle: "Post a status", + shortTitle: "Compose a status", + systemImageName: "square.and.pencil" + ) + AppShortcut( + intent: InlinePostIntent(), + phrases: [ + "Write a status with \(.applicationName)", + "Send on Status on Mastodon with \(.applicationName)", + ], + shortTitle: "Send a text status", systemImageName: "square.and.pencil" ) AppShortcut( @@ -27,7 +35,7 @@ struct AppShortcuts: AppShortcutsProvider { "Post images \(\.$images) in \(.applicationName)", "Send photos \(\.$images) with \(.applicationName)", ], - shortTitle: "Post images", + shortTitle: "Post a status with an image", systemImageName: "photo" ) } diff --git a/IceCubesAppIntents/InlinePostIntent.swift b/IceCubesAppIntents/InlinePostIntent.swift new file mode 100644 index 00000000..8d854cc7 --- /dev/null +++ b/IceCubesAppIntents/InlinePostIntent.swift @@ -0,0 +1,101 @@ +import Foundation +import AppIntents +import AppAccount +import Network +import Env +import Models + +enum PostVisibility: String, AppEnum { + case direct, priv, unlisted, pub + + public static var caseDisplayRepresentations: [PostVisibility : DisplayRepresentation] { + [.direct: "Private", + .priv: "Followers Only", + .unlisted: "Quiet Public", + .pub: "Public"] + } + + static var typeDisplayName: LocalizedStringResource { + get { "Visibility" } + } + + public static let typeDisplayRepresentation: TypeDisplayRepresentation = "Visibility" + + var toAppVisibility: Models.Visibility { + switch self { + case .direct: + .direct + case .priv: + .priv + case .unlisted: + .unlisted + case .pub: + .pub + } + } +} + +struct AppAccountWrapper: Identifiable, AppEntity { + var id: String { account.id } + + let account: AppAccount + + static var defaultQuery = DefaultAppAccountQuery() + + static var typeDisplayRepresentation: TypeDisplayRepresentation = "AppAccount" + + var displayRepresentation: DisplayRepresentation { + DisplayRepresentation(title: "\(account.accountName ?? account.server)") + } + +} + +struct DefaultAppAccountQuery: EntityQuery { + + func entities(for identifiers: [AppAccountWrapper.ID]) async throws -> [AppAccountWrapper] { + return await AppAccountsManager.shared.availableAccounts.filter { account in + identifiers.contains { id in + id == account.id + } + }.map{ AppAccountWrapper(account: $0 )} + } + + func suggestedEntities() async throws -> [AppAccountWrapper] { + await AppAccountsManager.shared.availableAccounts.map{ .init(account: $0)} + } + + func defaultResult() async -> AppAccountWrapper? { + await .init(account: AppAccountsManager.shared.currentAccount) + } +} + +struct InlinePostIntent: AppIntent { + static let title: LocalizedStringResource = "Send text status to Mastodon" + static var description: IntentDescription { + get { + "Send a text status to Mastodon using Ice Cubes" + } + } + static let openAppWhenRun: Bool = false + + @Parameter(title: "Account", requestValueDialog: IntentDialog("Account")) + var account: AppAccountWrapper + + @Parameter(title: "Post visibility", requestValueDialog: IntentDialog("Visibility of your post")) + var visibility: PostVisibility + + @Parameter(title: "Post content", requestValueDialog: IntentDialog("Content of the post to be sent to Mastodon")) + var content: String + + @MainActor + func perform() async throws -> some IntentResult & ProvidesDialog & ShowsSnippetView { + let client = Client(server: account.account.server, version: .v1, oauthToken: account.account.oauthToken) + let status = StatusData(status: content, visibility: visibility.toAppVisibility) + do { + let status: Status = try await client.post(endpoint: Statuses.postStatus(json: status)) + return .result(dialog: "\(status.content.asRawText) was posted on Mastodon") + } catch { + return .result(dialog: "An error occured while posting to Mastodon, please try again.") + } + } +}