diff --git a/Sources/PressureEstimationsFeature/EquipmentSettingsForm.swift b/Sources/PressureEstimationsFeature/EquipmentSettingsForm.swift index bda63ef..10ef2aa 100644 --- a/Sources/PressureEstimationsFeature/EquipmentSettingsForm.swift +++ b/Sources/PressureEstimationsFeature/EquipmentSettingsForm.swift @@ -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 ) { 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) case destination(PresentationAction) - case ratedStaticPressures(RatedStaticPressuresSection.Action) case view(View) @CasePathable @@ -95,21 +87,19 @@ public struct EquipmentSettingsForm { public var body: some Reducer { BindingReducer() - Scope(state: \.ratedStaticPressures, action: \.ratedStaticPressures) { - RatedStaticPressuresSection() - } Reduce { 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, + _ stateKeyPath: KeyPath, + _ 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) { diff --git a/Sources/PressureEstimationsFeature/RatedStaticPressuresSection.swift b/Sources/PressureEstimationsFeature/RatedStaticPressuresSection.swift deleted file mode 100644 index 97f7660..0000000 --- a/Sources/PressureEstimationsFeature/RatedStaticPressuresSection.swift +++ /dev/null @@ -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 - ) { - 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) - case delegate(DelegateAction) - - public enum DelegateAction { - case infoButtonTapped - } - } - - public var body: some Reducer { - BindingReducer() - Reduce { 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 - - public init(store: StoreOf) { - 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) - } -} diff --git a/Tests/PressureEstimationsFeatureTests/FlaggedMeasurementListTests.swift b/Tests/PressureEstimationsFeatureTests/FlaggedMeasurementListTests.swift index 11ddf10..128fcc0 100644 --- a/Tests/PressureEstimationsFeatureTests/FlaggedMeasurementListTests.swift +++ b/Tests/PressureEstimationsFeatureTests/FlaggedMeasurementListTests.swift @@ -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 { diff --git a/Tests/PressureEstimationsFeatureTests/RatedStaticPressures.swift b/Tests/PressureEstimationsFeatureTests/RatedStaticPressures.swift deleted file mode 100644 index 8420e7a..0000000 --- a/Tests/PressureEstimationsFeatureTests/RatedStaticPressures.swift +++ /dev/null @@ -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 -}