diff --git a/Sources/PulseUI/Features/Console/ConsoleDataSource.swift b/Sources/PulseUI/Features/Console/ConsoleDataSource.swift index eb8a2f767..24632553a 100644 --- a/Sources/PulseUI/Features/Console/ConsoleDataSource.swift +++ b/Sources/PulseUI/Features/Console/ConsoleDataSource.swift @@ -18,9 +18,6 @@ protocol ConsoleDataSourceDelegate: AnyObject { } final class ConsoleDataSource: NSObject, NSFetchedResultsControllerDelegate { - private(set) var entities: [NSManagedObject] = [] - private(set) var sections: [NSFetchedResultsSectionInfo]? - weak var delegate: ConsoleDataSourceDelegate? /// - warning: Incompatible with the "group by" option. @@ -81,6 +78,7 @@ final class ConsoleDataSource: NSObject, NSFetchedResultsControllerDelegate { NSSortDescriptor(key: sortKey, ascending: options.order == .ascending) ].compactMap { $0 } request.fetchBatchSize = ConsoleDataSource.fetchBatchSize + request.relationshipKeyPathsForPrefetching = ["request"] controller = NSFetchedResultsController( fetchRequest: request, managedObjectContext: store.viewContext, @@ -113,27 +111,37 @@ final class ConsoleDataSource: NSObject, NSFetchedResultsControllerDelegate { func refresh() { try? controller.performFetch() - refreshEntities() delegate?.dataSourceDidRefresh(self) } - + + // MARK: Accessing Entities + + var numberOfObjects: Int { + controller.fetchedObjects?.count ?? 0 + } + + func object(at indexPath: IndexPath) -> NSManagedObject { + controller.object(at: indexPath) + } + + var entities: [NSManagedObject] { + controller.fetchedObjects ?? [] + } + + var sections: [NSFetchedResultsSectionInfo]? { + controller.sectionNameKeyPath == nil ? nil : controller.sections + } + // MARK: NSFetchedResultsControllerDelegate func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - refreshEntities() delegate?.dataSource(self, didUpdateWith: nil) } func controller(_ controller: NSFetchedResultsController, didChangeContentWith diff: CollectionDifference) { - refreshEntities() delegate?.dataSource(self, didUpdateWith: diff) } - private func refreshEntities() { - entities = controller.fetchedObjects ?? [] - sections = controller.sectionNameKeyPath == nil ? nil : controller.sections - } - // MARK: Predicate private func refreshPredicate() { diff --git a/Sources/PulseUI/Features/Console/Views/ConsoleEntityDetailsView.swift b/Sources/PulseUI/Features/Console/Views/ConsoleEntityDetailsView.swift index 2bde308a4..ea0e320c3 100644 --- a/Sources/PulseUI/Features/Console/Views/ConsoleEntityDetailsView.swift +++ b/Sources/PulseUI/Features/Console/Views/ConsoleEntityDetailsView.swift @@ -46,7 +46,7 @@ struct ConsoleEntityDetailsRouterView: View { } struct ButtonChangeContentModeLayout: View { - @SceneStorage("is-details-vertical") private var isVertical = false + @SceneStorage("scene-is-details-vertical") private var isVertical = AppSettings.shared.isVertical var body: some View { Button(action: { isVertical.toggle() }, label: { @@ -55,6 +55,9 @@ struct ButtonChangeContentModeLayout: View { }) .help(isVertical ? "Switch to Horizontal Layout" : "Switch to Vertical Layout") .buttonStyle(.plain) + .onChange(of: isVertical) { + AppSettings.shared.isVertical = $0 + } } } diff --git a/Sources/PulseUI/Features/Console/Views/ConsoleTaskCell.swift b/Sources/PulseUI/Features/Console/Views/ConsoleTaskCell.swift index fbb7f08fb..12bf13b4e 100644 --- a/Sources/PulseUI/Features/Console/Views/ConsoleTaskCell.swift +++ b/Sources/PulseUI/Features/Console/Views/ConsoleTaskCell.swift @@ -74,10 +74,33 @@ struct ConsoleTaskCell: View { } private var message: some View { - Text(task.url ?? "–") - .font(ConsoleConstants.fontBody) - .foregroundColor(.primary) - .lineLimit(settings.lineLimit) + VStack(spacing: 3) { + HStack { + Text(task.url ?? "–") + .font(ConsoleConstants.fontBody) + .foregroundColor(.primary) + .lineLimit(settings.lineLimit) + + Spacer() + } + + let headerValueMap = settings.displayHeaders.reduce(into: [String: String]()) { partialResult, header in + partialResult[header] = task.originalRequest?.headers[header] + } + + ForEach(headerValueMap.keys.sorted(), id: \.self) { key in + HStack { + Text(key) + .font(.caption) + .foregroundColor(.secondary) + + Text(headerValueMap[key] ?? "-") + .font(.callout) + .bold() + Spacer() + } + } + } } private var details: some View { diff --git a/Sources/PulseUI/Features/Settings/SettingsView-ios.swift b/Sources/PulseUI/Features/Settings/SettingsView-ios.swift index 48dab4fad..df8de3864 100644 --- a/Sources/PulseUI/Features/Settings/SettingsView-ios.swift +++ b/Sources/PulseUI/Features/Settings/SettingsView-ios.swift @@ -10,7 +10,7 @@ import UniformTypeIdentifiers public struct SettingsView: View { @ObservedObject var viewModel: SettingsViewModel - + @State private var newHeaderName = "" @EnvironmentObject private var settings: UserSettings public init(store: LoggerStore = .shared) { @@ -35,6 +35,28 @@ public struct SettingsView: View { RemoteLoggerSettingsView(viewModel: .shared) } } + + Section(header: Text("List headers"), footer: Text("These headers will be included in the list view")) { + ForEach(settings.displayHeaders, id: \.self) { + Text($0) + } + .onDelete { indices in + settings.displayHeaders.remove(atOffsets: indices) + } + HStack { + TextField("New Header", text: $newHeaderName) + Button(action: { + withAnimation { + settings.displayHeaders.append(newHeaderName) + newHeaderName = "" + } + }) { + Image(systemName: "plus.circle.fill") + .accessibilityLabel("Add header") + } + .disabled(newHeaderName.isEmpty) + } + } } } } diff --git a/Sources/PulseUI/Features/Settings/UserSettings.swift b/Sources/PulseUI/Features/Settings/UserSettings.swift index de031e65a..8c460bc59 100644 --- a/Sources/PulseUI/Features/Settings/UserSettings.swift +++ b/Sources/PulseUI/Features/Settings/UserSettings.swift @@ -17,4 +17,29 @@ final class UserSettings: ObservableObject { @AppStorage("sharing-output") var sharingOutput: ShareStoreOutput = .store + + @AppStorage("display-headers") + var displayHeaders: [String] = [] +} + +// MARK: - Array + RawREpresentable + +extension Array: RawRepresentable where Element: Codable { + public init?(rawValue: String) { + guard let data = rawValue.data(using: .utf8), + let result = try? JSONDecoder().decode([Element].self, from: data) + else { + return nil + } + self = result + } + + public var rawValue: String { + guard let data = try? JSONEncoder().encode(self), + let result = String(data: data, encoding: .utf8) + else { + return "[]" + } + return result + } } diff --git a/Sources/PulseUI/Views/ContextMenus.swift b/Sources/PulseUI/Views/ContextMenus.swift index bf0a61f24..0975a4bb1 100644 --- a/Sources/PulseUI/Views/ContextMenus.swift +++ b/Sources/PulseUI/Views/ContextMenus.swift @@ -80,9 +80,6 @@ enum ContextMenu { // NetworkTaskFilterMenu(task: task) // } // } -#if PULSE_STANDALONE_APP - StandaloneNetworkTaskContextMenu(task: task) -#endif if let message = task.message { Section { PinButton(viewModel: .init(message))