New shortcut + Localized

This commit is contained in:
Lumaa 2024-08-11 16:25:34 +02:00
parent 1a982bdf1e
commit 185b74d8c3
3 changed files with 241 additions and 5 deletions

View File

@ -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" : { "login.instance.unsafe" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -2519,7 +2631,8 @@
"value" : "Envoyer vers l'Apple Watch" "value" : "Envoyer vers l'Apple Watch"
} }
} }
} },
"shouldTranslate" : false
}, },
"settings.cancel" : { "settings.cancel" : {
"localizations" : { "localizations" : {
@ -4602,6 +4715,7 @@
} }
}, },
"widget.followers" : { "widget.followers" : {
"comment" : "Lowercase, shown in the \"Follow Count\" widget",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -4667,4 +4781,4 @@
} }
}, },
"version" : "1.0" "version" : "1.0"
} }

View File

@ -7,7 +7,8 @@ import RevenueCat
@main @main
struct ThreadedApp: App { struct ThreadedApp: App {
init() { 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 let apiKey = plist["RevenueCat_public"], let deviceId = UIDevice.current.identifierForVendor?.uuidString {
#if DEBUG #if DEBUG
Purchases.logLevel = .debug Purchases.logLevel = .debug
@ -16,6 +17,8 @@ struct ThreadedApp: App {
Purchases.configure(withAPIKey: apiKey, appUserID: deviceId) Purchases.configure(withAPIKey: apiKey, appUserID: deviceId)
} }
} }
ThreadedShortcuts.updateAppShortcutParameters()
} }
var body: some Scene { var body: some Scene {

View File

@ -5,6 +5,23 @@ import SwiftData
import WidgetKit import WidgetKit
import AppIntents 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 // MARK: - Account Intents
/// Widgets that require to select only an account will use this `ConfigurationIntent` /// Widgets that require to select only an account will use this `ConfigurationIntent`
@ -31,6 +48,7 @@ struct AccountEntity: AppEntity {
let client: Client let client: Client
let id: String let id: String
let username: String let username: String
let server: String
/// Bearer token /// Bearer token
let token: OauthToken let token: OauthToken
@ -42,14 +60,16 @@ struct AccountEntity: AppEntity {
} }
init(acct: String, username: String, token: OauthToken) { 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.id = acct
self.username = username self.username = username
self.token = token self.token = token
} }
init(loggedAccount: LoggedAccount) { 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.id = loggedAccount.acct
self.username = String(loggedAccount.acct.split(separator: "@")[0]) self.username = String(loggedAccount.acct.split(separator: "@")[0])
self.token = loggedAccount.token self.token = loggedAccount.token
@ -106,6 +126,22 @@ struct AccountQuery: EntityQuery {
// MARK: - Post Intents // 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 { struct OpenComposerIntent: AppIntent {
static var title: LocalizedStringResource = "intent.open.composer" static var title: LocalizedStringResource = "intent.open.composer"
static var description: IntentDescription? = IntentDescription("intent.open.composer.description") static var description: IntentDescription? = IntentDescription("intent.open.composer.description")
@ -121,3 +157,86 @@ struct OpenComposerIntent: AppIntent {
return .result() 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<String> {
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)
}
}
}