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 { @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) } }