import SharedModels import SwiftUI public struct FlaggedEquipmentMeasurementView: View { @Environment(\.flaggedEquipmentMeasurementStyle) private var style let measurement: EquipmentMeasurement.FlaggedMeasurement let ignoreIfZero: [EquipmentMeasurement.FlaggedMeasurement.FieldKey] public init( _ measurement: EquipmentMeasurement.FlaggedMeasurement, ignoreIfZero: [EquipmentMeasurement.FlaggedMeasurement.FieldKey] = [] ) { self.measurement = measurement self.ignoreIfZero = ignoreIfZero } public var body: some View { style.makeBody( configuration: FlaggedEquipmentMeasurementStyleConfiguration( measurement: measurement, ignoreIfZero: ignoreIfZero ) ) } } @MainActor public protocol FlaggedEquipmentMeasurementStyle { associatedtype Body: View typealias Configuration = FlaggedEquipmentMeasurementStyleConfiguration @ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body } public struct FlaggedEquipmentMeasurementStyleConfiguration { public let measurement: EquipmentMeasurement.FlaggedMeasurement public let ignoreIfZero: [EquipmentMeasurement.FlaggedMeasurement.FieldKey] } @MainActor @preconcurrency public struct AnyFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { private var _makeBody: (Configuration) -> AnyView internal init(makeBody: @escaping (Configuration) -> AnyView) { self._makeBody = makeBody } @MainActor public init(_ style: S) { self.init { configuration in AnyView(style.makeBody(configuration: configuration)) } } public func makeBody(configuration: Configuration) -> some View { _makeBody(configuration) } } @MainActor public struct GridFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { public func makeBody(configuration: Configuration) -> some View { Grid(alignment: .leading, verticalSpacing: 20) { ForEach(EquipmentMeasurement.FlaggedMeasurement.FieldKey.allCases) { field in if configuration.measurement[keyPath: field.flaggedKeyPath].wrappedValue == 0, configuration.ignoreIfZero.contains(field) { EmptyView() } else { FlaggedView( field.title, flagged: configuration.measurement[keyPath: field.flaggedKeyPath] ) .flaggedViewStyle(.gridRow(fractionLength: field == .airflow ? 0 : 2)) } } } } } extension FlaggedEquipmentMeasurementStyle where Self == GridFlaggedEquipmentMeasurementStyle { public static var grid: Self { .init() } } //fileprivate struct FlaggedEquipmentMeasurementStyleKey: @preconcurrency EnvironmentKey { // // static let defaultValue = AnyFlaggedEquipmentMeasurementStyle(.grid) //} extension EnvironmentValues { @Entry var flaggedEquipmentMeasurementStyle = MainActor.assumeIsolated { AnyFlaggedEquipmentMeasurementStyle(.grid) } } extension View { public func flaggedEquipmentMeasurementStyle(_ style: AnyFlaggedEquipmentMeasurementStyle) -> some View { environment(\.flaggedEquipmentMeasurementStyle, style) } public func flaggedEquipmentMeasurementStyle( _ style: S ) -> some View { flaggedEquipmentMeasurementStyle(AnyFlaggedEquipmentMeasurementStyle(style)) } } // MARK: - FieldKey extension EquipmentMeasurement.FlaggedMeasurement { // NOTE: These need to be kept in display order. public enum FieldKey: Hashable, CaseIterable, Identifiable { case returnPlenum case filterDrop case coilDrop case supplyPlenum case staticPressure case airflow public var id: Self { self } var title: String { switch self { case .returnPlenum: return "Return Plenum" case .filterDrop: return "Filter Pressure Drop" case .coilDrop: return "Coil Pressure Drop" case .supplyPlenum: return "Supply Plenum" case .staticPressure: return "External Static Pressure" case .airflow: return "System Airflow" } } var flaggedKeyPath: KeyPath { switch self { case .returnPlenum: return \.returnPlenumPressure case .filterDrop: return \.filterPressureDrop case .coilDrop: return \.coilPressureDrop case .supplyPlenum: return \.supplyPlenumPressure case .staticPressure: return \.externalStaticPressure case .airflow: return \.airflow } } } } #if DEBUG private let equipmentMeasurement = EquipmentMeasurement.airHandler(.init( airflow: 1600, manufacturersIncludedFilterPressureDrop: 0.1, returnPlenumPressure: 0.37, postFilterPressure: 0.78, postCoilPressure: 0.9, supplyPlenumPressure: 0.11 )) #Preview { NavigationStack { FlaggedEquipmentMeasurementView( .init( budgets: .init(equipmentType: .airHandler, fanType: .variableSpeed), measurement: equipmentMeasurement, ratedPressures: .init(), tons: .four ) ) .padding() } } #endif