feat: Breaks rated static pressure section into it's own view.

This commit is contained in:
2024-06-17 11:06:00 -04:00
parent 0d5fcb7639
commit cf4c00d9d5
2 changed files with 173 additions and 68 deletions

View File

@@ -27,6 +27,7 @@ public struct EquipmentSettingsForm {
public var equipmentType: EquipmentMeasurement.EquipmentType
public var focusedField: Field? = nil
@Shared public var sharedSettings: SharedPressureEstimationState
public var ratedStaticPressures: RatedStaticPressuresSection.State
public init(
destination: Destination.State? = nil,
@@ -38,20 +39,30 @@ public struct EquipmentSettingsForm {
self.includesFilterDrop = includesFilterDrop
self.equipmentType = equipmentType
self._sharedSettings = sharedSettings
self.ratedStaticPressures = .init(staticPressures: sharedSettings.equipmentMetadata.ratedStaticPressures)
}
public var isValid: Bool {
guard equipmentType == .furnaceAndCoil else { return true }
return sharedSettings.heatingCapacity != nil
guard equipmentType == .furnaceAndCoil
else { return ratedStaticPressures.isValid }
return ratedStaticPressures.isValid && sharedSettings.heatingCapacity != nil
}
// Note: These need to be in display order.
public enum Field: Hashable, CaseIterable, FocusableField, Identifiable {
case heatingCapacity
case minimumStaticPressure
case maximumStaticPressure
case ratedStaticPressure
case ratedStaticPressure(RatedStaticPressuresSection.State.FocusedField)
case manufacturersIncludedFilterPressureDrop
public static var allCases: [EquipmentSettingsForm.State.Field] {
[
.heatingCapacity,
.ratedStaticPressure(.maximum),
.ratedStaticPressure(.minimum),
.ratedStaticPressure(.rated),
.manufacturersIncludedFilterPressureDrop
]
}
public var id: Self { self }
}
@@ -60,6 +71,7 @@ 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
@@ -72,6 +84,9 @@ 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 {
@@ -95,6 +110,14 @@ 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 {
@@ -177,7 +200,6 @@ public struct EquipmentSettingsFormView: View {
}
}
.dynamicBottomPadding()
// .applyPadding()
} header: {
if store.equipmentType == .airHandler {
EmptyView()
@@ -186,15 +208,9 @@ public struct EquipmentSettingsFormView: View {
}
}
Section {
Grid(alignment: .leading, horizontalSpacing: 40) {
ForEach(RatingsField.allCases, content: ratingsRow(for:))
}
.dynamicBottomPadding()
.labeledContentStyle(.gridRow)
} header: {
header("Rated Static Pressure", infoView: .ratedStaticPressures)
}
RatedStaticPressuresSectionView(
store: store.scope(state: \.ratedStaticPressures, action: \.ratedStaticPressures)
)
Section {
VStack(alignment: .leading) {
@@ -240,27 +256,6 @@ public struct EquipmentSettingsFormView: View {
}
}
private func ratingsRow(for ratingsField: RatingsField) -> some View {
func binding(for ratingsField: RatingsField) -> Binding<Double> {
switch ratingsField {
case .maximum:
return $store.sharedSettings.ratedStaticPressures.maximum
case .minimum:
return $store.sharedSettings.ratedStaticPressures.minimum
case .rated:
return $store.sharedSettings.ratedStaticPressures.rated
}
}
return TextLabeledContent(ratingsField.label) {
TextField(ratingsField.prompt, value: binding(for: ratingsField), fractionLength: 2)
.decimalPad()
.focused($focusedField, equals: ratingsField.field)
.onSubmit { send(.submitField) }
}
}
private func header<Label: View>(
infoView: EquipmentSettingsForm.InfoView,
label: @escaping () -> Label
@@ -289,17 +284,6 @@ public struct EquipmentSettingsFormView: View {
}
}
fileprivate extension View {
@ViewBuilder
func applyPadding() -> some View {
#if os(macOS)
self.padding(.bottom, 20)
#else
self
#endif
}
}
fileprivate struct BudgetFlagViewStyle: FlaggedViewStyle {
func makeBody(configuration: Configuration) -> some View {
@@ -348,26 +332,6 @@ fileprivate extension InfoViewFeature.State {
}
}
fileprivate enum RatingsField: String, Hashable, CaseIterable, Identifiable {
case maximum
case minimum
case rated
var id: Self { self }
var field: EquipmentSettingsForm.State.Field {
switch self {
case .maximum: return .maximumStaticPressure
case .minimum: return .minimumStaticPressure
case .rated: return .ratedStaticPressure
}
}
var label: String { rawValue.capitalized }
var prompt: String { "\(label) Pressure" }
}
#Preview {
NavigationStack {
EquipmentSettingsFormView(

View File

@@ -0,0 +1,141 @@
import ComposableArchitecture
import SharedModels
import Styleguide
import SwiftUI
#warning("Add info view destination??")
/// 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 {
@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
var label: String { rawValue.capitalized }
}
}
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: Text("Max Static Pressure")
)
.decimalPad()
.focused($focusedField, equals: .maximum)
}
GridRow {
label(for: .minimum)
TextField(
"Minimum",
value: $store.minPressure,
fractionLength: 2,
prompt: Text("Min Static Pressure")
)
.decimalPad()
.focused($focusedField, equals: .minimum)
}
GridRow {
label(for: .rated)
TextField(
"Rated",
value: $store.ratedPressure,
fractionLength: 2,
prompt: Text("Rated Static Pressure")
)
.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)
}
}