feat: Adds text labeled content and style.

This commit is contained in:
2024-06-11 16:31:59 -04:00
parent c6c45ffa7e
commit da8a8638c7
8 changed files with 144 additions and 77 deletions

View File

@@ -19,7 +19,7 @@ public struct EquipmentMeasurementForm {
@ObservableState @ObservableState
public struct State: Equatable, Sendable { public struct State: Equatable, Sendable {
@Presents public var destination: Destination.State? @Presents public var destination: Destination.State?
@Shared public var sharedSettings: SharedPressureEstimationSettings @Shared public var sharedSettings: SharedPressureEstimationState
public var allowEquipmentTypeSelection: Bool public var allowEquipmentTypeSelection: Bool
public var equipmentType: EquipmentMeasurement.EquipmentType public var equipmentType: EquipmentMeasurement.EquipmentType
public var focusedField: Field? public var focusedField: Field?
@@ -28,7 +28,7 @@ public struct EquipmentMeasurementForm {
public init( public init(
allowEquipmentTypeSelection: Bool = true, allowEquipmentTypeSelection: Bool = true,
destination: Destination.State? = nil, destination: Destination.State? = nil,
sharedSettings: Shared<SharedPressureEstimationSettings>, sharedSettings: Shared<SharedPressureEstimationState>,
equipmentType: EquipmentMeasurement.EquipmentType = .airHandler, equipmentType: EquipmentMeasurement.EquipmentType = .airHandler,
focusedField: Field? = nil, focusedField: Field? = nil,
measurements: Measurements = .init() measurements: Measurements = .init()
@@ -224,7 +224,7 @@ public struct EquipmentMeasurementForm {
} }
} }
extension Store where State == EquipmentMeasurementForm.State { fileprivate extension Store where State == EquipmentMeasurementForm.State {
func prompt( func prompt(
field: EquipmentMeasurementForm.State.Field field: EquipmentMeasurementForm.State.Field
@@ -258,7 +258,7 @@ extension Store where State == EquipmentMeasurementForm.State {
case .supplyPlenumPressure: case .supplyPlenumPressure:
return "Supply" return "Supply"
case .airflow: case .airflow:
return "Airflow" return "CFM"
} }
} }
@@ -293,12 +293,7 @@ public struct EquipmentMeasurementFormView: View {
} }
Section { Section {
Grid(alignment: .leading, horizontalSpacing: 40) { Grid(alignment: .leading, horizontalSpacing: 40) {
ForEach(store.pressureFields) { field in ForEach(store.pressureFields, content: gridRow(for:))
GridRow {
TextLabel(store.label(field: field))
textField(for: field)
}
}
} }
} header: { } header: {
HStack { HStack {
@@ -310,10 +305,7 @@ public struct EquipmentMeasurementFormView: View {
Section { Section {
Grid(alignment: .leading, horizontalSpacing: 60) { Grid(alignment: .leading, horizontalSpacing: 60) {
GridRow { gridRow(for: .airflow)
TextLabel(store.label(field: .airflow))
textField(for: .airflow)
}
} }
} footer: { } footer: {
HStack { HStack {
@@ -325,6 +317,7 @@ public struct EquipmentMeasurementFormView: View {
} }
} }
.bind($focusedField, to: $store.focusedField) .bind($focusedField, to: $store.focusedField)
.labeledContentStyle(.gridRow)
.onAppear { send(.onAppear) } .onAppear { send(.onAppear) }
.textLabelStyle(.boldSecondary) .textLabelStyle(.boldSecondary)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
@@ -350,6 +343,12 @@ public struct EquipmentMeasurementFormView: View {
} }
} }
private func gridRow(for field: EquipmentMeasurementForm.State.Field) -> some View {
TextLabeledContent(store.label(field: field)) {
textField(for: field)
}
}
private func textField( private func textField(
for field: EquipmentMeasurementForm.State.Field for field: EquipmentMeasurementForm.State.Field
) -> some View { ) -> some View {
@@ -424,7 +423,7 @@ fileprivate extension InfoViewFeature.State {
NavigationStack { NavigationStack {
EquipmentMeasurementFormView( EquipmentMeasurementFormView(
store: Store(initialState: EquipmentMeasurementForm.State( store: Store(initialState: EquipmentMeasurementForm.State(
sharedSettings: Shared(SharedPressureEstimationSettings())) sharedSettings: Shared(SharedPressureEstimationState()))
) { ) {
EquipmentMeasurementForm() EquipmentMeasurementForm()
} }

View File

@@ -26,13 +26,13 @@ public struct EquipmentSettingsForm {
public var includesFilterDrop: Bool public var includesFilterDrop: Bool
public var equipmentType: EquipmentMeasurement.EquipmentType public var equipmentType: EquipmentMeasurement.EquipmentType
public var focusedField: Field? = nil public var focusedField: Field? = nil
@Shared public var sharedSettings: SharedPressureEstimationSettings @Shared public var sharedSettings: SharedPressureEstimationState
public init( public init(
destination: Destination.State? = nil, destination: Destination.State? = nil,
includesFilterDrop: Bool = false, includesFilterDrop: Bool = false,
equipmentType: EquipmentMeasurement.EquipmentType = .airHandler, equipmentType: EquipmentMeasurement.EquipmentType = .airHandler,
sharedSettings: Shared<SharedPressureEstimationSettings> sharedSettings: Shared<SharedPressureEstimationState>
) { ) {
self.destination = destination self.destination = destination
self.includesFilterDrop = includesFilterDrop self.includesFilterDrop = includesFilterDrop
@@ -46,12 +46,14 @@ public struct EquipmentSettingsForm {
} }
// Note: These need to be in display order. // Note: These need to be in display order.
public enum Field: Hashable, CaseIterable, FocusableField { public enum Field: Hashable, CaseIterable, FocusableField, Identifiable {
case heatingCapacity case heatingCapacity
case minimumStaticPressure case minimumStaticPressure
case maximumStaticPressure case maximumStaticPressure
case ratedStaticPressure case ratedStaticPressure
case manufacturersIncludedFilterPressureDrop case manufacturersIncludedFilterPressureDrop
public var id: Self { self }
} }
} }
@@ -183,37 +185,9 @@ public struct EquipmentSettingsFormView: View {
Section { Section {
Grid(alignment: .leading, horizontalSpacing: 40) { Grid(alignment: .leading, horizontalSpacing: 40) {
GridRow { ForEach(RatingsField.allCases, content: ratingsRow(for:))
TextLabel("Minimum")
textField(
"Minimum Pressure",
value: $store.sharedSettings.ratedStaticPressures.minimum,
fractionLength: 2
)
.focused($focusedField, equals: .minimumStaticPressure)
.decimalPad()
}
GridRow {
TextLabel("Maximum")
textField(
"Maximum Pressure",
value: $store.sharedSettings.ratedStaticPressures.maximum,
fractionLength: 2
)
.focused($focusedField, equals: .maximumStaticPressure)
.decimalPad()
}
GridRow {
TextLabel("Rated")
textField(
"Rated Pressure",
value: $store.sharedSettings.ratedStaticPressures.rated,
fractionLength: 2
)
.focused($focusedField, equals: .ratedStaticPressure)
.decimalPad()
}
} }
.labeledContentStyle(.gridRow)
} header: { } header: {
header("Rated Static Pressure", infoView: .ratedStaticPressures) header("Rated Static Pressure", infoView: .ratedStaticPressures)
} }
@@ -231,8 +205,7 @@ public struct EquipmentSettingsFormView: View {
Spacer() Spacer()
textField( textField(
"Filter Drop", "Filter Drop",
value: $store.sharedSettings.manufacturersIncludedFilterPressureDrop, value: $store.sharedSettings.manufacturersIncludedFilterPressureDrop
fractionLength: 2
) )
.focused($focusedField, equals: .manufacturersIncludedFilterPressureDrop) .focused($focusedField, equals: .manufacturersIncludedFilterPressureDrop)
.decimalPad() .decimalPad()
@@ -265,6 +238,27 @@ 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>( private func header<Label: View>(
infoView: EquipmentSettingsForm.InfoView, infoView: EquipmentSettingsForm.InfoView,
label: @escaping () -> Label label: @escaping () -> Label
@@ -283,20 +277,13 @@ public struct EquipmentSettingsFormView: View {
header(infoView: infoView) { Text(title) } header(infoView: infoView) { Text(title) }
} }
private func textField(
_ title: String,
value: Binding<Double>,
fractionLength: Int = 2
) -> some View {
TextField(title, value: value, fractionLength: fractionLength, prompt: Text(title))
}
private func textField( private func textField(
_ title: String, _ title: String,
value: Binding<Double?>, value: Binding<Double?>,
fractionLength: Int = 2 fractionLength: Int = 2
) -> some View { ) -> some View {
TextField(title, value: value, fractionLength: fractionLength, prompt: Text(title)) TextField(title, value: value, fractionLength: fractionLength, prompt: Text(title))
.onSubmit { send(.submitField) }
} }
} }
@@ -348,12 +335,71 @@ 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" }
}
//fileprivate extension Store where State == EquipmentSettingsForm.State {
//
//
// func label(for ratingsField: RatingsField) -> String {
// self.label(for: ratingsField.field)
// }
//
// func prompt(for ratingsField: RatingsField) -> String {
// self.prompt(for: ratingsField.field)
// }
//
// func label(for field: EquipmentSettingsForm.State.Field) -> String {
// switch field {
// case .heatingCapacity:
// return "Heating"
// case .minimumStaticPressure:
// return "Minimum"
// case .maximumStaticPressure:
// return "Maximum"
// case .ratedStaticPressure:
// return "Rated"
// case .manufacturersIncludedFilterPressureDrop:
// return "Filter Drop"
// }
// }
//
//
// func prompt(for field: EquipmentSettingsForm.State.Field) -> String {
// switch field {
// case .heatingCapacity:
// return "Heating Capacity"
// case .minimumStaticPressure, .maximumStaticPressure, .ratedStaticPressure:
// return "\(label(for: field)) Pressure"
// case .manufacturersIncludedFilterPressureDrop:
// return label(for: field)
// }
// }
//}
#Preview { #Preview {
NavigationStack { NavigationStack {
EquipmentSettingsFormView( EquipmentSettingsFormView(
store: Store( store: Store(
initialState: EquipmentSettingsForm.State( initialState: EquipmentSettingsForm.State(
sharedSettings: Shared(SharedPressureEstimationSettings()) sharedSettings: Shared(SharedPressureEstimationState())
) )
) { ) {
EquipmentSettingsForm()._printChanges() EquipmentSettingsForm()._printChanges()

View File

@@ -20,11 +20,11 @@ public struct FlaggedMeasurementsList: Sendable {
public struct State: Equatable { public struct State: Equatable {
@Presents public var destination: Destination.State? @Presents public var destination: Destination.State?
@Shared var sharedSettings: SharedPressureEstimationSettings @Shared var sharedSettings: SharedPressureEstimationState
public init( public init(
destination: Destination.State? = nil, destination: Destination.State? = nil,
sharedSettings: Shared<SharedPressureEstimationSettings> sharedSettings: Shared<SharedPressureEstimationState>
) { ) {
self.destination = destination self.destination = destination
self._sharedSettings = sharedSettings self._sharedSettings = sharedSettings
@@ -36,7 +36,7 @@ public struct FlaggedMeasurementsList: Sendable {
return [.coilDrop, .filterDrop] return [.coilDrop, .filterDrop]
} }
public subscript<T>(dynamicMember keyPath: WritableKeyPath<SharedPressureEstimationSettings, T>) -> T { public subscript<T>(dynamicMember keyPath: WritableKeyPath<SharedPressureEstimationState, T>) -> T {
get { sharedSettings[keyPath: keyPath] } get { sharedSettings[keyPath: keyPath] }
set { sharedSettings[keyPath: keyPath] = newValue } set { sharedSettings[keyPath: keyPath] = newValue }
} }
@@ -58,7 +58,7 @@ public struct FlaggedMeasurementsList: Sendable {
public enum View { public enum View {
case addButtonTapped case addButtonTapped
case destination(DestinationAction) case destination(DestinationAction)
case editButtonTapped(id: SharedPressureEstimationSettings.FlaggedMeasurementContainer.ID) case editButtonTapped(id: SharedPressureEstimationState.FlaggedMeasurementContainer.ID)
case onAppear case onAppear
@CasePathable @CasePathable
@@ -282,7 +282,7 @@ public struct FlaggedMeasurementListView: View {
#if DEBUG #if DEBUG
private let budgets = BudgetedPercentEnvelope(equipmentType: .airHandler, fanType: .constantSpeed) private let budgets = BudgetedPercentEnvelope(equipmentType: .airHandler, fanType: .constantSpeed)
private let flaggedMeasurements = IdentifiedArrayOf<SharedPressureEstimationSettings.FlaggedMeasurementContainer>( private let flaggedMeasurements = IdentifiedArrayOf<SharedPressureEstimationState.FlaggedMeasurementContainer>(
uniqueElements: [ uniqueElements: [
.init( .init(
id: UUID(0), id: UUID(0),
@@ -303,7 +303,7 @@ private let flaggedMeasurements = IdentifiedArrayOf<SharedPressureEstimationSett
store: Store( store: Store(
initialState: FlaggedMeasurementsList.State( initialState: FlaggedMeasurementsList.State(
sharedSettings: Shared( sharedSettings: Shared(
SharedPressureEstimationSettings( SharedPressureEstimationState(
budgets: budgets, budgets: budgets,
equipmentMeasurement: .mock(type: .airHandler), equipmentMeasurement: .mock(type: .airHandler),
flaggedEquipmentMeasurement: nil, flaggedEquipmentMeasurement: nil,

View File

@@ -19,12 +19,12 @@ public struct PressureEstimationsFeature {
@ObservableState @ObservableState
public struct State: Equatable { public struct State: Equatable {
@Presents public var destination: Destination.State? @Presents public var destination: Destination.State?
@Shared(.sharedPressureEstimationSettings) var sharedSettings = SharedPressureEstimationSettings() @Shared(.sharedPressureEstimationSettings) var sharedSettings = SharedPressureEstimationState()
public var equipmentSettings: EquipmentSettingsForm.State public var equipmentSettings: EquipmentSettingsForm.State
public init( public init(
destination: Destination.State? = nil, destination: Destination.State? = nil,
sharedSettings: SharedPressureEstimationSettings = .init() sharedSettings: SharedPressureEstimationState = .init()
) { ) {
self.destination = destination self.destination = destination
self._sharedSettings = Shared(sharedSettings) self._sharedSettings = Shared(sharedSettings)

View File

@@ -4,7 +4,7 @@ import SharedModels
/// Holds onto shared values for several of the views in this feature. /// Holds onto shared values for several of the views in this feature.
@dynamicMemberLookup @dynamicMemberLookup
public struct SharedPressureEstimationSettings: Equatable, Sendable { public struct SharedPressureEstimationState: Equatable, Sendable {
public var budgets: BudgetedPercentEnvelope? public var budgets: BudgetedPercentEnvelope?
public var equipmentMeasurement: EquipmentMeasurement? public var equipmentMeasurement: EquipmentMeasurement?
public var equipmentMetadata: EquipmentMetadata public var equipmentMetadata: EquipmentMetadata
@@ -53,7 +53,7 @@ public struct SharedPressureEstimationSettings: Equatable, Sendable {
} }
} }
extension PersistenceReaderKey where Self == InMemoryKey<SharedPressureEstimationSettings> { extension PersistenceReaderKey where Self == InMemoryKey<SharedPressureEstimationState> {
static var sharedPressureEstimationSettings: Self { static var sharedPressureEstimationSettings: Self {
.inMemory("sharedPressureEstimationSettings") .inMemory("sharedPressureEstimationSettings")
} }

View File

@@ -49,11 +49,7 @@ public struct DefaultInfoButtonStyle<Style: LabelStyle>: PrimitiveButtonStyle {
.foregroundStyle(color) .foregroundStyle(color)
.labelStyle(labelStyle) .labelStyle(labelStyle)
} }
// configuration.label .buttonStyle(.plain)
// .font(font)
// .foregroundStyle(color.opacity(configuration.isPressed ? 0.5 : 1))
// .labelStyle(labelStyle)
// .scaleEffect(configuration.isPressed ? 0.8 : 1)
} }
} }

View File

@@ -0,0 +1,17 @@
import SwiftUI
public struct GridRowTextLabeledContentStyle: LabeledContentStyle {
public func makeBody(configuration: Configuration) -> some View {
GridRow {
configuration.label
configuration.content
}
}
}
extension LabeledContentStyle where Self == GridRowTextLabeledContentStyle {
public static var gridRow: Self {
GridRowTextLabeledContentStyle()
}
}

View File

@@ -49,6 +49,15 @@ extension TextLabeledContent where Label == Text {
} }
#Preview { #Preview {
TextLabeledContent("Label") { Text("Content") } VStack {
TextLabeledContent("Label") { Text("Content") }
.textLabelStyle(.boldSecondary)
Grid {
TextLabeledContent("One") { Text("One-Content") }
TextLabeledContent("Two") { Text("Two-Content") }
}
.textLabelStyle(.boldSecondary) .textLabelStyle(.boldSecondary)
.labeledContentStyle(.gridRow)
}
} }