From 185b74d8c36ccd163c85673e70db30a5bd117b70 Mon Sep 17 00:00:00 2001 From: Lumaa Date: Sun, 11 Aug 2024 16:25:34 +0200 Subject: [PATCH] New shortcut + Localized --- Threaded/Localizable.xcstrings | 118 +++++++++++++++++++++++++++++- Threaded/ThreadedApp.swift | 5 +- ThreadedWidgets/AppIntent.swift | 123 +++++++++++++++++++++++++++++++- 3 files changed, 241 insertions(+), 5 deletions(-) diff --git a/Threaded/Localizable.xcstrings b/Threaded/Localizable.xcstrings index 6dcb532..4c51f7d 100644 --- a/Threaded/Localizable.xcstrings +++ b/Threaded/Localizable.xcstrings @@ -1621,6 +1621,118 @@ } } }, + "intent.publish.any.issue" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There was an issue while posting." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Il y a eu un soucis lors de la publication." + } + } + } + }, + "intent.publish.any.visibility-dialog" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "How visible do you want your post to be?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Quelle visibilité voulez-vous que cette publication ai ?" + } + } + } + }, + "intent.publish.text" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Publish a text-based post" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Publier du texte dans une publication" + } + } + } + }, + "intent.publish.text.account-dialog" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select the account the post will be published on" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sélectionnez le compte sur lequel la publication sera publiée" + } + } + } + }, + "intent.publish.text.content-dialog" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Write the content of your post" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Écrivez le contenu de votre publication" + } + } + } + }, + "intent.publish.text.description" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Publish a text-based post on Mastodon" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Publie du texte dans une publication sur Mastodon" + } + } + } + }, + "intent.publish.text.summary-${content}" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Post \"${content}\"" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Publier « ${content} »" + } + } + } + }, "login.instance.unsafe" : { "localizations" : { "en" : { @@ -2519,7 +2631,8 @@ "value" : "Envoyer vers l'Apple Watch" } } - } + }, + "shouldTranslate" : false }, "settings.cancel" : { "localizations" : { @@ -4602,6 +4715,7 @@ } }, "widget.followers" : { + "comment" : "Lowercase, shown in the \"Follow Count\" widget", "localizations" : { "en" : { "stringUnit" : { @@ -4667,4 +4781,4 @@ } }, "version" : "1.0" -} \ No newline at end of file +} diff --git a/Threaded/ThreadedApp.swift b/Threaded/ThreadedApp.swift index 6e3635a..92c3a8f 100644 --- a/Threaded/ThreadedApp.swift +++ b/Threaded/ThreadedApp.swift @@ -7,7 +7,8 @@ import RevenueCat @main struct ThreadedApp: App { init() { - guard let plist = AppDelegate.readSecret() else { return } + guard let plist = AppDelegate.readSecret() else { fatalError("Missing Secret.plist file") } + if let apiKey = plist["RevenueCat_public"], let deviceId = UIDevice.current.identifierForVendor?.uuidString { #if DEBUG Purchases.logLevel = .debug @@ -16,6 +17,8 @@ struct ThreadedApp: App { Purchases.configure(withAPIKey: apiKey, appUserID: deviceId) } } + + ThreadedShortcuts.updateAppShortcutParameters() } var body: some Scene { diff --git a/ThreadedWidgets/AppIntent.swift b/ThreadedWidgets/AppIntent.swift index b93ad45..7bcbc99 100644 --- a/ThreadedWidgets/AppIntent.swift +++ b/ThreadedWidgets/AppIntent.swift @@ -5,6 +5,23 @@ import SwiftData import WidgetKit import AppIntents +// MARK: - Shortcuts + +struct ThreadedShortcuts: AppShortcutsProvider { + static var appShortcuts: [AppShortcut] = [ + .init( + intent: OpenComposerIntent(), + phrases: [ + "Start a \(.applicationName) post", + "Post on \(.applicationName)" + ], + shortTitle: "status.posting", + systemImageName: "square.and.pencil" + ) + ] + static var shortcutTileColor: ShortcutTileColor = .grayBlue +} + // MARK: - Account Intents /// Widgets that require to select only an account will use this `ConfigurationIntent` @@ -31,6 +48,7 @@ struct AccountEntity: AppEntity { let client: Client let id: String let username: String + let server: String /// Bearer token let token: OauthToken @@ -42,14 +60,16 @@ struct AccountEntity: AppEntity { } init(acct: String, username: String, token: OauthToken) { - self.client = Client(server: String(acct.split(separator: "@")[1]), version: .v2, oauthToken: token) + self.server = String(acct.split(separator: "@")[1]) + self.client = Client(server: self.server, version: .v2, oauthToken: token) self.id = acct self.username = username self.token = token } init(loggedAccount: LoggedAccount) { - self.client = Client(server: String(loggedAccount.acct.split(separator: "@")[1]), version: .v2, oauthToken: loggedAccount.token) + self.server = loggedAccount.app?.server ?? "" + self.client = Client(server: self.server, version: .v2, oauthToken: loggedAccount.token) self.id = loggedAccount.acct self.username = String(loggedAccount.acct.split(separator: "@")[0]) self.token = loggedAccount.token @@ -106,6 +126,22 @@ struct AccountQuery: EntityQuery { // MARK: - Post Intents +extension Visibility: AppEnum { + public static var caseDisplayRepresentations: [Visibility : DisplayRepresentation] { + [ + .pub : DisplayRepresentation(title: "status.posting.visibility.public"), + .priv : DisplayRepresentation(title: "status.posting.visibility.private"), + .unlisted : DisplayRepresentation(title: "status.posting.visibility.unlisted"), + .direct : DisplayRepresentation(title: "status.posting.visibility.direct") + ] + + } + + public static var typeDisplayRepresentation: TypeDisplayRepresentation { + TypeDisplayRepresentation(name: "status.posting.visibility") + } +} + struct OpenComposerIntent: AppIntent { static var title: LocalizedStringResource = "intent.open.composer" static var description: IntentDescription? = IntentDescription("intent.open.composer.description") @@ -121,3 +157,86 @@ struct OpenComposerIntent: AppIntent { return .result() } } + +struct PublishTextIntent: AppIntent { + static var title: LocalizedStringResource = "intent.publish.text" + static var description: IntentDescription? = IntentDescription("intent.publish.text.description") + + static var isDiscoverable: Bool = true + static var openAppWhenRun: Bool = false + + static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication + + @Parameter(title: "account", requestDisambiguationDialog: IntentDialog("intent.publish.text.account-dialog")) + var account: AccountEntity? + + @Parameter(title: "status.posting.placeholder", requestValueDialog: IntentDialog("intent.publish.text.content-dialog")) + var content: String + + @Parameter(title: "status.posting.visibility", requestDisambiguationDialog: IntentDialog("intent.publish.any.visibility-dialog")) + var visibility: Visibility + + static var parameterSummary: any ParameterSummary { + Summary("intent.publish.text.summary-\(\.$content)") { + \.$account + \.$visibility + } + } + + func perform() async throws -> some IntentResult & ShowsSnippetView & ReturnsValue { + if let client = account?.client, !client.server.isEmpty { + let data: StatusData = .init( + status: self.content, + visibility: self.visibility + ) + + // posting requires v1 + if let res = try? await client.post(endpoint: Statuses.postStatus(json: data), forceVersion: .v1), res.statusCode == 200 { + return .result( + value: self.content, + view: Self.StatusSuccess(acc: account!, json: data) + ) + } + } + return await .result(value: "", view: IssueView()) + } + + private struct IssueView: View { + var body: some View { + Label("intent.publish.any.issue", systemImage: "exclamationmark.triangle.fill") + .foregroundStyle(Color.red) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) + .padding() + .background(Color.black) + .clipShape(Capsule()) + .padding(.horizontal) + } + } + + private struct StatusSuccess: View { + var acc: AccountEntity + var json: StatusData + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: 7.5) { + Text("@\(acc.username)") + .foregroundStyle(Color.white) + .bold() + + Text(json.status) + .foregroundStyle(Color.white) + .lineLimit(2) + } + + Spacer() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.vertical) + .padding(.horizontal, 25) + .background(Color.black) + .clipShape(Capsule()) + .padding(.horizontal) + } + } +}