feat: Reverts static pressure.
This commit is contained in:
@@ -5,7 +5,6 @@ import SharedModels
|
||||
import Styleguide
|
||||
import SwiftUI
|
||||
|
||||
#warning("Revert rated static pressures section.")
|
||||
@Reducer
|
||||
public struct EquipmentSettingsForm {
|
||||
|
||||
@@ -24,56 +23,50 @@ public struct EquipmentSettingsForm {
|
||||
@ObservableState
|
||||
public struct State: Equatable {
|
||||
@Presents public var destination: Destination.State?
|
||||
public var includesFilterDrop: Bool
|
||||
public var equipmentType: EquipmentMeasurement.EquipmentType
|
||||
public var focusedField: Field? = nil
|
||||
@Shared public var sharedSettings: SharedPressureEstimationState
|
||||
public var ratedStaticPressures: RatedStaticPressuresSection.State
|
||||
public var maxStaticPressure: Double?
|
||||
public var minStaticPressure: Double?
|
||||
public var ratedStaticPressure: Double?
|
||||
|
||||
public init(
|
||||
destination: Destination.State? = nil,
|
||||
includesFilterDrop: Bool = false,
|
||||
equipmentType: EquipmentMeasurement.EquipmentType = .airHandler,
|
||||
sharedSettings: Shared<SharedPressureEstimationState>
|
||||
) {
|
||||
self.destination = destination
|
||||
self.includesFilterDrop = includesFilterDrop
|
||||
self.equipmentType = equipmentType
|
||||
self._sharedSettings = sharedSettings
|
||||
self.ratedStaticPressures = .init(staticPressures: sharedSettings.equipmentMetadata.ratedStaticPressures)
|
||||
self.maxStaticPressure = sharedSettings.ratedStaticPressures.maximum.wrappedValue
|
||||
self.minStaticPressure = sharedSettings.ratedStaticPressures.minimum.wrappedValue
|
||||
self.ratedStaticPressure = sharedSettings.ratedStaticPressures.rated.wrappedValue
|
||||
}
|
||||
|
||||
private var equipmentTypeValidation: Bool {
|
||||
guard equipmentType == .furnaceAndCoil else { return true }
|
||||
return sharedSettings.heatingCapacity != nil
|
||||
}
|
||||
|
||||
|
||||
private var filterDropValidation: Bool {
|
||||
guard includesFilterDrop else { return true }
|
||||
return sharedSettings.manufacturersIncludedFilterPressureDrop != nil
|
||||
private var staticPressuresValidation: Bool {
|
||||
maxStaticPressure != nil
|
||||
&& minStaticPressure != nil
|
||||
&& ratedStaticPressure != nil
|
||||
}
|
||||
|
||||
public var isValid: Bool {
|
||||
ratedStaticPressures.isValid
|
||||
&& equipmentTypeValidation
|
||||
&& filterDropValidation
|
||||
equipmentTypeValidation
|
||||
&& staticPressuresValidation
|
||||
}
|
||||
|
||||
// Note: These need to be in display order.
|
||||
public enum Field: Hashable, CaseIterable, FocusableField, Identifiable {
|
||||
case heatingCapacity
|
||||
case ratedStaticPressure(RatedStaticPressuresSection.State.FocusedField)
|
||||
case maxStaticPressure
|
||||
case minStaticPressure
|
||||
case ratedStaticPressure
|
||||
case manufacturersIncludedFilterPressureDrop
|
||||
|
||||
public static var allCases: [EquipmentSettingsForm.State.Field] {
|
||||
[
|
||||
.heatingCapacity,
|
||||
.ratedStaticPressure(.maximum),
|
||||
.ratedStaticPressure(.minimum),
|
||||
.ratedStaticPressure(.rated),
|
||||
.manufacturersIncludedFilterPressureDrop
|
||||
]
|
||||
}
|
||||
|
||||
public var id: Self { self }
|
||||
}
|
||||
@@ -82,7 +75,6 @@ public struct EquipmentSettingsForm {
|
||||
public enum Action: BindableAction, ViewAction {
|
||||
case binding(BindingAction<State>)
|
||||
case destination(PresentationAction<Destination.Action>)
|
||||
case ratedStaticPressures(RatedStaticPressuresSection.Action)
|
||||
case view(View)
|
||||
|
||||
@CasePathable
|
||||
@@ -95,21 +87,19 @@ public struct EquipmentSettingsForm {
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
BindingReducer()
|
||||
Scope(state: \.ratedStaticPressures, action: \.ratedStaticPressures) {
|
||||
RatedStaticPressuresSection()
|
||||
}
|
||||
Reduce<State, Action> { state, action in
|
||||
switch action {
|
||||
|
||||
case .binding(\.includesFilterDrop):
|
||||
guard state.includesFilterDrop else {
|
||||
state.sharedSettings.manufacturersIncludedFilterPressureDrop = nil
|
||||
return .none
|
||||
}
|
||||
guard state.sharedSettings.manufacturersIncludedFilterPressureDrop != nil else {
|
||||
return .none
|
||||
}
|
||||
state.sharedSettings.manufacturersIncludedFilterPressureDrop = 0.1
|
||||
case .binding(\.maxStaticPressure):
|
||||
handleStaticPressure(\.maximum, \.maxStaticPressure, &state)
|
||||
return .none
|
||||
|
||||
case .binding(\.minStaticPressure):
|
||||
handleStaticPressure(\.minimum, \.minStaticPressure, &state)
|
||||
return .none
|
||||
|
||||
case .binding(\.ratedStaticPressure):
|
||||
handleStaticPressure(\.rated, \.ratedStaticPressure, &state)
|
||||
return .none
|
||||
|
||||
case .binding:
|
||||
@@ -122,17 +112,9 @@ public struct EquipmentSettingsForm {
|
||||
case .destination:
|
||||
return .none
|
||||
|
||||
|
||||
case .ratedStaticPressures(.delegate(.infoButtonTapped)):
|
||||
state.destination = .infoView(.init(view: .ratedStaticPressures))
|
||||
return .none
|
||||
|
||||
case .ratedStaticPressures:
|
||||
return .none
|
||||
|
||||
case let .view(action):
|
||||
switch action {
|
||||
|
||||
|
||||
case let .infoButtonTapped(infoView):
|
||||
state.destination = .infoView(.init(view: infoView))
|
||||
return .none
|
||||
@@ -150,6 +132,17 @@ public struct EquipmentSettingsForm {
|
||||
}
|
||||
.ifLet(\.$destination, action: \.destination)
|
||||
}
|
||||
|
||||
private func handleStaticPressure(
|
||||
_ staticKeyPath: WritableKeyPath<RatedStaticPressures, Double>,
|
||||
_ stateKeyPath: KeyPath<State, Double?>,
|
||||
_ state: inout State
|
||||
) {
|
||||
let value = state[keyPath: stateKeyPath]
|
||||
state.sharedSettings.equipmentMetadata.ratedStaticPressures[keyPath: staticKeyPath] = value ?? 0
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ViewAction(for: EquipmentSettingsForm.self)
|
||||
@@ -219,30 +212,68 @@ public struct EquipmentSettingsFormView: View {
|
||||
}
|
||||
}
|
||||
|
||||
RatedStaticPressuresSectionView(
|
||||
store: store.scope(state: \.ratedStaticPressures, action: \.ratedStaticPressures)
|
||||
)
|
||||
Section {
|
||||
Grid(alignment: .leading, horizontalSpacing: 40) {
|
||||
GridRow {
|
||||
TextLabel("Maximum")
|
||||
TextField(
|
||||
"Maximum",
|
||||
value: $store.maxStaticPressure,
|
||||
fractionLength: 2,
|
||||
prompt: Text("Maximum Pressure")
|
||||
)
|
||||
.decimalPad()
|
||||
.focused($focusedField, equals: .maxStaticPressure)
|
||||
}
|
||||
GridRow {
|
||||
TextLabel("Minimum")
|
||||
TextField(
|
||||
"Minimum",
|
||||
value: $store.minStaticPressure,
|
||||
fractionLength: 2,
|
||||
prompt: Text("Minimum Pressure")
|
||||
)
|
||||
.decimalPad()
|
||||
.focused($focusedField, equals: .minStaticPressure)
|
||||
}
|
||||
GridRow {
|
||||
TextLabel("Rated")
|
||||
TextField(
|
||||
"Rated",
|
||||
value: $store.ratedStaticPressure,
|
||||
fractionLength: 2,
|
||||
prompt: Text("Rated Pressure")
|
||||
)
|
||||
.decimalPad()
|
||||
.focused($focusedField, equals: .ratedStaticPressure)
|
||||
}
|
||||
}
|
||||
.dynamicBottomPadding()
|
||||
|
||||
} header: {
|
||||
header("Rated Static Pressures", infoView: .ratedStaticPressures)
|
||||
}
|
||||
|
||||
Section {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
TextLabel("Includes Filter Drop")
|
||||
Spacer()
|
||||
Toggle("Includes Filter Drop", isOn: $store.includesFilterDrop)
|
||||
}
|
||||
if store.includesFilterDrop {
|
||||
// HStack {
|
||||
// TextLabel("Includes Filter Drop")
|
||||
// Spacer()
|
||||
// Toggle("Includes Filter Drop", isOn: $store.includesFilterDrop)
|
||||
// }
|
||||
// if store.includesFilterDrop {
|
||||
HStack {
|
||||
TextLabel("Filter Drop")
|
||||
Spacer()
|
||||
textField(
|
||||
"Filter Drop",
|
||||
"Optional",
|
||||
value: $store.sharedSettings.manufacturersIncludedFilterPressureDrop
|
||||
)
|
||||
.focused($focusedField, equals: .manufacturersIncludedFilterPressureDrop)
|
||||
.decimalPad()
|
||||
.padding(.leading, 40)
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
} header: {
|
||||
header(infoView: .manufacturersIncludedFilterPressureDrop) {
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
import ComposableArchitecture
|
||||
import SharedModels
|
||||
import Styleguide
|
||||
import SwiftUI
|
||||
|
||||
/// Allows for rated static pressure fields to be optional values, setting their corresponding shared state values to zero when they're nilled out.
|
||||
///
|
||||
@Reducer
|
||||
public struct RatedStaticPressuresSection {
|
||||
|
||||
public init() { }
|
||||
|
||||
@ObservableState
|
||||
public struct State: Equatable {
|
||||
|
||||
@Shared public var staticPressures: RatedStaticPressures
|
||||
public var focusedField: FocusedField?
|
||||
public var maxPressure: Double?
|
||||
public var minPressure: Double?
|
||||
public var ratedPressure: Double?
|
||||
|
||||
public init(
|
||||
staticPressures: Shared<RatedStaticPressures>
|
||||
) {
|
||||
self._staticPressures = staticPressures
|
||||
self.maxPressure = staticPressures.maximum.wrappedValue
|
||||
self.minPressure = staticPressures.minimum.wrappedValue
|
||||
self.ratedPressure = staticPressures.rated.wrappedValue
|
||||
}
|
||||
|
||||
public var isValid: Bool {
|
||||
maxPressure != nil
|
||||
&& minPressure != nil
|
||||
&& ratedPressure != nil
|
||||
}
|
||||
|
||||
public enum FocusedField: String, Hashable, CaseIterable, FocusableField {
|
||||
case maximum
|
||||
case minimum
|
||||
case rated
|
||||
|
||||
public var label: String { rawValue.capitalized }
|
||||
public var prompt: String { "\(label) Pressure"}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action: BindableAction {
|
||||
case binding(BindingAction<State>)
|
||||
case delegate(DelegateAction)
|
||||
|
||||
public enum DelegateAction {
|
||||
case infoButtonTapped
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some Reducer<State, Action> {
|
||||
BindingReducer()
|
||||
Reduce<State, Action> { state, action in
|
||||
switch action {
|
||||
case .binding(\.maxPressure):
|
||||
state.staticPressures.maximum = state.maxPressure ?? 0
|
||||
return .none
|
||||
|
||||
case .binding(\.minPressure):
|
||||
state.staticPressures.minimum = state.minPressure ?? 0
|
||||
return .none
|
||||
|
||||
case .binding(\.ratedPressure):
|
||||
state.staticPressures.rated = state.ratedPressure ?? 0
|
||||
return .none
|
||||
|
||||
case .binding:
|
||||
return .none
|
||||
|
||||
case .delegate:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct RatedStaticPressuresSectionView: View {
|
||||
|
||||
@FocusState private var focusedField: RatedStaticPressuresSection.State.FocusedField?
|
||||
@Bindable var store: StoreOf<RatedStaticPressuresSection>
|
||||
|
||||
public init(store: StoreOf<RatedStaticPressuresSection>) {
|
||||
self.store = store
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Section {
|
||||
Grid(alignment: .leading, horizontalSpacing: 40) {
|
||||
GridRow {
|
||||
label(for: .maximum)
|
||||
TextField(
|
||||
"Maximum",
|
||||
value: $store.maxPressure,
|
||||
fractionLength: 2,
|
||||
prompt: prompt(for: .maximum)
|
||||
)
|
||||
.decimalPad()
|
||||
.focused($focusedField, equals: .maximum)
|
||||
}
|
||||
GridRow {
|
||||
label(for: .minimum)
|
||||
TextField(
|
||||
"Minimum",
|
||||
value: $store.minPressure,
|
||||
fractionLength: 2,
|
||||
prompt: prompt(for: .minimum)
|
||||
)
|
||||
.decimalPad()
|
||||
.focused($focusedField, equals: .minimum)
|
||||
}
|
||||
GridRow {
|
||||
label(for: .rated)
|
||||
TextField(
|
||||
"Rated",
|
||||
value: $store.ratedPressure,
|
||||
fractionLength: 2,
|
||||
prompt: prompt(for: .rated)
|
||||
)
|
||||
.decimalPad()
|
||||
.focused($focusedField, equals: .rated)
|
||||
}
|
||||
}
|
||||
.dynamicBottomPadding()
|
||||
|
||||
} header: {
|
||||
HStack {
|
||||
SectionHeaderLabel("Rated Static Pressures")
|
||||
Spacer()
|
||||
InfoButton { store.send(.delegate(.infoButtonTapped)) }
|
||||
}
|
||||
}
|
||||
.bind($focusedField, to: $store.focusedField)
|
||||
}
|
||||
|
||||
private func label(for field: RatedStaticPressuresSection.State.FocusedField) -> some View {
|
||||
TextLabel(field.label)
|
||||
}
|
||||
|
||||
private func prompt(for field: RatedStaticPressuresSection.State.FocusedField) -> Text {
|
||||
Text(field.prompt)
|
||||
}
|
||||
}
|
||||
@@ -173,6 +173,39 @@ final class FlaggedMeasurementListReducerTests: XCTestCase {
|
||||
await store.finish()
|
||||
|
||||
}
|
||||
|
||||
// @MainActor
|
||||
// func testBindingsSetSharedState() async {
|
||||
// let store = TestStore(
|
||||
// initialState: FlaggedMeasurementsList.State(
|
||||
// sharedSettings: Shared(SharedPressureEstimationState(
|
||||
// budgets: nil,
|
||||
// equipmentMeasurement: .mock(type: .airHandler),
|
||||
// equipmentMetadata: .init(
|
||||
// coolingCapacity: .three,
|
||||
// fanType: .constantSpeed,
|
||||
// ratedStaticPressures: .init()
|
||||
// )
|
||||
// ))
|
||||
// )
|
||||
// ) {
|
||||
// FlaggedMeasurementsList()
|
||||
// }
|
||||
//
|
||||
// await store.send(.binding(.set(\.maxStaticPressure, nil))) {
|
||||
// $0.maxStaticPressure = nil
|
||||
// $0.sharedSettings.ratedStaticPressures.maximum = 0
|
||||
// }
|
||||
// await store.send(.binding(.set(\.minStaticPressure, nil))) {
|
||||
// $0.minStaticPressure = nil
|
||||
// $0.sharedSettings.ratedStaticPressures.minimum = 0
|
||||
// }
|
||||
// await store.send(.binding(.set(\.ratedStaticPressure, nil))) {
|
||||
// $0.ratedStaticPressure = nil
|
||||
// $0.sharedSettings.ratedStaticPressures.rated = 0
|
||||
// }
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
extension Tag {
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import ComposableArchitecture
|
||||
import PressureEstimationsFeature
|
||||
import SharedModels
|
||||
import Testing
|
||||
import XCTest
|
||||
|
||||
struct RatedStaticPressureStateTests {
|
||||
|
||||
@Test(
|
||||
"Rated static pressure validation",
|
||||
.tags(.ratedStaticPressuresSection)
|
||||
)
|
||||
func validation() {
|
||||
var state = RatedStaticPressuresSection.State(
|
||||
staticPressures: Shared(RatedStaticPressures())
|
||||
)
|
||||
#expect(state.isValid)
|
||||
|
||||
state.maxPressure = nil
|
||||
#expect(!state.isValid)
|
||||
|
||||
state.maxPressure = 1
|
||||
state.minPressure = nil
|
||||
#expect(!state.isValid)
|
||||
|
||||
state.minPressure = 1
|
||||
state.ratedPressure = nil
|
||||
#expect(!state.isValid)
|
||||
|
||||
state.ratedPressure = 1
|
||||
#expect(state.isValid)
|
||||
}
|
||||
|
||||
@Test(
|
||||
"Focused field label",
|
||||
.tags(.ratedStaticPressuresSection),
|
||||
arguments: RatedStaticPressuresSection.State.FocusedField.allCases
|
||||
)
|
||||
func label(field: RatedStaticPressuresSection.State.FocusedField) {
|
||||
#expect(field.label == "\(field.rawValue.capitalized)")
|
||||
}
|
||||
|
||||
@Test(
|
||||
"Focused field prompt",
|
||||
.tags(.ratedStaticPressuresSection),
|
||||
arguments: RatedStaticPressuresSection.State.FocusedField.allCases
|
||||
)
|
||||
func prompt(field: RatedStaticPressuresSection.State.FocusedField) {
|
||||
#expect(field.prompt == "\(field.rawValue.capitalized) Pressure")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class RatedStaticPressureReducerTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testBindingsSetSharedState() async {
|
||||
let store = TestStore(
|
||||
initialState: RatedStaticPressuresSection.State(
|
||||
staticPressures: Shared(RatedStaticPressures())
|
||||
)
|
||||
) {
|
||||
RatedStaticPressuresSection()
|
||||
}
|
||||
|
||||
await store.send(.binding(.set(\.maxPressure, nil))) {
|
||||
$0.maxPressure = nil
|
||||
$0.staticPressures.maximum = 0
|
||||
}
|
||||
await store.send(.binding(.set(\.minPressure, nil))) {
|
||||
$0.minPressure = nil
|
||||
$0.staticPressures.minimum = 0
|
||||
}
|
||||
await store.send(.binding(.set(\.ratedPressure, nil))) {
|
||||
$0.ratedPressure = nil
|
||||
$0.staticPressures.rated = 0
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension Tag {
|
||||
|
||||
@Tag static var ratedStaticPressuresSection: Self
|
||||
}
|
||||
Reference in New Issue
Block a user