Files
linkding-ios/Marks/MarksApp.swift
Krishna Kumar 199fc2d043
All checks were successful
CI / build-and-deploy (push) Successful in 41s
Fix ShareExtension "Open Marks first" + add widget + BookmarkListRow polish
- Save serverConfig to App Group on every app launch so ShareExtension
  can always find credentials without requiring a disconnect/reconnect flow
- Add MarksWidget target with RandomBookmarkWidget (random saved bookmark,
  reloads hourly) using file-based App Group storage to avoid CFPreferences issues
- Extract BookmarkListRow as shared component with editorial typography tweaks
  (17pt semibold title, 13pt domain, relative date, 32×32 favicon, italic AI summary,
  20pt podcast icon, 16pt horizontal insets)
- Add MarksWidget and ShareExtension Xcode schemes for direct run/debug

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:11:05 -05:00

101 lines
3.2 KiB
Swift

import SwiftUI
private let defaultConfig = ServerConfig(
host: "linkding-production-f7e0.up.railway.app",
port: nil,
path: "",
token: "04c3388f543a6f4401ae41958b4a459a0125c4bf",
useHttps: true
)
@main
struct MarksApp: App {
@State private var serverConfig: ServerConfig = ServerConfig.load() ?? defaultConfig
var body: some Scene {
WindowGroup {
MainContainer(config: serverConfig) {
defaultConfig.save()
serverConfig = defaultConfig
}
.task { serverConfig.save() }
}
}
}
private struct IdentifiableURL: Identifiable {
let id = UUID()
let url: URL
}
// Holds the API + ViewModel so they survive re-renders
struct MainContainer: View {
let config: ServerConfig
let onDisconnect: () -> Void
@State private var viewModel: BookmarksViewModel
@State private var deepLinkBrowser: IdentifiableURL?
@State private var showDeepLinkPlayer = false
init(config: ServerConfig, onDisconnect: @escaping () -> Void) {
self.config = config
self.onDisconnect = onDisconnect
let api = LinkdingAPI(config: config)
_viewModel = State(wrappedValue: BookmarksViewModel(api: api, cacheKey: config.host))
}
var body: some View {
TabView {
Tab("Bookmarks", systemImage: "bookmark") {
BookmarksView(viewModel: viewModel, onDisconnect: onDisconnect)
}
Tab("Tags", systemImage: "tag") {
TagsView(viewModel: viewModel)
}
Tab(role: .search) {
SearchView(viewModel: viewModel)
}
}
.onOpenURL { url in
handleDeepLink(url)
}
.sheet(item: $deepLinkBrowser) { item in
BrowserView(url: item.url, title: item.url.host ?? "", claude: viewModel.claude, podcastPlayer: viewModel.podcastPlayer)
}
.sheet(isPresented: $showDeepLinkPlayer) {
PodcastPlayerView(
vm: viewModel.podcastPlayer,
articleUrl: viewModel.podcastPlayer.currentArticleUrl,
articleTitle: viewModel.podcastPlayer.currentArticleTitle,
claude: viewModel.claude,
stopOnDismiss: false
)
}
}
private func handleDeepLink(_ url: URL) {
guard url.scheme == "marks",
let comps = URLComponents(url: url, resolvingAgainstBaseURL: false),
let articleUrl = comps.queryItems?.first(where: { $0.name == "url" })?.value
else { return }
switch url.host {
case "bookmark":
if let browserURL = URL(string: articleUrl) {
deepLinkBrowser = IdentifiableURL(url: browserURL)
}
case "podcast":
let podcasts = WidgetDataStore.loadPodcasts()
let found = podcasts.first { $0.articleUrl == articleUrl }
viewModel.podcastPlayer.start(
articleUrl: articleUrl,
articleTitle: found?.title ?? "",
claude: viewModel.claude
)
showDeepLinkPlayer = true
default:
break
}
}
}