mirror of
https://github.com/kean/Pulse.git
synced 2026-05-30 21:07:33 +00:00
176 lines
6.1 KiB
Swift
176 lines
6.1 KiB
Swift
// The MIT License (MIT)
|
|
//
|
|
// Copyright (c) 2020-2026 Alexander Grebenyuk (github.com/kean).
|
|
|
|
#if os(iOS) || os(visionOS)
|
|
|
|
import SwiftUI
|
|
import CoreData
|
|
import Pulse
|
|
import Combine
|
|
|
|
@available(iOS 18, tvOS 18, macOS 15, watchOS 11, visionOS 1, *)
|
|
struct ConsoleListView: View {
|
|
var initialSearchText: String?
|
|
|
|
@EnvironmentObject var environment: ConsoleEnvironment
|
|
@EnvironmentObject var filters: ConsoleFiltersViewModel
|
|
|
|
var body: some View {
|
|
_ConsoleListView(environment: environment, filters: filters, initialSearchText: initialSearchText)
|
|
}
|
|
}
|
|
|
|
@available(iOS 18, tvOS 18, macOS 15, watchOS 11, visionOS 1, *)
|
|
private struct _ConsoleListView: View {
|
|
private let environment: ConsoleEnvironment
|
|
|
|
@StateObject private var listViewModel: ConsoleListViewModel
|
|
@StateObject private var searchBarViewModel: ConsoleSearchBarViewModel
|
|
@StateObject private var searchViewModel: ConsoleSearchViewModel
|
|
|
|
@Environment(\.store) private var store
|
|
@Environment(\.router) private var router
|
|
|
|
@State private var editMode: EditMode = .inactive
|
|
@State private var selection = Set<NSManagedObjectID>()
|
|
@State private var shareItems: ShareItems?
|
|
|
|
init(environment: ConsoleEnvironment, filters: ConsoleFiltersViewModel, initialSearchText: String? = nil) {
|
|
self.environment = environment
|
|
|
|
let listViewModel = ConsoleListViewModel(environment: environment, filters: filters)
|
|
let searchBarViewModel = ConsoleSearchBarViewModel()
|
|
if let initialSearchText {
|
|
searchBarViewModel.text = initialSearchText
|
|
}
|
|
let searchViewModel = ConsoleSearchViewModel(environment: environment, searchBar: searchBarViewModel)
|
|
searchViewModel.isSearching = initialSearchText != nil
|
|
|
|
_listViewModel = StateObject(wrappedValue: listViewModel)
|
|
_searchBarViewModel = StateObject(wrappedValue: searchBarViewModel)
|
|
_searchViewModel = StateObject(wrappedValue: searchViewModel)
|
|
}
|
|
|
|
var body: some View {
|
|
list
|
|
.environmentObject(listViewModel)
|
|
.environmentObject(searchViewModel)
|
|
.environmentObject(searchBarViewModel)
|
|
.onAppear { listViewModel.isViewVisible = true }
|
|
.onDisappear { listViewModel.isViewVisible = false }
|
|
}
|
|
|
|
private var list: some View {
|
|
List(selection: editMode.isEditing ? $selection : nil) {
|
|
ConsoleToolbarView()
|
|
.listRowSeparator(.hidden, edges: .all)
|
|
.listRowInsets(EdgeInsets(top: 12, leading: 16, bottom: 9, trailing: 16))
|
|
if searchViewModel.isSearching {
|
|
ConsoleSearchListContentView()
|
|
} else {
|
|
ConsoleListContentView()
|
|
}
|
|
}
|
|
.listStyle(.plain)
|
|
.listSectionSpacing(0)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.environment(\.defaultMinListRowHeight, 8)
|
|
.environment(\.editMode, $editMode)
|
|
.animation(.default, value: editMode)
|
|
.animation(.default, value: searchViewModel.isSearching)
|
|
.searchable(text: $searchBarViewModel.text, isPresented: $searchViewModel.isSearching)
|
|
.searchPresentationToolbarBehavior(.avoidHidingContent)
|
|
.textInputAutocapitalization(.never)
|
|
.onSubmit(of: .search, searchViewModel.onSubmitSearch)
|
|
.disableAutocorrection(true)
|
|
.toolbar { toolbar }
|
|
.onChange(of: editMode) {
|
|
if !$0.isEditing {
|
|
selection.removeAll()
|
|
}
|
|
}
|
|
.sheet(item: $searchViewModel.editingFilterState) { state in
|
|
ConsoleCustomFilterEditSheet(filter: state.filter, fieldGroups: state.filter.availableFieldGroups) {
|
|
searchViewModel.applyEditedFilter($0)
|
|
}
|
|
}
|
|
.sheet(item: $shareItems, content: ShareView.init)
|
|
}
|
|
|
|
// MARK: Toolbar
|
|
|
|
@ToolbarContentBuilder private var toolbar: some ToolbarContent {
|
|
ToolbarItem(placement: .principal) {
|
|
if editMode.isEditing {
|
|
Text(selectionTitle)
|
|
.font(.headline)
|
|
} else {
|
|
ConsoleNavigationTitleView()
|
|
}
|
|
}
|
|
if editMode.isEditing {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button("Done") {
|
|
editMode = .inactive
|
|
}
|
|
}
|
|
ToolbarItem(placement: .bottomBar) {
|
|
Spacer()
|
|
}
|
|
ToolbarItem(placement: .bottomBar) {
|
|
shareMenu
|
|
}
|
|
} else {
|
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
|
Button(action: { router.isShowingShareStore = true }) {
|
|
Image(systemName: "square.and.arrow.up")
|
|
}
|
|
ConsoleContextMenu(editMode: $editMode)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Selection
|
|
|
|
private var selectionTitle: String {
|
|
switch selection.count {
|
|
case 0: return "Select Items"
|
|
case 1: return "1 Selected"
|
|
default: return "\(selection.count) Selected"
|
|
}
|
|
}
|
|
|
|
private var shareMenu: some View {
|
|
Menu {
|
|
Button(action: { shareSelectedEntities(as: .pdf) }) {
|
|
Label("PDF", systemImage: "square.and.arrow.up")
|
|
}
|
|
|
|
Button(action: { shareSelectedEntities(as: .html) }) {
|
|
Label("HTML", systemImage: "square.and.arrow.up")
|
|
}
|
|
Button(action: { shareSelectedEntities(as: .plainText) }) {
|
|
Label("Text", systemImage: "square.and.arrow.up")
|
|
}
|
|
} label: {
|
|
Label("Share...", systemImage: "square.and.arrow.up")
|
|
}
|
|
.disabled(selection.isEmpty)
|
|
}
|
|
|
|
private func shareSelectedEntities(as output: ShareOutput) {
|
|
let entities = selection.compactMap { objectID in
|
|
listViewModel.entities.first(where: { $0.objectID == objectID })
|
|
}
|
|
guard !entities.isEmpty, let store = store as? LoggerStore else { return }
|
|
Task {
|
|
if let items = try? await ShareService.share(entities, store: store, as: output) {
|
|
shareItems = items
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|