import SwiftUI /// Full-featured list row used by BookmarksView, TagBookmarksView, and SearchView. /// Owns podcast and episode-picker sheet state; parent owns BrowserView sheet. struct BookmarkListRow: View { /// Which visual language the row speaks. `.library` is the paper design /// used by the bookmarks screen; `.classic` is the system-styled row the /// Tags and Search screens still use, so redesigning one doesn't silently /// restyle the others. enum Style { case classic, library } let bookmark: Bookmark let viewModel: BookmarksViewModel var readingProgress: Double = 0 var style: Style = .classic let onOpen: () -> Void var onEdit: (() -> Void)? = nil @Environment(\.openURL) private var openURL @State private var showFullPlayer = false @State private var episodePickerBookmark: Bookmark? var body: some View { row .contentShape(Rectangle()) .onTapGesture { onOpen() } .listRowInsets(EdgeInsets(top: 0, leading: 18, bottom: 0, trailing: 18)) .listRowSeparator(.visible) .listRowSeparatorTint(style == .library ? Paper.rule.opacity(0.5) : nil) .listRowBackground(style == .library ? Paper.sheet : nil) // Delete is destructive and not undoable — require an explicit tap on the // revealed button rather than letting a single full swipe delete instantly. .swipeActions(edge: .trailing, allowsFullSwipe: false) { 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) if let onEdit { Button { onEdit() } label: { Label("Edit", systemImage: "pencil") } .tint(.blue) } } .contextMenu { bookmarkMenuItems( bookmark: bookmark, viewModel: viewModel, openURL: openURL, onOpen: onOpen, onEdit: onEdit, onPodcast: handlePodcast ) } .podcastSheets( viewModel: viewModel, showFullPlayer: $showFullPlayer, episodePicker: $episodePickerBookmark ) } @ViewBuilder private var row: some View { switch style { case .classic: BookmarkRow( bookmark: bookmark, readingProgress: readingProgress, onPodcast: handlePodcast ) case .library: LibraryListRow( bookmark: bookmark, readingProgress: readingProgress, onPodcast: handlePodcast ) } } private func handlePodcast() { launchPodcast( for: bookmark, viewModel: viewModel, showFullPlayer: $showFullPlayer, episodePicker: $episodePickerBookmark ) } }