Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* [*] Fix Blogging Reminders text color in dark mode [#25780]
* [*] Share extensions: Fix the site picker showing an error while sites are still loading [#25798]
* [*] Share Extension: Fix a crash after uploading a post to WordPress.com [#25773]
* [*] Add App Shortcuts for creating a post and opening Notifications, Stats, and the Reader from Siri, Spotlight, and the Shortcuts app [#25754]


27.0
-----
Expand Down
29 changes: 29 additions & 0 deletions Sources/Jetpack/AppIntents/AppIntentOpenError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import AppIntents
import Foundation

/// Errors surfaced to the system when an open intent cannot proceed.
enum AppIntentOpenError: Error, CustomLocalizedStringResourceConvertible {
case notLoggedIn
case siteNotFound

var localizedStringResource: LocalizedStringResource {
switch self {
case .notLoggedIn:
return LocalizedStringResource(
"ios-appintents.openError.notLoggedIn",
defaultValue: "Sign in to the app first to use this action.",
table: "AppIntents",
comment:
"Error shown by Siri or the Shortcuts app when an App Intent runs while no account is signed in."
)
case .siteNotFound:
return LocalizedStringResource(
"ios-appintents.openError.siteNotFound",
defaultValue: "The site could not be found.",
table: "AppIntents",
comment:
"Error shown by Siri or the Shortcuts app when the site an App Intent should act on no longer exists."
)
}
}
}
64 changes: 64 additions & 0 deletions Sources/Jetpack/AppIntents/JetpackAppShortcuts.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import AppIntents

/// Surfaces the navigation intents in the Shortcuts app and Siri without any user setup.
struct JetpackAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: NewPostIntent(),
phrases: [
"Create a new post in \(.applicationName)",
"Write a new post in \(.applicationName)",
"New \(.applicationName) post"
],
shortTitle: LocalizedStringResource(
"New Post",
defaultValue: "New Post",
table: "Localizable",
comment: "Short title of the New Post shortcut tile in Spotlight and the Shortcuts app."
),
systemImageName: "square.and.pencil"
)
AppShortcut(
intent: OpenNotificationsIntent(),
phrases: [
"Open my \(.applicationName) notifications",
"Show my \(.applicationName) notifications"
],
shortTitle: LocalizedStringResource(
"Notifications",
defaultValue: "Notifications",
table: "Localizable",
comment: "Short title of the Notifications shortcut tile in Spotlight and the Shortcuts app."
),
systemImageName: "bell"
)
AppShortcut(
intent: OpenStatsIntent(),
phrases: [
"Open my \(.applicationName) stats",
"Show my site stats in \(.applicationName)"
],
shortTitle: LocalizedStringResource(
"Stats",
defaultValue: "Stats",
table: "Localizable",
comment: "Short title of the Stats shortcut tile in Spotlight and the Shortcuts app."
),
systemImageName: "chart.bar"
)
AppShortcut(
intent: OpenReaderIntent(),
phrases: [
"Open the \(.applicationName) Reader",
"Open Reader in \(.applicationName)"
],
shortTitle: LocalizedStringResource(
"Reader",
defaultValue: "Reader",
table: "Localizable",
comment: "Short title of the Reader shortcut tile in Spotlight and the Shortcuts app."
),
systemImageName: "book"
)
}
}
29 changes: 29 additions & 0 deletions Sources/Jetpack/AppIntents/NewPostIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import AppIntents

/// Opens the post editor for the last used site.
struct NewPostIntent: AppIntent {
static let title = LocalizedStringResource(
"New Post",
defaultValue: "New Post",
table: "Localizable",
comment: "Title of the App Intent that opens the post editor. Shown in the Shortcuts app and Spotlight."
)
static let description = IntentDescription(
LocalizedStringResource(
"ios-appintents.newPost.description",
defaultValue: "Opens the editor to write a new post.",
table: "AppIntents",
comment: "Description of the App Intent that opens the post editor. Shown in the Shortcuts app."
)
)
static let openAppWhenRun = true

@MainActor
func perform() async throws -> some IntentResult {
guard AccountHelper.isLoggedIn else {
throw AppIntentOpenError.notLoggedIn
}
RootViewCoordinator.sharedPresenter.showPostEditor(animated: false)
return .result()
}
}
31 changes: 31 additions & 0 deletions Sources/Jetpack/AppIntents/OpenNotificationsIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import AppIntents

/// Opens the Notifications tab.
struct OpenNotificationsIntent: AppIntent {
static let title = LocalizedStringResource(
"ios-appintents.openNotifications.title",
defaultValue: "Open Notifications",
table: "AppIntents",
comment: "Title of the App Intent that opens the Notifications tab. Shown in the Shortcuts app and Spotlight."
)
static let description = IntentDescription(
LocalizedStringResource(
"ios-appintents.openNotifications.description",
defaultValue: "Opens your notifications.",
table: "AppIntents",
comment: "Description of the App Intent that opens the Notifications tab. Shown in the Shortcuts app."
)
)
static let openAppWhenRun = true

@MainActor
func perform() async throws -> some IntentResult {
guard AccountHelper.isLoggedIn else {
throw AppIntentOpenError.notLoggedIn
}
let presenter = RootViewCoordinator.sharedPresenter
presenter.rootViewController.dismiss(animated: false)
presenter.showNotificationsTab()
return .result()
}
}
31 changes: 31 additions & 0 deletions Sources/Jetpack/AppIntents/OpenReaderIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import AppIntents

/// Opens the Reader tab.
struct OpenReaderIntent: AppIntent {
static let title = LocalizedStringResource(
"notifications.emptyState.buttonOpenReader",
defaultValue: "Open Reader",
table: "Localizable",
comment: "Title of the App Intent that opens the Reader tab. Shown in the Shortcuts app and Spotlight."
)
static let description = IntentDescription(
LocalizedStringResource(
"ios-appintents.openReader.description",
defaultValue: "Opens the Reader to browse blogs you follow.",
table: "AppIntents",
comment: "Description of the App Intent that opens the Reader tab. Shown in the Shortcuts app."
)
)
static let openAppWhenRun = true

@MainActor
func perform() async throws -> some IntentResult {
guard AccountHelper.isLoggedIn else {
throw AppIntentOpenError.notLoggedIn
}
let presenter = RootViewCoordinator.sharedPresenter
presenter.rootViewController.dismiss(animated: false)
presenter.showReader(path: nil)
return .result()
}
}
46 changes: 46 additions & 0 deletions Sources/Jetpack/AppIntents/OpenStatsIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import AppIntents
import JetpackStatsWidgetsCore
import WordPressData

/// Opens Stats for a chosen site, or the last used site when none is picked.
struct OpenStatsIntent: AppIntent {
static let title = LocalizedStringResource(
"ios-appintents.openStats.title",
defaultValue: "Open Stats",
table: "AppIntents",
comment: "Title of the App Intent that opens Stats for a site. Shown in the Shortcuts app and Spotlight."
)
static let description = IntentDescription(
LocalizedStringResource(
"ios-appintents.openStats.description",
defaultValue: "Opens the stats for one of your sites.",
table: "AppIntents",
comment: "Description of the App Intent that opens Stats for a site. Shown in the Shortcuts app."
)
)
static let openAppWhenRun = true

@Parameter(
title: LocalizedStringResource(
"ios-widget.ILcGmf",
defaultValue: "Site",
table: "Localizable",
comment: "Label of the site parameter of the Open Stats App Intent. Shown in the Shortcuts app."
)
)
var site: SiteEntity?

@MainActor
func perform() async throws -> some IntentResult {
guard AccountHelper.isLoggedIn else {
throw AppIntentOpenError.notLoggedIn
}
guard let blog = Blog.forAppIntent(siteIdentifier: site?.id, in: ContextManager.shared.mainContext) else {
throw AppIntentOpenError.siteNotFound
}
let presenter = RootViewCoordinator.sharedPresenter
presenter.rootViewController.dismiss(animated: false)
presenter.showStats(for: blog, source: .shortcut)
return .result()
}
}
94 changes: 94 additions & 0 deletions Sources/Jetpack/Resources/AppIntents.xcstrings
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
{
"sourceLanguage" : "en",
"strings" : {
"ios-appintents.newPost.description" : {
"comment" : "Description of the App Intent that opens the post editor. Shown in the Shortcuts app.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Opens the editor to write a new post."
}
}
}
},
"ios-appintents.openError.notLoggedIn" : {
"comment" : "Error shown by Siri or the Shortcuts app when an App Intent runs while no account is signed in.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sign in to the app first to use this action."
}
}
}
},
"ios-appintents.openError.siteNotFound" : {
"comment" : "Error shown by Siri or the Shortcuts app when the site an App Intent should act on no longer exists.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "The site could not be found."
}
}
}
},
"ios-appintents.openNotifications.description" : {
"comment" : "Description of the App Intent that opens the Notifications tab. Shown in the Shortcuts app.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Opens your notifications."
}
}
}
},
"ios-appintents.openNotifications.title" : {
"comment" : "Title of the App Intent that opens the Notifications tab. Shown in the Shortcuts app and Spotlight.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Open Notifications"
}
}
}
},
"ios-appintents.openReader.description" : {
"comment" : "Description of the App Intent that opens the Reader tab. Shown in the Shortcuts app.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Opens the Reader to browse blogs you follow."
}
}
}
},
"ios-appintents.openStats.description" : {
"comment" : "Description of the App Intent that opens Stats for a site. Shown in the Shortcuts app.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Opens the stats for one of your sites."
}
}
}
},
"ios-appintents.openStats.title" : {
"comment" : "Title of the App Intent that opens Stats for a site. Shown in the Shortcuts app and Spotlight.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Open Stats"
}
}
}
}
},
"version" : "1.0"
}
Loading