feat: Begins equipment measurement form

This commit is contained in:
2024-06-04 22:57:18 -04:00
parent a65ac76dde
commit 6ec3eacb8d
3 changed files with 354 additions and 1 deletions

View File

@@ -0,0 +1,337 @@
import ComposableArchitecture
import SharedModels
import Styleguide
import SwiftUI
@Reducer
public struct EquipmentMeasurementForm {
public init() { }
@Reducer(state: .equatable)
public enum Destination {
case infoView(InfoViewFeature)
}
@ObservableState
public struct State: Equatable {
@Presents public var destination: Destination.State?
public var equipmentType: EquipmentType
public var focusedField: Field?
public var fields: FormFields
public init(
destination: Destination.State? = nil,
equipmentType: EquipmentType = .airHandler,
focusedField: Field? = nil,
fields: FormFields = .init()
) {
self.destination = destination
self.equipmentType = equipmentType
self.focusedField = focusedField
self.fields = fields
}
public var equipmentMeasurement: EquipmentMeasurement {
fields.equipmentMeasurement(type: equipmentType)
}
public var isValid: Bool {
fields.airflow != nil
&& fields.returnPlenumPressure != nil
&& fields.postFilterPressure != nil
&& fields.coilPressure != nil
&& fields.supplyPlenumPressure != nil
}
public struct FormFields: Equatable {
public var airflow: Double?
public var returnPlenumPressure: Double?
public var postFilterPressure: Double?
public var coilPressure: Double?
public var supplyPlenumPressure: Double?
public init(
airflow: Double? = nil,
returnPlenumPressure: Double? = nil,
postFilterPressure: Double? = nil,
coilPressure: Double? = nil,
supplyPlenumPressure: Double? = nil
) {
self.airflow = airflow
self.returnPlenumPressure = returnPlenumPressure
self.postFilterPressure = postFilterPressure
self.coilPressure = coilPressure
self.supplyPlenumPressure = supplyPlenumPressure
}
public init(
equipmentMeasurement: EquipmentMeasurement
) {
switch equipmentMeasurement {
case let .airHandler(equipment):
self.init(
airflow: equipment.airflow,
returnPlenumPressure: equipment.returnPlenumPressure,
postFilterPressure: equipment.postFilterPressure,
coilPressure: equipment.postCoilPressure,
supplyPlenumPressure: equipment.supplyPlenumPressure
)
case let .furnaceAndCoil(equipment):
self.init(
airflow: equipment.airflow,
returnPlenumPressure: equipment.returnPlenumPressure,
postFilterPressure: equipment.postFilterPressure,
coilPressure: equipment.preCoilPressure,
supplyPlenumPressure: equipment.supplyPlenumPressure
)
}
}
public func equipmentMeasurement(type: EquipmentType) -> EquipmentMeasurement {
switch type {
case .airHandler:
return .airHandler(.init(
airflow: airflow,
returnPlenumPressure: returnPlenumPressure,
postFilterPressure: postFilterPressure,
postCoilPressure: coilPressure,
supplyPlenumPressure: supplyPlenumPressure
))
case .furnaceAndCoil:
return .furnaceAndCoil(.init(
airflow: airflow,
returnPlenumPressure: returnPlenumPressure,
postFilterPressure: postFilterPressure,
preCoilPressure: coilPressure,
supplyPlenumPressure: supplyPlenumPressure
))
}
}
}
public enum Field: Hashable, CaseIterable, FocusableField, Identifiable {
case returnPlenumPressure
case postFilterPressure
case coilPressure
case supplyPlenumPressure
case airflow
public var id: Self { self }
}
}
public enum Action: BindableAction, ViewAction {
case binding(BindingAction<State>)
case destination(PresentationAction<Destination.Action>)
case view(View)
@CasePathable
public enum View {
case infoButtonTapped
case resetButtonTapped
case submitField
}
}
public var body: some Reducer<State, Action> {
BindingReducer()
Reduce<State, Action> { state, action in
switch action {
case .binding:
return .none
case .destination:
return .none
case let .view(action):
switch action {
case .infoButtonTapped:
return .none
case .resetButtonTapped:
state.fields = .init()
return .none
case .submitField:
state.focusedField = state.focusedField?.next
return .none
}
}
}
.ifLet(\.$destination, action: \.destination)
}
}
fileprivate extension Store where State == EquipmentMeasurementForm.State {
func prompt(
field: EquipmentMeasurementForm.State.Field
) -> String {
let label = label(field: field)
guard field != .airflow else { return label }
return "\(label) Pressure"
}
func label(
field: EquipmentMeasurementForm.State.Field
) -> String {
switch field {
case .returnPlenumPressure:
return "Return"
case .postFilterPressure:
return "Post-Filter"
case .coilPressure:
switch state.equipmentType {
case .airHandler:
return "Post-Coil"
case .furnaceAndCoil:
return "Pre-Coil"
}
case .supplyPlenumPressure:
return "Supply"
case .airflow:
return "Airflow"
}
}
var pressureFields: [EquipmentMeasurementForm.State.Field] {
EquipmentMeasurementForm.State.Field.allCases.filter {
$0 != .airflow
}
}
}
@ViewAction(for: EquipmentMeasurementForm.self)
public struct EquipmentMeasurementFormView: View {
@Bindable public var store: StoreOf<EquipmentMeasurementForm>
public init(store: StoreOf<EquipmentMeasurementForm>) {
self.store = store
}
public var body: some View {
Form {
Section {
} header: {
Text("Equipment Type")
} footer: {
Picker("Equipment Type", selection: $store.equipmentType) {
ForEach(EquipmentType.allCases) {
Text($0.description)
.tag($0)
}
}
.pickerStyle(.segmented)
.labelsHidden()
}
Section {
Grid(alignment: .leading, horizontalSpacing: 40) {
ForEach(store.pressureFields) { field in
GridRow {
TextLabel(store.label(field: field))
textField(for: field)
}
}
}
} header: {
HStack {
Text("Static Measurements")
Spacer()
InfoButton { send(.infoButtonTapped) }
}
}
Section {
Grid(alignment: .leading, horizontalSpacing: 60) {
GridRow {
TextLabel(store.label(field: .airflow))
textField(for: .airflow)
}
}
} footer: {
HStack {
Spacer()
ResetButton { send(.resetButtonTapped) }
.padding(.top)
Spacer()
}
}
}
.textLabelStyle(.boldSecondary)
.textFieldStyle(.roundedBorder)
}
private func textField(
for field: EquipmentMeasurementForm.State.Field
) -> some View {
let value: Binding<Double?>
var fractionLength: Int = 2
switch field {
case .returnPlenumPressure:
value = $store.fields.returnPlenumPressure
case .postFilterPressure:
value = $store.fields.postFilterPressure
case .coilPressure:
value = $store.fields.coilPressure
case .supplyPlenumPressure:
value = $store.fields.supplyPlenumPressure
case .airflow:
value = $store.fields.airflow
fractionLength = 0
}
return textField(
title: store.prompt(field: field),
value: value,
fractionLength: fractionLength,
numberPad: field == .airflow
)
}
@ViewBuilder
private func textField(
title: String,
value: Binding<Double?>,
fractionLength: Int,
numberPad: Bool
) -> some View {
if numberPad {
TextField(
title,
value: value,
fractionLength: fractionLength,
prompt: Text(title)
)
.numberPad()
} else {
TextField(
title,
value: value,
fractionLength: fractionLength,
prompt: Text(title)
)
.decimalPad()
}
}
}
#Preview {
EquipmentMeasurementFormView(
store: Store(initialState: EquipmentMeasurementForm.State()) {
EquipmentMeasurementForm()
}
)
}

View File

@@ -1,9 +1,11 @@
import Foundation
public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible {
public enum EquipmentType: Equatable, CaseIterable, CustomStringConvertible, Identifiable {
case airHandler
case furnaceAndCoil
public var id: Self { self }
public var description: String {
switch self {
case .airHandler:

View File

@@ -16,4 +16,18 @@ extension TextField where Label == Text {
)
}
public init<S: StringProtocol>(
_ titleKey: S,
value: Binding<Double?>,
fractionLength: Int,
prompt: Text? = nil
) {
self.init(
titleKey,
value: value,
format: .number.precision(.fractionLength(fractionLength)),
prompt: prompt
)
}
}