import SwiftUI struct GenericListView: View { @EnvironmentObject private var settings: SettingsManager @EnvironmentObject private var manager: BooruManager @Binding var selectedTab: Int @State private var searchText: String = "" @State private var isShowingRemoveAllConfirmation = false @Binding var isPresented: Bool @State private var isNewCollectionAlertPresented = false @State private var newCollectionName = "" @State private var itemPendingCollectionAssignment: UUID? @State private var isCollectionErrorAlertPresented = false @State private var selectedFolder: UUID? let allowBookmarking: Bool let title: String let emptyMessage: String let emptyIcon: String let emptyDescription: String let removeAllMessage: String let removeAllButtonText: String let items: [T] let removeAction: (IndexSet) -> Void let removeActionUUID: (UUID) -> Void let removeAllAction: () -> Void var filteredItems: [T] { items.filter { item in let matchesFolder: Bool = { if selectedFolder == nil { return true } if selectedFolder == uncategorisedUUID { return item.folder == nil } return item.folder == selectedFolder }() let matchesSearch = searchText.isEmpty || item.tags .joined(separator: " ") .lowercased() .contains(searchText.lowercased()) return matchesFolder && matchesSearch } } private let uncategorisedUUID = UUID(uuidString: "00000000-0000-0000-0000-000000000000")! var body: some View { NavigationStack { VStack(spacing: 0) { if items.isEmpty { ContentUnavailableView( emptyMessage, systemImage: emptyIcon, description: Text(emptyDescription) ) } else { if !settings.folders.isEmpty && !allowBookmarking { Picker("Collection", selection: $selectedFolder) { Text("All").tag(nil as UUID?) if items.contains(where: { $0.folder == nil }) { Text("Uncategorised").tag(uncategorisedUUID as UUID?) } ForEach(settings.folders, id: \.id) { folder in Text(folder.name).tag(folder.id) } } .pickerStyle(.menu) #if os(macOS) .padding() #else .padding(.horizontal) #endif } List { if filteredItems.isEmpty && (!searchText.isEmpty || !(selectedFolder == nil)) { Text("No matching items found") } ForEach( filteredItems.sorted { $0.date > $1.date }, id: \.id ) { item in itemButtonContent(item: item) } .onDelete(perform: removeAction) } .searchable(text: $searchText) } } } .navigationTitle(title) .refreshable { settings.syncFromCloud() } .toolbar { #if os(macOS) ToolbarItem { Button(action: { settings.syncFromCloud() }) { Label("Refresh", systemImage: "arrow.clockwise") } } #endif ToolbarItem { Button(action: { isShowingRemoveAllConfirmation = true }) { Label("Remove All", systemImage: "trash") } } } .confirmationDialog( removeAllMessage, isPresented: $isShowingRemoveAllConfirmation ) { Button(removeAllButtonText) { removeAllAction() } } .alert( "New Collection", isPresented: $isNewCollectionAlertPresented ) { TextField("Collection Name", text: $newCollectionName) Button("Cancel") { newCollectionName = "" isNewCollectionAlertPresented = false } Button("Create") { if newCollectionName.isEmpty { isCollectionErrorAlertPresented = true } else { let newFolder = SettingsFolder(name: newCollectionName) settings.folders.append(newFolder) if let id = itemPendingCollectionAssignment { settings.updateBookmarkFolder(withID: id, folder: newFolder.id) } itemPendingCollectionAssignment = nil newCollectionName = "" isNewCollectionAlertPresented = false } } } .alert( "Error", isPresented: $isCollectionErrorAlertPresented, ) { Button("OK", role: .cancel) { () } } message: { Text("Collection name cannot be empty.") } } func itemButtonContent(item: T) -> some View { Button(action: { let previousProvider = settings.preferredBooru settings.preferredBooru = item.provider manager.searchText = item.tags.joined(separator: " ") selectedTab = 0 if previousProvider == settings.preferredBooru { manager.performSearch(settings: settings) } isPresented.toggle() }) { GenericItemView( item: item, removeAction: removeActionUUID ) } .contextMenu { Button(action: { manager.searchText += " \(item.tags.joined(separator: " "))" manager.selectedPost = nil manager.performSearch(settings: settings) isPresented.toggle() }) { Label("Add to Search", systemImage: "plus") } Menu("\(item.folder != nil ? "Move" : "Add") to Collection") { ForEach(settings.folders, id: \.id) { folder in if item.folder != folder.id { Button(action: { settings.updateBookmarkFolder(withID: item.id, folder: folder.id) }) { Label(folder.name, systemImage: "folder") } } } Button(action: { itemPendingCollectionAssignment = item.id isNewCollectionAlertPresented = true }) { Label("New Collection", systemImage: "plus") } } if item.folder != nil { Button(action: { settings.updateBookmarkFolder(withID: item.id, folder: nil) }) { Label("Remove from Collection", systemImage: "folder.badge.minus") } } if allowBookmarking { let isBookmarked = settings.bookmarks.contains { $0.tags == item.tags } Button(action: { if isBookmarked { settings.removeBookmark(withTags: item.tags) } else { settings.addBookmark(provider: settings.preferredBooru, tags: item.tags) } }) { if isBookmarked { Label("Unbookmark Tag\(item.tags.count == 1 ? "" : "s")", systemImage: "bookmark.fill") } else { Label("Bookmark Tag\(item.tags.count == 1 ? "" : "s")", systemImage: "bookmark") } } } Button { removeActionUUID(item.id) } label: { Label("Remove", systemImage: "trash") } } #if os(macOS) .buttonStyle(.plain) #endif } }