Files
linkding-ios/Marks/Views/BookmarkListRow.swift
Krishna Kumar a5cd9b1d03
All checks were successful
CI / build-and-deploy (push) Successful in 34s
fix(BookmarkListRow): prevent swipe actions from triggering URL open
Replace Button wrapper with onTapGesture so swipe gestures don't compete
with the row tap recognizer and accidentally open bookmarks.

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

111 lines
3.8 KiB
Swift

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 {
let bookmark: Bookmark
let viewModel: BookmarksViewModel
var readingProgress: Double = 0
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 {
BookmarkRow(
bookmark: bookmark,
readingProgress: readingProgress,
onPodcast: handlePodcast
)
.contentShape(Rectangle())
.onTapGesture { onOpen() }
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
.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)
if let onEdit {
Button { onEdit() } label: {
Label("Edit", systemImage: "pencil")
}
.tint(.blue)
}
}
.contextMenu {
if let onEdit {
Button { onEdit() } label: {
Label("Edit", systemImage: "pencil")
}
}
Button { onOpen() } label: {
Label("Open", systemImage: "globe")
}
Button {
if let url = URL(string: bookmark.url) { openURL(url) }
} label: {
Label("Open in Safari", systemImage: "safari")
}
Button { handlePodcast() } 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")
}
}
.sheet(isPresented: $showFullPlayer) {
PodcastPlayerView(
vm: viewModel.podcastPlayer,
articleUrl: viewModel.podcastPlayer.currentArticleUrl,
articleTitle: viewModel.podcastPlayer.currentArticleTitle,
claude: viewModel.claude,
stopOnDismiss: false
)
}
.sheet(item: $episodePickerBookmark) { b in
EpisodePickerView(bookmark: b, vm: viewModel.podcastPlayer, claude: viewModel.claude)
}
}
private func handlePodcast() {
let episodes = PodcastIndex.find(for: bookmark.url)
if episodes.count >= 2 {
episodePickerBookmark = bookmark
} else if let ep = episodes.first {
viewModel.podcastPlayer.start(
articleUrl: ep.articleUrl,
articleTitle: ep.title ?? bookmark.displayTitle,
claude: viewModel.claude
)
showFullPlayer = true
} else {
viewModel.podcastPlayer.start(
articleUrl: bookmark.url,
articleTitle: bookmark.displayTitle,
claude: viewModel.claude
)
showFullPlayer = true
}
}
}