import Foundation import AppIntents /// Which top-level tab the app is showing. Used so an intent can switch tabs. enum AppTab: Hashable { case bookmarks, tags, sources, podcasts, search } /// Bridges App Intents (which run in the main app process, since there is no /// separate AppIntents extension target) to the live SwiftUI scene. /// /// Intents mutate this shared, observable singleton; `MainContainer` and /// `SearchView` observe it and react (present a browser, switch to the search /// tab, etc.). This is the standard "intent → in-app navigation" pattern when /// perform() runs in-process. @MainActor @Observable final class IntentRouter { static let shared = IntentRouter() private init() {} /// A bookmark URL an intent asked to open in the in-app browser. var openBookmarkURL: String? /// A query an intent asked the app to search for. var searchRequest: String? func openBookmark(url: String) { openBookmarkURL = url } func search(_ query: String) { searchRequest = query } } /// Errors surfaced to Siri / Shortcuts when an intent can't run. enum MarksIntentError: Error, CustomLocalizedStringResourceConvertible { case notConfigured case invalidURL var localizedStringResource: LocalizedStringResource { switch self { case .notConfigured: "Open Marks and connect to your Linkding server first." case .invalidURL: "That doesn't look like a valid link." } } } /// Shared plumbing for the intents: build an API client from saved config and /// keep the widgets in sync after mutations. enum MarksIntent { /// Builds a `LinkdingAPI` from the saved server config, or throws a /// user-facing error if the app has never been connected. static func api() throws -> LinkdingAPI { guard let config = ServerConfig.load() else { throw MarksIntentError.notConfigured } return LinkdingAPI(config: config) } /// Prepend a freshly-saved bookmark into the widget cache so the Recent /// widget reflects it immediately (mirrors `BookmarksViewModel.load`). static func refreshWidgets(adding b: Bookmark) { var items = WidgetDataStore.loadBookmarks() items.removeAll { $0.url == b.url } items.insert( WidgetBookmark(url: b.url, title: b.displayTitle, domain: b.domain, faviconUrl: b.faviconUrl), at: 0 ) WidgetDataStore.saveBookmarks(Array(items.prefix(20))) } }