From ae7b4134099eadd6da83896eb5be31c9ff6147e6 Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Wed, 5 Jun 2024 11:00:23 -0400 Subject: [PATCH] Feat: Adds flagged equipment measurement view and style --- .../SharedModels/EquipmentMeasurement.swift | 62 ++++++--- .../FlaggedEquipmentMeasurement.swift | 55 ++------ .../FlaggedEquipmentMeasurementView.swift | 26 ++++ Sources/Styleguide/FlaggedView.swift | 6 +- .../FlaggedEquipmentMeasurementStyle.swift | 123 ++++++++++++++++++ .../Styleguide/Styles/FlaggedViewStyle.swift | 39 +++++- 6 files changed, 240 insertions(+), 71 deletions(-) create mode 100644 Sources/Styleguide/FlaggedEquipmentMeasurementView.swift create mode 100644 Sources/Styleguide/Styles/FlaggedEquipmentMeasurementStyle.swift diff --git a/Sources/SharedModels/EquipmentMeasurement.swift b/Sources/SharedModels/EquipmentMeasurement.swift index 5f6d42d..6e6f20e 100644 --- a/Sources/SharedModels/EquipmentMeasurement.swift +++ b/Sources/SharedModels/EquipmentMeasurement.swift @@ -97,24 +97,50 @@ public enum EquipmentMeasurement: Equatable { } } -extension EquipmentMeasurement.AirHandler { - - public enum Key: String, Equatable, CaseIterable { - case returnPlenumPressure - case postFilterPressure - case postCoilPressure - case supplyPlenumPressure - case airflow - } -} +//extension EquipmentMeasurement.AirHandler { +// +// public enum Key: String, Equatable, CaseIterable { +// case returnPlenumPressure +// case postFilterPressure +// case postCoilPressure +// case supplyPlenumPressure +// case airflow +// } +//} +// +//extension EquipmentMeasurement.FurnaceAndCoil { +// +// public enum Key: String, Equatable, CaseIterable { +// case returnPlenumPressure +// case postFilterPressure +// case preCoilPressure +// case supplyPlenumPressure +// case airflow +// } +//} -extension EquipmentMeasurement.FurnaceAndCoil { - - public enum Key: String, Equatable, CaseIterable { - case returnPlenumPressure - case postFilterPressure - case preCoilPressure - case supplyPlenumPressure - case airflow +#if DEBUG +extension EquipmentMeasurement { + public static func mock(type equipmentType: EquipmentType) -> Self { + switch equipmentType { + case .airHandler: + return .airHandler(.init( + airflow: 1200, + returnPlenumPressure: 0.3, + postFilterPressure: 0.6, + postCoilPressure: 0.9, + supplyPlenumPressure: 0.2 + )) + + case .furnaceAndCoil: + return .furnaceAndCoil(.init( + airflow: 1200, + returnPlenumPressure: 0.3, + postFilterPressure: 0.6, + preCoilPressure: 0.4, + supplyPlenumPressure: 0.1 + )) + } } } +#endif diff --git a/Sources/SharedModels/FlaggedEquipmentMeasurement.swift b/Sources/SharedModels/FlaggedEquipmentMeasurement.swift index fac2558..353fd4e 100644 --- a/Sources/SharedModels/FlaggedEquipmentMeasurement.swift +++ b/Sources/SharedModels/FlaggedEquipmentMeasurement.swift @@ -120,54 +120,19 @@ public struct FlaggedEquipmentMeasurement: Equatable { } } -// MARK: - Key +// MARK: - Helpers +#if DEBUG extension FlaggedEquipmentMeasurement { - // NOTE: These need to be kept in display order. - public enum Key: Equatable, CaseIterable { - case returnPlenum - case filterDrop - case coilDrop - case supplyPlenum - case staticPressure - case airflow - - public 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" - } - } - - public 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 - } - } + public static func mock(type equipmentType: EquipmentType) -> Self { + .init( + budgets: .init(equipmentType: equipmentType, fanType: .variableSpeed), + measurement: .mock(type: equipmentType), + ratedPressures: .init(), + tons: .default + ) } } - -// MARK: - Helpers +#endif fileprivate extension Flagged { init( diff --git a/Sources/Styleguide/FlaggedEquipmentMeasurementView.swift b/Sources/Styleguide/FlaggedEquipmentMeasurementView.swift new file mode 100644 index 0000000..d22a1e5 --- /dev/null +++ b/Sources/Styleguide/FlaggedEquipmentMeasurementView.swift @@ -0,0 +1,26 @@ +import SharedModels +import SwiftUI + +public struct FlaggedEquipmentMeasurementView: View { + + @Environment(\.flaggedEquipmentMeasurementStyle) private var style + + let measurement: FlaggedEquipmentMeasurement + + public init(_ measurement: FlaggedEquipmentMeasurement) { + self.measurement = measurement + } + + public var body: some View { + style.makeBody( + configuration: FlaggedEquipmentMeasurementStyleConfiguration( + measurement: measurement + ) + ) + } +} + +#Preview { + FlaggedEquipmentMeasurementView(.mock(type: .airHandler)) + .padding() +} diff --git a/Sources/Styleguide/FlaggedView.swift b/Sources/Styleguide/FlaggedView.swift index 02b4c7e..73c6fa4 100644 --- a/Sources/Styleguide/FlaggedView.swift +++ b/Sources/Styleguide/FlaggedView.swift @@ -18,7 +18,7 @@ public struct FlaggedView: View { public var body: some View { flaggedViewStyle.makeBody( - configuration: .init(flagged: flagged, label: .init(label())) + configuration: FlaggedViewStyleConfiguration(flagged: flagged, label: .init(label())) ) } } @@ -27,6 +27,10 @@ extension FlaggedView where Label == Text { public init(_ title: LocalizedStringKey, flagged: Flagged) { self.init(flagged: flagged) { Text(title) } } + + public init(_ title: S, flagged: Flagged) { + self.init(flagged: flagged) { Text(title) } + } } extension FlaggedView where Label == EmptyView { diff --git a/Sources/Styleguide/Styles/FlaggedEquipmentMeasurementStyle.swift b/Sources/Styleguide/Styles/FlaggedEquipmentMeasurementStyle.swift new file mode 100644 index 0000000..33a2b61 --- /dev/null +++ b/Sources/Styleguide/Styles/FlaggedEquipmentMeasurementStyle.swift @@ -0,0 +1,123 @@ +import SharedModels +import SwiftUI + +public protocol FlaggedEquipmentMeasurementStyle { + associatedtype Body: View + typealias Configuration = FlaggedEquipmentMeasurementStyleConfiguration + + @ViewBuilder + func makeBody(configuration: Self.Configuration) -> Self.Body +} + +public struct FlaggedEquipmentMeasurementStyleConfiguration { + public let measurement: FlaggedEquipmentMeasurement +} + +public struct AnyFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { + private var _makeBody: (Configuration) -> AnyView + + internal init(makeBody: @escaping (Configuration) -> AnyView) { + self._makeBody = makeBody + } + + public init(_ style: S) { + self.init { configuration in + AnyView(style.makeBody(configuration: configuration)) + } + } + + public func makeBody(configuration: Configuration) -> some View { + _makeBody(configuration) + } +} + +public struct GridFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { + + public func makeBody(configuration: Configuration) -> some View { + Grid(alignment: .leading, verticalSpacing: 20) { + ForEach(FlaggedEquipmentMeasurement.Key.allCases) { field in + 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() } +} + +private struct FlaggedEquipmentMeasurementStyleKey: EnvironmentKey { + static let defaultValue = AnyFlaggedEquipmentMeasurementStyle(GridFlaggedEquipmentMeasurementStyle()) +} + +extension EnvironmentValues { + public var flaggedEquipmentMeasurementStyle: AnyFlaggedEquipmentMeasurementStyle { + get { self[FlaggedEquipmentMeasurementStyleKey.self] } + set { self[FlaggedEquipmentMeasurementStyleKey.self] = newValue } + } +} + +extension View { + public func flaggedEquipmentMeasurementStyle(_ style: AnyFlaggedEquipmentMeasurementStyle) -> some View { + environment(\.flaggedEquipmentMeasurementStyle, style) + } + + public func flaggedEquipmentMeasurementStyle( + _ style: S + ) -> some View { + flaggedEquipmentMeasurementStyle(AnyFlaggedEquipmentMeasurementStyle(style)) + } +} + +// MARK: - Key +fileprivate extension FlaggedEquipmentMeasurement { + // NOTE: These need to be kept in display order. + enum Key: Hashable, CaseIterable, Identifiable { + case returnPlenum + case filterDrop + case coilDrop + case supplyPlenum + case staticPressure + case airflow + + 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 + } + } + } +} diff --git a/Sources/Styleguide/Styles/FlaggedViewStyle.swift b/Sources/Styleguide/Styles/FlaggedViewStyle.swift index f7c1ffe..e27da3b 100644 --- a/Sources/Styleguide/Styles/FlaggedViewStyle.swift +++ b/Sources/Styleguide/Styles/FlaggedViewStyle.swift @@ -71,7 +71,6 @@ public struct DefaultFlagViewStyle: FlaggedViewStyle { flaggedMessageView(flagged: configuration.flagged) } } - } public struct FlagAndMessageOnlyStyle: FlaggedViewStyle { @@ -106,6 +105,33 @@ extension FlaggedViewStyle where Self == FlagAndMessageOnlyStyle { } } +public struct FlaggedGridRowStyle: FlaggedViewStyle { + + let fractionLength: Int + + public func makeBody(configuration: Configuration) -> some View { + GridRow { + VStack(alignment: .leading, spacing: 8) { + configuration.label + .foregroundStyle(Color.secondary) + flaggedMessageView(flagged: configuration.flagged) + } + Text( + configuration.flagged.wrappedValue, + format: .number.precision(.fractionLength(fractionLength)) + ) + configuration.flagged.flagImage + .gridCellAnchor(.trailing) + } + } +} + +extension FlaggedViewStyle where Self == FlaggedGridRowStyle { + public static func gridRow(fractionLength: Int = 2) -> Self { + .init(fractionLength: fractionLength) + } +} + private struct FlaggedViewStyleKey: EnvironmentKey { static let defaultValue = AnyFlaggedViewStyle(style: DefaultFlagViewStyle()) } @@ -123,10 +149,9 @@ extension FlaggedViewStyle where Self == DefaultFlagViewStyle { } } +fileprivate extension Flagged.CheckResult.Key { -extension Flagged.CheckResult.Key { - - public var flagColor: Color { + var flagColor: Color { switch self { case .good: return .green @@ -140,7 +165,7 @@ extension Flagged.CheckResult.Key { extension Flagged { - public var flagColor: Color { projectedValue.key.flagColor } + var flagColor: Color { projectedValue.key.flagColor } public var flagImage: some View { Image(systemName: "flag.fill") @@ -151,7 +176,7 @@ extension Flagged { } @ViewBuilder -private func flaggedMessageView(flagged: Flagged) -> some View { +fileprivate func flaggedMessageView(flagged: Flagged) -> some View { if let message = flagged.message { HStack { Text(flagged.projectedValue.key.title) @@ -171,6 +196,6 @@ extension View { public func flaggedViewStyle( _ style: S ) -> some View { - environment(\.flaggedViewStyle, AnyFlaggedViewStyle(style: style)) + flaggedViewStyle(AnyFlaggedViewStyle(style: style)) } }