feat: Breaks rated static pressure section into it's own view.
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user