summaryrefslogtreecommitdiff
path: root/Sora/Views/Generic/GenericListView.swift
blob: 1460487e87602c038e6f28e90ae61ae093826ba7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import SwiftUI

struct GenericListView<T: Identifiable & Hashable & GenericItem>: View {
  @EnvironmentObject private var settings: SettingsManager
  @ObservedObject var manager: BooruManager
  @Binding var selectedTab: Int
  @State private var searchText: String = ""
  @State private var isShowingRemoveAllConfirmation = false
  @Binding var isPresented: 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] {
    guard !searchText.isEmpty else {
      return items
    }

    return items.filter { item in
      item.tags
        .joined(separator: " ")
        .lowercased()
        .contains(searchText.lowercased())
    }
  }

  var body: some View {
    NavigationStack {
      VStack {
        if items.isEmpty {
          ContentUnavailableView(
            emptyMessage,
            systemImage: emptyIcon,
            description: Text(emptyDescription)
          )
        } else {
          List {
            if filteredItems.isEmpty, !searchText.isEmpty {
              Text("No matching items found")
            }

            ForEach(
              filteredItems.sorted { $0.date > $1.date },
              id: \.id
            ) { item in
              itemButtonContent(item: item)
            }
            .onDelete(perform: removeAction)
          }
        }
      }
    }
    .navigationTitle(title)
    .searchable(text: $searchText)
    .toolbar {
      ToolbarItem {
        Button(action: {
          isShowingRemoveAllConfirmation = true
        }) {
          Label("Remove All", systemImage: "trash")
        }
      }
    }
    .confirmationDialog(
      removeAllMessage,
      isPresented: $isShowingRemoveAllConfirmation
    ) {
      Button(removeAllButtonText) {
        removeAllAction()
      }
    }
  }

  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")
      }

      Button(action: {
        settings.addBookmark(provider: settings.preferredBooru, tags: item.tags)
      }) {
        Label("Bookmark Tag\(item.tags.count == 1 ? "" : "s")", systemImage: "bookmark")
      }
    }
    #if os(macOS)
      .buttonStyle(.plain)
    #endif
  }
}