import SwiftUI /// The library's card presentation of `viewModel.bookmarks`. Drops into /// BookmarksView's content area in place of the List and carries the same /// actions — tap to open, long press for the full menu. The filter strip above /// it belongs to BookmarksView, since both layouts share it. struct LibraryGridView: View { @Bindable var viewModel: BookmarksViewModel let onOpen: (Bookmark) -> Void let onEdit: (Bookmark) -> Void @Environment(\.openURL) private var openURL @State private var showFullPlayer = false @State private var episodePicker: Bookmark? private var items: [LibraryItem] { viewModel.bookmarks.map(LibraryItem.init(bookmark:)) } var body: some View { ScrollView { LazyVGrid( // Two columns, not three: at the scaled-up type a third // column leaves ~9 characters per line and every title // truncates. The text size sets the column count. columns: Array(repeating: GridItem(.flexible(), spacing: 10), count: 2), spacing: 10 ) { ForEach(items) { item in card(item) } } .padding(.horizontal, 18) .padding(.top, 16) .padding(.bottom, 40) if viewModel.isLoadingMore { ProgressView().padding(.bottom, 28) } } .scrollBounceBehavior(.basedOnSize) .background(Paper.sheet) .podcastSheets( viewModel: viewModel, showFullPlayer: $showFullPlayer, episodePicker: $episodePicker ) } @ViewBuilder private func card(_ item: LibraryItem) -> some View { // The grid renders LibraryItems, but every action needs the Bookmark it // came from. Ids are linkding's, so this is a direct lookup. if let bookmark = viewModel.bookmarks.first(where: { $0.id == item.id }) { Button { onOpen(bookmark) } label: { LibraryCard(item: item) } .buttonStyle(RowPressStyle()) .contextMenu { bookmarkMenuItems( bookmark: bookmark, viewModel: viewModel, openURL: openURL, onOpen: { onOpen(bookmark) }, onEdit: { onEdit(bookmark) }, onPodcast: { launchPodcast( for: bookmark, viewModel: viewModel, showFullPlayer: $showFullPlayer, episodePicker: $episodePicker ) } ) } .onAppear { maybeLoadMore(bookmark) } } } 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() } } }