Files
swift-estimated-pressures-core/Sources/AirflowAtPressureFeature/AirflowAtPressure.swift

190 lines
4.6 KiB
Swift

import ComposableArchitecture
import EstimatedPressureDependency
import SharedModels
import Styleguide
import SwiftUI
@Reducer
public struct AirflowAtPressureFeature {
@ObservableState
public struct State: Equatable {
public var isPresentingInfoView: Bool = false
public var existingAirflow: Double?
public var existingPressure: Double?
public var targetPressure: Double?
public var calculatedAirflow: Positive<Double>?
public init(
existingAirflow: Double? = nil,
existingPressure: Double? = nil,
targetPressure: Double? = 0.5,
calculatedAirflow: Positive<Double>? = nil
) {
self.existingAirflow = existingAirflow
self.existingPressure = existingPressure
self.targetPressure = targetPressure
self.calculatedAirflow = calculatedAirflow
}
public var isValid: Bool {
existingAirflow != nil
&& existingPressure != nil
&& targetPressure != nil
}
}
public enum Action: BindableAction, ViewAction {
case binding(BindingAction<State>)
case receive(ReceiveAction)
case view(View)
@CasePathable
public enum ReceiveAction {
case calculatedAirflow(Positive<Double>?)
}
@CasePathable
public enum View {
case dismissInfoViewButtonTapped
case infoButtonTapped
case resetButtonTapped
case submit
}
}
@Dependency(\.estimatedPressuresClient) var estimatedPressuresClient
public var body: some Reducer<State, Action> {
BindingReducer()
Reduce<State, Action> { state, action in
switch action {
case .binding:
return .none
case let .receive(action):
switch action {
case let .calculatedAirflow(airflow):
state.calculatedAirflow = airflow
return .none
}
case let .view(action):
switch action {
case .dismissInfoViewButtonTapped:
state.isPresentingInfoView = false
return .none
case .infoButtonTapped:
state.isPresentingInfoView = true
return .none
case .resetButtonTapped:
state = .init()
return .none
case .submit:
guard state.isValid else { return .none }
return .run { [state] send in
await send(
.receive(.calculatedAirflow(
try? await estimatedPressuresClient.estimatedAirflow(.init(
existingAirflow: state.existingAirflow ?? 0,
existingPressure: state.existingPressure ?? 0,
targetPressure: state.targetPressure ?? 0
))
))
)
}
}
}
}
}
}
@ViewAction(for: AirflowAtPressureFeature.self)
public struct AirflowAtPressureView: View {
@Perception.Bindable
public var store: StoreOf<AirflowAtPressureFeature>
public init(store: StoreOf<AirflowAtPressureFeature>) {
self.store = store
}
public var body: some View {
Form {
Section {
TextField(
"Existing Airflow",
value: $store.existingAirflow,
format: .number,
prompt: Text("Airflow")
)
.onSubmit { send(.submit) }
.numberPad()
TextField(
"Existing Pressure",
value: $store.existingPressure,
format: .number,
prompt: Text("Existing Pressure")
)
.onSubmit { send(.submit) }
.decimalPad()
TextField(
"Target Pressure",
value: $store.targetPressure,
format: .number,
prompt: Text("Target Pressure")
)
.onSubmit { send(.submit) }
.decimalPad()
} header: {
HStack {
Text("Inputs")
Spacer()
InfoButton { send(.infoButtonTapped) }
}
.labelStyle(.iconOnly)
} footer: {
HStack {
Spacer()
Button("Reset") { send(.resetButtonTapped) }
Spacer()
}
.padding(.top)
.buttonStyle(.borderedProminent)
}
Section {
if let airflow = store.calculatedAirflow {
Text(airflow.positiveValue, format: .number.precision(.fractionLength(0)))
}
} header: {
Text("Calculated Airflow")
}
}
.sheet(isPresented: $store.isPresentingInfoView) {
NavigationStack {
AirflowAtPressureInfoView()
.toolbar {
Button("Done") { send(.dismissInfoViewButtonTapped) }
}
}
}
}
}
#Preview {
AirflowAtPressureView(
store: Store(initialState: AirflowAtPressureFeature.State()) {
AirflowAtPressureFeature()
}
)
}