import SwiftUI struct BookmarksView: View { @Bindable var viewModel: BookmarksViewModel let onDisconnect: () -> Void @Environment(\.openURL) private var openURL @State private var showSettings = false @State private var showCollections = false @State private var showAddBookmark = false @State private var editingBookmark: Bookmark? @State private var browsingBookmark: Bookmark? @State private var podcastBookmark: Bookmark? var body: some View { NavigationStack { List { ForEach(viewModel.bookmarks) { bookmark in BookmarkRow(bookmark: bookmark) .onTapGesture { browsingBookmark = bookmark } .onAppear { maybeLoadMore(bookmark) } .listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20)) .listRowSeparator(.visible) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button(role: .destructive) { Task { await viewModel.delete(bookmark) } } label: { Label("Delete", systemImage: "trash") } } .swipeActions(edge: .leading) { Button { Task { await viewModel.archive(bookmark) } } label: { Label("Archive", systemImage: "archivebox") } .tint(.orange) Button { editingBookmark = bookmark } label: { Label("Edit", systemImage: "pencil") } .tint(.blue) } .contextMenu { Button { editingBookmark = bookmark } label: { Label("Edit", systemImage: "pencil") } Button { browsingBookmark = bookmark } label: { Label("Open", systemImage: "globe") } Button { if let url = URL(string: bookmark.url) { openURL(url) } } label: { Label("Open in Safari", systemImage: "safari") } if viewModel.claude != nil { Button { podcastBookmark = bookmark } label: { Label("Convert to Podcast", systemImage: "headphones") } } Divider() Button { Task { await viewModel.archive(bookmark) } } label: { Label("Archive", systemImage: "archivebox") } Button(role: .destructive) { Task { await viewModel.delete(bookmark) } } label: { Label("Delete", systemImage: "trash") } } } if viewModel.isLoadingMore { HStack { Spacer(); ProgressView(); Spacer() } .listRowSeparator(.hidden) } } .listStyle(.plain) .navigationTitle(viewModel.unreadFilter ? "Unread" : "Bookmarks") .navigationBarTitleDisplayMode(.large) .toolbar { ToolbarItem(placement: .topBarLeading) { if viewModel.claude != nil { Menu { Button { Task { await viewModel.generateSmartCollections() } showCollections = true } label: { Label("Smart Collections", systemImage: "sparkles") } Button { Task { await viewModel.enrichAll() } } label: { Label("Enrich All with AI", systemImage: "wand.and.stars") } } label: { Image(systemName: "sparkles") } } } ToolbarItem(placement: .topBarTrailing) { HStack(spacing: 16) { Button { showAddBookmark = true } label: { Image(systemName: "plus") } Button { Task { await viewModel.toggleUnreadFilter() } } label: { Image(systemName: viewModel.unreadFilter ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle") } Button { showSettings = true } label: { Image(systemName: "gearshape") } } } } .overlay { if viewModel.isLoading && viewModel.bookmarks.isEmpty { ProgressView() } if !viewModel.isLoading && viewModel.bookmarks.isEmpty { ContentUnavailableView("No Bookmarks", systemImage: "bookmark", description: Text("Bookmarks you save will appear here.")) } } .overlay(alignment: .bottom) { if viewModel.enrichmentProgress > 0 { enrichmentBanner } } } .sheet(isPresented: $showSettings) { SettingsView(viewModel: viewModel, onDisconnect: onDisconnect) } .sheet(isPresented: $showCollections) { CollectionsView(viewModel: viewModel) } .sheet(isPresented: $showAddBookmark) { AddBookmarkView(viewModel: viewModel) } .sheet(item: $editingBookmark) { bookmark in EditBookmarkView(viewModel: viewModel, bookmark: bookmark) } .sheet(item: $browsingBookmark) { bookmark in if let url = URL(string: bookmark.url) { BrowserView(url: url, title: bookmark.displayTitle, claude: viewModel.claude) } } .sheet(item: $podcastBookmark) { bookmark in if let claude = viewModel.claude { PodcastPlayerView(articleUrl: bookmark.url, claude: claude) } } .refreshable { await viewModel.load() } .task { if viewModel.bookmarks.isEmpty { await viewModel.load() } } .alert("Error", isPresented: Binding( get: { viewModel.error != nil }, set: { if !$0 { viewModel.error = nil } } )) { Button("OK") { viewModel.error = nil } } message: { Text(viewModel.error ?? "") } } private var enrichmentBanner: some View { HStack(spacing: 10) { ProgressView(value: viewModel.enrichmentProgress) .progressViewStyle(.linear) .tint(.primary) Text("Adding AI summaries…") .font(.system(size: 13)) .foregroundStyle(.secondary) } .padding(.horizontal, 20) .padding(.vertical, 12) .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 12)) .padding(.horizontal, 16) .padding(.bottom, 8) } private func maybeLoadMore(_ bookmark: Bookmark) { guard let last = viewModel.bookmarks.last, last.id == bookmark.id, viewModel.nextPageUrl != nil, !viewModel.isLoadingMore else { return } Task { await viewModel.loadMore() } } }