feat(intents): add App Intents + Siri integration
All checks were successful
CI / build-and-deploy (push) Successful in 24s
All checks were successful
CI / build-and-deploy (push) Successful in 24s
Expose Marks to Siri, Spotlight, and Shortcuts via App Intents: - BookmarkEntity (AppEntity + IndexedEntity, keyed on the Linkding server id) with a server-backed EntityStringQuery for live lookup - Intents: Add, Open (OpenIntent), Search, ShowUnread, and an AI Summarize intent backed by ClaudeService; AppShortcutsProvider with spoken phrases; Siri snippet views and dialog - SearchMarksIntent conforms to the system.search App Schema (@AppIntent(schema:), ShowInAppSearchResultsIntent) so it is executable by conversational Siri / Apple Intelligence - IntentRouter bridges intents (run in the app process) to the SwiftUI scene for open/search navigation; widgets refreshed after adds - Link AppIntents.framework so the metadata processor extracts shortcut phrases and validates the assistant schema Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,8 @@ struct MainContainer: View {
|
||||
@State private var viewModel: BookmarksViewModel
|
||||
@State private var deepLinkBrowser: IdentifiableURL?
|
||||
@State private var showDeepLinkPlayer = false
|
||||
@State private var selectedTab: AppTab = .bookmarks
|
||||
@State private var router = IntentRouter.shared
|
||||
|
||||
init(config: ServerConfig, onDisconnect: @escaping () -> Void) {
|
||||
self.config = config
|
||||
@@ -45,20 +47,23 @@ struct MainContainer: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
Tab("Bookmarks", systemImage: "bookmark") {
|
||||
TabView(selection: $selectedTab) {
|
||||
Tab("Bookmarks", systemImage: "bookmark", value: AppTab.bookmarks) {
|
||||
BookmarksView(viewModel: viewModel, onDisconnect: onDisconnect)
|
||||
}
|
||||
Tab("Tags", systemImage: "tag") {
|
||||
Tab("Tags", systemImage: "tag", value: AppTab.tags) {
|
||||
TagsView(viewModel: viewModel)
|
||||
}
|
||||
Tab(role: .search) {
|
||||
Tab(value: AppTab.search, role: .search) {
|
||||
SearchView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
.onOpenURL { url in
|
||||
handleDeepLink(url)
|
||||
}
|
||||
.task { applyPendingIntent() }
|
||||
.onChange(of: router.openBookmarkURL) { _, _ in applyPendingIntent() }
|
||||
.onChange(of: router.searchRequest) { _, _ in applyPendingIntent() }
|
||||
.sheet(item: $deepLinkBrowser) { item in
|
||||
BrowserView(url: item.url, title: item.url.host ?? "", claude: viewModel.claude, podcastPlayer: viewModel.podcastPlayer)
|
||||
}
|
||||
@@ -73,6 +78,27 @@ struct MainContainer: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reacts to requests an App Intent placed on `IntentRouter` (opening a
|
||||
/// bookmark, or searching). Also runs once at launch so cold-starts via an
|
||||
/// intent are honored.
|
||||
private func applyPendingIntent() {
|
||||
if let urlString = router.openBookmarkURL {
|
||||
router.openBookmarkURL = nil
|
||||
if let url = URL(string: urlString) {
|
||||
deepLinkBrowser = IdentifiableURL(url: url)
|
||||
}
|
||||
}
|
||||
if let query = router.searchRequest {
|
||||
selectedTab = .search
|
||||
Task {
|
||||
viewModel.searchQuery = query
|
||||
await viewModel.search()
|
||||
}
|
||||
// SearchView consumes the text from the router on appear; leave it
|
||||
// set until then, then SearchView clears it.
|
||||
}
|
||||
}
|
||||
|
||||
private func handleDeepLink(_ url: URL) {
|
||||
guard url.scheme == "marks",
|
||||
let comps = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||
|
||||
Reference in New Issue
Block a user