diff --git a/Sources/FlaggedViews/FlaggedEquipmentMeasurementView.swift b/Sources/FlaggedViews/FlaggedEquipmentMeasurementView.swift index f4b1746..fac2683 100644 --- a/Sources/FlaggedViews/FlaggedEquipmentMeasurementView.swift +++ b/Sources/FlaggedViews/FlaggedEquipmentMeasurementView.swift @@ -20,6 +20,128 @@ public struct FlaggedEquipmentMeasurementView: View { } } +public protocol FlaggedEquipmentMeasurementStyle { + associatedtype Body: View + typealias Configuration = FlaggedEquipmentMeasurementStyleConfiguration + + @ViewBuilder + func makeBody(configuration: Self.Configuration) -> Self.Body +} + +public struct FlaggedEquipmentMeasurementStyleConfiguration { + public let measurement: FlaggedEquipmentMeasurement +} + +public struct AnyFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { + private var _makeBody: (Configuration) -> AnyView + + internal init(makeBody: @escaping (Configuration) -> AnyView) { + self._makeBody = makeBody + } + + public init(_ style: S) { + self.init { configuration in + AnyView(style.makeBody(configuration: configuration)) + } + } + + public func makeBody(configuration: Configuration) -> some View { + _makeBody(configuration) + } +} + +public struct GridFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { + + public func makeBody(configuration: Configuration) -> some View { + Grid(alignment: .leading, verticalSpacing: 20) { + ForEach(FlaggedEquipmentMeasurement.Key.allCases) { field in + FlaggedView( + field.title, + flagged: configuration.measurement[keyPath: field.flaggedKeyPath] + ) + .flaggedViewStyle(.gridRow(fractionLength: field == .airflow ? 0 : 2)) + } + } + } +} + +extension FlaggedEquipmentMeasurementStyle where Self == GridFlaggedEquipmentMeasurementStyle { + public static var grid: Self { .init() } +} + +private struct FlaggedEquipmentMeasurementStyleKey: EnvironmentKey { + static let defaultValue = AnyFlaggedEquipmentMeasurementStyle(GridFlaggedEquipmentMeasurementStyle()) +} + +extension EnvironmentValues { + public var flaggedEquipmentMeasurementStyle: AnyFlaggedEquipmentMeasurementStyle { + get { self[FlaggedEquipmentMeasurementStyleKey.self] } + set { self[FlaggedEquipmentMeasurementStyleKey.self] = newValue } + } +} + +extension View { + public func flaggedEquipmentMeasurementStyle(_ style: AnyFlaggedEquipmentMeasurementStyle) -> some View { + environment(\.flaggedEquipmentMeasurementStyle, style) + } + + public func flaggedEquipmentMeasurementStyle( + _ style: S + ) -> some View { + flaggedEquipmentMeasurementStyle(AnyFlaggedEquipmentMeasurementStyle(style)) + } +} + +// MARK: - Key +fileprivate extension FlaggedEquipmentMeasurement { + // NOTE: These need to be kept in display order. + enum Key: Hashable, CaseIterable, Identifiable { + case returnPlenum + case filterDrop + case coilDrop + case supplyPlenum + case staticPressure + case airflow + + var id: Self { self } + + var title: String { + switch self { + case .returnPlenum: + return "Return Plenum" + case .filterDrop: + return "Filter Pressure Drop" + case .coilDrop: + return "Coil Pressure Drop" + case .supplyPlenum: + return "Supply Plenum" + case .staticPressure: + return "External Static Pressure" + case .airflow: + return "System Airflow" + } + } + + var flaggedKeyPath: KeyPath { + switch self { + case .returnPlenum: + return \.returnPlenumPressure + case .filterDrop: + return \.filterPressureDrop + case .coilDrop: + return \.coilPressureDrop + case .supplyPlenum: + return \.supplyPlenumPressure + case .staticPressure: + return \.externalStaticPressure + case .airflow: + return \.airflow + } + } + } +} + + #if DEBUG private let equipmentMeasurement = EquipmentMeasurement.airHandler(.init( airflow: 1600, diff --git a/Sources/FlaggedViews/FlaggedMessageLabel.swift b/Sources/FlaggedViews/FlaggedMessageLabel.swift index 163a05a..2a622fa 100644 --- a/Sources/FlaggedViews/FlaggedMessageLabel.swift +++ b/Sources/FlaggedViews/FlaggedMessageLabel.swift @@ -22,6 +22,28 @@ public struct FlaggedMessageLabel: View { } } +private struct FlaggedMessageLabelStyleKey: EnvironmentKey { + static var defaultValue = AnyTextLabelStyle(style: .font(.caption)) +} + +extension EnvironmentValues { + var flaggedMessageLabelStyle: AnyTextLabelStyle { + get { self[FlaggedMessageLabelStyleKey.self] } + set { self[FlaggedMessageLabelStyleKey.self] = newValue } + } +} + +extension View { + public func flaggedMessageLabelStyle(_ style: AnyTextLabelStyle) -> some View { + environment(\.flaggedMessageLabelStyle, style) + } + + public func flaggedMessageLabelStyle(_ style: S) -> some View { + flaggedMessageLabelStyle(AnyTextLabelStyle(style: style)) + } +} + + #Preview { FlaggedStatusLabel(status: .warning) .flaggedStatusLabelStyle(.textLabel(.heavyTitle2)) diff --git a/Sources/FlaggedViews/FlaggedMessageView.swift b/Sources/FlaggedViews/FlaggedMessageView.swift index 5922fd7..57cb059 100644 --- a/Sources/FlaggedViews/FlaggedMessageView.swift +++ b/Sources/FlaggedViews/FlaggedMessageView.swift @@ -26,3 +26,130 @@ extension FlaggedMessageView { self.init(message: flagged.message, status: flagged.status) } } + +public protocol FlaggedMessageViewStyle { + associatedtype Body: View + typealias Configuration = FlaggedMessageViewStyleConfiguration + + @ViewBuilder + func makeBody(configuration: Self.Configuration) -> Self.Body +} + +public struct FlaggedMessageViewStyleConfiguration { + public let message: String? + public let status: Flagged.CheckResult.Status + + public init(message: String?, status: Flagged.CheckResult.Status) { + self.message = message + self.status = status + } +} + +public struct AnyFlaggedMessageViewStyle: FlaggedMessageViewStyle { + private let _makeBody: (Configuration) -> AnyView + + internal init(makeBody: @escaping (Configuration) -> AnyView) { + self._makeBody = makeBody + } + + public init(_ style: S) { + self.init { configuration in + AnyView(style.makeBody(configuration: configuration)) + } + } + + public func makeBody(configuration: Configuration) -> some View { + _makeBody(configuration) + } +} + +public struct HorizontalFlaggedMessageViewStyle: FlaggedMessageViewStyle { + + @ViewBuilder + public func makeBody(configuration: Configuration) -> some View { + if configuration.message != nil { + HStack { + FlaggedStatusLabel(status: configuration.status) + FlaggedMessageLabel(message: configuration.message) + } + } + } +} + +public struct VerticalFlaggedMessageViewStyle: FlaggedMessageViewStyle { + + @ViewBuilder + public func makeBody(configuration: Configuration) -> some View { + if configuration.message != nil { + VStack(alignment: .leading) { + FlaggedStatusLabel(status: configuration.status) + FlaggedMessageLabel(message: configuration.message) + } + } + } +} + +public struct AutomaticFlaggedMessageViewStyle: FlaggedMessageViewStyle { + + let horizontalSizeClass: UserInterfaceSizeClass? + + @ViewBuilder + public func makeBody(configuration: Configuration) -> some View { + if horizontalSizeClass == .compact { + VerticalFlaggedMessageViewStyle().makeBody(configuration: configuration) + } else { + HorizontalFlaggedMessageViewStyle().makeBody(configuration: configuration) + } + } +} + +extension FlaggedMessageViewStyle where Self == VerticalFlaggedMessageViewStyle { + public static var vertical: Self { .init() } +} + +extension FlaggedMessageViewStyle where Self == HorizontalFlaggedMessageViewStyle { + public static var horizontal: Self { .init() } +} + +extension FlaggedMessageViewStyle where Self == AutomaticFlaggedMessageViewStyle { + public static func automatic(horizontalSizeClass: UserInterfaceSizeClass? = nil) -> Self { + .init(horizontalSizeClass: horizontalSizeClass) + } +} + +private struct FlaggedMessageViewStyleKey: EnvironmentKey { + static var defaultValue = AnyFlaggedMessageViewStyle( + AutomaticFlaggedMessageViewStyle(horizontalSizeClass: nil) + ) +} + +extension EnvironmentValues { + public var flaggedMessageViewStyle: AnyFlaggedMessageViewStyle { + get { self[FlaggedMessageViewStyleKey.self] } + set { self[FlaggedMessageViewStyleKey.self] = newValue } + } +} + +extension View { + public func flaggedMessageViewStyle(_ style: AnyFlaggedMessageViewStyle) -> some View { + environment(\.flaggedMessageViewStyle, style) + } + + public func flaggedMessageViewStyle(_ style: S) -> some View { + flaggedMessageViewStyle(AnyFlaggedMessageViewStyle(style)) + } +} + +extension Flagged.CheckResult.Status { + + public var color: Color { + switch self { + case .good: + return .green + case .warning: + return .yellow + case .error: + return .red + } + } +} diff --git a/Sources/FlaggedViews/FlaggedStatusLabel.swift b/Sources/FlaggedViews/FlaggedStatusLabel.swift index bac7e70..3fc233e 100644 --- a/Sources/FlaggedViews/FlaggedStatusLabel.swift +++ b/Sources/FlaggedViews/FlaggedStatusLabel.swift @@ -23,6 +23,136 @@ public struct FlaggedStatusLabel: View { } } + +public protocol FlaggedStatusLabelStyle { + associatedtype Body: View + typealias Configuration = FlaggedStatusLabelStyleConfiguration + + func makeBody(configuration: Self.Configuration) -> Self.Body +} + +public struct FlaggedStatusLabelStyleConfiguration { + public let status: Flagged.CheckResult.Status +} + +public struct AnyFlaggedStatusLabelStyle: FlaggedStatusLabelStyle { + private let _makeBody: (Configuration) -> AnyView + + internal init(makeBody: @escaping (Configuration) -> AnyView) { + self._makeBody = makeBody + } + + public init(_ style: Style) { + self.init { configuration in + AnyView(style.makeBody(configuration: configuration)) + } + } + + public func makeBody(configuration: Configuration) -> some View { + _makeBody(configuration) + } +} + +public struct FlaggedStatusTextLabelStyle: FlaggedStatusLabelStyle { + + let textLabelStyle: AnyTextLabelStyle? + + public func makeBody(configuration: Configuration) -> some View { + TextLabel(configuration.status.title) + .textLabelStyle( + textLabelStyle + ?? AnyTextLabelStyle( + style: .colored(configuration.status.color).font(.caption, fontWeight: .bold) + ) + ) + } +} + +private struct FlaggedStatusLabelStyleKey: EnvironmentKey { + static var defaultValue = AnyFlaggedStatusLabelStyle( + FlaggedStatusTextLabelStyle( + textLabelStyle: AnyTextLabelStyle( + style: .font(.callout, fontWeight: .bold) + ) + ) + ) +} + +extension FlaggedStatusLabelStyle where Self == FlaggedStatusTextLabelStyle { + + public static var textLabel: Self { textLabel() } + + public static func textLabel( + _ style: AnyTextLabelStyle? = nil + ) -> Self { + .init(textLabelStyle: style) + } + + public static func textLabel( + _ style: S + ) -> Self { + .init(textLabelStyle: AnyTextLabelStyle(style: style)) + } +} + +public struct FlaggedStatusPillStyle: FlaggedStatusLabelStyle { + let labelStyle: AnyTextLabelStyle? + let opacity: Double + + public func makeBody(configuration: Configuration) -> some View { + TextLabel(configuration.status.title) + .textLabelStyle(labelStyle ?? AnyTextLabelStyle( + style: .font(.caption, fontWeight: .bold).combining(.colored(.white)) + ) + ) + .padding(.horizontal, 10) + .padding(.vertical, 5) + .background { + Capsule() + .foregroundStyle(configuration.status.color.opacity(opacity)) + } + } +} + +extension FlaggedStatusLabelStyle where Self == FlaggedStatusPillStyle { + + public static var pill: Self { .pill() } + + public static func pill( + _ style: AnyTextLabelStyle? = nil, + opacity: Double = 1 + ) -> Self { + .init(labelStyle: style, opacity: opacity) + } + + public static func pill(_ style: S, opacity: Double = 1) -> Self { + .init(labelStyle: AnyTextLabelStyle(style: style), opacity: opacity) + } +} + +extension EnvironmentValues { + public var flaggedStatusLabelStyle: AnyFlaggedStatusLabelStyle { + get { self[FlaggedStatusLabelStyleKey.self] } + set { self[FlaggedStatusLabelStyleKey.self] = newValue } + } +} + +extension View { + public func flaggedStatusLabelStyle( + _ style: AnyFlaggedStatusLabelStyle + ) -> some View { + environment( + \.flaggedStatusLabelStyle, + style + ) + + } + + public func flaggedStatusLabelStyle(_ style: S) -> some View { + flaggedStatusLabelStyle(AnyFlaggedStatusLabelStyle(style)) + } +} + #Preview { VStack { FlaggedStatusLabel(status: .warning) diff --git a/Sources/FlaggedViews/FlaggedView.swift b/Sources/FlaggedViews/FlaggedView.swift index 73c6fa4..c95397c 100644 --- a/Sources/FlaggedViews/FlaggedView.swift +++ b/Sources/FlaggedViews/FlaggedView.swift @@ -18,7 +18,10 @@ public struct FlaggedView: View { public var body: some View { flaggedViewStyle.makeBody( - configuration: FlaggedViewStyleConfiguration(flagged: flagged, label: .init(label())) + configuration: FlaggedViewStyleConfiguration( + flagged: flagged, + label: .init(label()) + ) ) } } @@ -38,3 +41,180 @@ extension FlaggedView where Label == EmptyView { self.init(flagged: flagged) { EmptyView() } } } + +public protocol FlaggedViewStyle { + associatedtype Body: View + typealias Configuration = FlaggedViewStyleConfiguration + + @ViewBuilder + func makeBody(configuration: Self.Configuration) -> Self.Body +} + +public struct FlaggedViewStyleConfiguration { + public let flagged: Flagged + public let label: Label + + /// A type erased label for a flagged view. + public struct Label: View { + public let body: AnyView + + public init(_ content: Content) { + self.body = AnyView(content) + } + } +} + +public struct AnyFlaggedViewStyle: FlaggedViewStyle { + private var _makeBody: (Configuration) -> AnyView + + internal init(makeBody: @escaping (Configuration) -> AnyView) { + self._makeBody = makeBody + } + + public init(style: S) { + self.init { configuration in + AnyView(style.makeBody(configuration: configuration)) + } + } + + public func makeBody(configuration: Configuration) -> some View { + _makeBody(configuration) + } +} + +public struct DefaultFlagViewStyle: FlaggedViewStyle { + + let alignment: HorizontalAlignment + let fractionLength: Int + let spacing: CGFloat + + init( + alignment: HorizontalAlignment = .leading, + fractionLength: Int = 2, + spacing: CGFloat = 8 + ) { + self.alignment = alignment + self.fractionLength = fractionLength + self.spacing = spacing + } + + public func makeBody(configuration: Configuration) -> some View { + VStack(alignment: alignment, spacing: spacing) { + HStack { + configuration.label + Spacer() + Text( + configuration.flagged.wrappedValue, + format: .number.precision(.fractionLength(fractionLength)) + ) + configuration.flagged.flagImage + } + FlaggedMessageView(flagged: configuration.flagged) + } + } +} + +public struct FlagAndMessageOnlyStyle: FlaggedViewStyle { + + public enum StackStyle { + case horizontal + case vertical + } + + let stackStyle: StackStyle + + @ViewBuilder + public func makeBody(configuration: Configuration) -> some View { + switch stackStyle { + case .horizontal: + HStack { + FlaggedMessageView(flagged: configuration.flagged) + configuration.flagged.flagImage + } + case .vertical: + VStack { + configuration.flagged.flagImage + FlaggedMessageView(flagged: configuration.flagged) + } + } + } +} + +extension FlaggedViewStyle where Self == FlagAndMessageOnlyStyle { + public static func flagAndMessageOnly(_ stackStyle: FlagAndMessageOnlyStyle.StackStyle = .vertical) -> Self { + .init(stackStyle: stackStyle) + } +} + +public struct FlaggedGridRowStyle: FlaggedViewStyle { + + let fractionLength: Int + + public func makeBody(configuration: Configuration) -> some View { + GridRow { + VStack(alignment: .leading, spacing: 8) { + configuration.label + .foregroundStyle(Color.secondary) + FlaggedMessageView(flagged: configuration.flagged) + } + Spacer() + HStack(spacing: 10) { + Text( + configuration.flagged.wrappedValue, + format: .number.precision(.fractionLength(fractionLength)) + ) + configuration.flagged.flagImage + } + .gridCellAnchor(.trailing) + } + } +} + +extension FlaggedViewStyle where Self == FlaggedGridRowStyle { + public static func gridRow(fractionLength: Int = 2) -> Self { + .init(fractionLength: fractionLength) + } +} + +private struct FlaggedViewStyleKey: EnvironmentKey { + static let defaultValue = AnyFlaggedViewStyle(style: DefaultFlagViewStyle()) +} + +extension EnvironmentValues { + public var flaggedViewStyle: AnyFlaggedViewStyle { + get { self[FlaggedViewStyleKey.self] } + set { self[FlaggedViewStyleKey.self] = newValue } + } +} + +extension FlaggedViewStyle where Self == DefaultFlagViewStyle { + public static func `default`(alignment: HorizontalAlignment = .leading, fractionLength: Int = 2, spacing: CGFloat = 8) -> Self { + .init(alignment: alignment, fractionLength: fractionLength, spacing: spacing) + } +} + + +extension Flagged { + public var statusColor: Color { self.status.color } + + public var flagImage: some View { + Image(systemName: "flag.fill") + .foregroundStyle(statusColor) + } + + public var messageView: some View { + FlaggedMessageView(flagged: self) + } +} + +extension View { + public func flaggedViewStyle(_ style: AnyFlaggedViewStyle) -> some View { + environment(\.flaggedViewStyle, style) + } + + public func flaggedViewStyle( + _ style: S + ) -> some View { + flaggedViewStyle(AnyFlaggedViewStyle(style: style)) + } +} diff --git a/Sources/FlaggedViews/Styles/FlaggedEquipmentMeasurementStyle.swift b/Sources/FlaggedViews/Styles/FlaggedEquipmentMeasurementStyle.swift deleted file mode 100644 index 33a2b61..0000000 --- a/Sources/FlaggedViews/Styles/FlaggedEquipmentMeasurementStyle.swift +++ /dev/null @@ -1,123 +0,0 @@ -import SharedModels -import SwiftUI - -public protocol FlaggedEquipmentMeasurementStyle { - associatedtype Body: View - typealias Configuration = FlaggedEquipmentMeasurementStyleConfiguration - - @ViewBuilder - func makeBody(configuration: Self.Configuration) -> Self.Body -} - -public struct FlaggedEquipmentMeasurementStyleConfiguration { - public let measurement: FlaggedEquipmentMeasurement -} - -public struct AnyFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { - private var _makeBody: (Configuration) -> AnyView - - internal init(makeBody: @escaping (Configuration) -> AnyView) { - self._makeBody = makeBody - } - - public init(_ style: S) { - self.init { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - public func makeBody(configuration: Configuration) -> some View { - _makeBody(configuration) - } -} - -public struct GridFlaggedEquipmentMeasurementStyle: FlaggedEquipmentMeasurementStyle { - - public func makeBody(configuration: Configuration) -> some View { - Grid(alignment: .leading, verticalSpacing: 20) { - ForEach(FlaggedEquipmentMeasurement.Key.allCases) { field in - FlaggedView( - field.title, - flagged: configuration.measurement[keyPath: field.flaggedKeyPath] - ) - .flaggedViewStyle(.gridRow(fractionLength: field == .airflow ? 0 : 2)) - } - } - } -} - -extension FlaggedEquipmentMeasurementStyle where Self == GridFlaggedEquipmentMeasurementStyle { - public static var grid: Self { .init() } -} - -private struct FlaggedEquipmentMeasurementStyleKey: EnvironmentKey { - static let defaultValue = AnyFlaggedEquipmentMeasurementStyle(GridFlaggedEquipmentMeasurementStyle()) -} - -extension EnvironmentValues { - public var flaggedEquipmentMeasurementStyle: AnyFlaggedEquipmentMeasurementStyle { - get { self[FlaggedEquipmentMeasurementStyleKey.self] } - set { self[FlaggedEquipmentMeasurementStyleKey.self] = newValue } - } -} - -extension View { - public func flaggedEquipmentMeasurementStyle(_ style: AnyFlaggedEquipmentMeasurementStyle) -> some View { - environment(\.flaggedEquipmentMeasurementStyle, style) - } - - public func flaggedEquipmentMeasurementStyle( - _ style: S - ) -> some View { - flaggedEquipmentMeasurementStyle(AnyFlaggedEquipmentMeasurementStyle(style)) - } -} - -// MARK: - Key -fileprivate extension FlaggedEquipmentMeasurement { - // NOTE: These need to be kept in display order. - enum Key: Hashable, CaseIterable, Identifiable { - case returnPlenum - case filterDrop - case coilDrop - case supplyPlenum - case staticPressure - case airflow - - var id: Self { self } - - var title: String { - switch self { - case .returnPlenum: - return "Return Plenum" - case .filterDrop: - return "Filter Pressure Drop" - case .coilDrop: - return "Coil Pressure Drop" - case .supplyPlenum: - return "Supply Plenum" - case .staticPressure: - return "External Static Pressure" - case .airflow: - return "System Airflow" - } - } - - var flaggedKeyPath: KeyPath { - switch self { - case .returnPlenum: - return \.returnPlenumPressure - case .filterDrop: - return \.filterPressureDrop - case .coilDrop: - return \.coilPressureDrop - case .supplyPlenum: - return \.supplyPlenumPressure - case .staticPressure: - return \.externalStaticPressure - case .airflow: - return \.airflow - } - } - } -} diff --git a/Sources/FlaggedViews/Styles/FlaggedMessageLabelStyle.swift b/Sources/FlaggedViews/Styles/FlaggedMessageLabelStyle.swift deleted file mode 100644 index 557c7c8..0000000 --- a/Sources/FlaggedViews/Styles/FlaggedMessageLabelStyle.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Styleguide -import SwiftUI - -private struct FlaggedMessageLabelStyleKey: EnvironmentKey { - static var defaultValue = AnyTextLabelStyle(style: .font(.caption)) -} - -extension EnvironmentValues { - var flaggedMessageLabelStyle: AnyTextLabelStyle { - get { self[FlaggedMessageLabelStyleKey.self] } - set { self[FlaggedMessageLabelStyleKey.self] = newValue } - } -} - -extension View { - public func flaggedMessageLabelStyle(_ style: AnyTextLabelStyle) -> some View { - environment(\.flaggedMessageLabelStyle, style) - } - - public func flaggedMessageLabelStyle(_ style: S) -> some View { - flaggedMessageLabelStyle(AnyTextLabelStyle(style: style)) - } -} - diff --git a/Sources/FlaggedViews/Styles/FlaggedMessageViewStyle.swift b/Sources/FlaggedViews/Styles/FlaggedMessageViewStyle.swift deleted file mode 100644 index ce4ddfa..0000000 --- a/Sources/FlaggedViews/Styles/FlaggedMessageViewStyle.swift +++ /dev/null @@ -1,130 +0,0 @@ -import SharedModels -import Styleguide -import SwiftUI - -public protocol FlaggedMessageViewStyle { - associatedtype Body: View - typealias Configuration = FlaggedMessageViewStyleConfiguration - - @ViewBuilder - func makeBody(configuration: Self.Configuration) -> Self.Body -} - -public struct FlaggedMessageViewStyleConfiguration { - public let message: String? - public let status: Flagged.CheckResult.Status - - public init(message: String?, status: Flagged.CheckResult.Status) { - self.message = message - self.status = status - } -} - -public struct AnyFlaggedMessageViewStyle: FlaggedMessageViewStyle { - private let _makeBody: (Configuration) -> AnyView - - internal init(makeBody: @escaping (Configuration) -> AnyView) { - self._makeBody = makeBody - } - - public init(_ style: S) { - self.init { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - public func makeBody(configuration: Configuration) -> some View { - _makeBody(configuration) - } -} - -public struct HorizontalFlaggedMessageViewStyle: FlaggedMessageViewStyle { - - @ViewBuilder - public func makeBody(configuration: Configuration) -> some View { - if configuration.message != nil { - HStack { - FlaggedStatusLabel(status: configuration.status) - FlaggedMessageLabel(message: configuration.message) - } - } - } -} - -public struct VerticalFlaggedMessageViewStyle: FlaggedMessageViewStyle { - - @ViewBuilder - public func makeBody(configuration: Configuration) -> some View { - if configuration.message != nil { - VStack(alignment: .leading) { - FlaggedStatusLabel(status: configuration.status) - FlaggedMessageLabel(message: configuration.message) - } - } - } -} - -public struct AutomaticFlaggedMessageViewStyle: FlaggedMessageViewStyle { - - let horizontalSizeClass: UserInterfaceSizeClass? - - @ViewBuilder - public func makeBody(configuration: Configuration) -> some View { - if horizontalSizeClass == .compact { - VerticalFlaggedMessageViewStyle().makeBody(configuration: configuration) - } else { - HorizontalFlaggedMessageViewStyle().makeBody(configuration: configuration) - } - } -} - -extension FlaggedMessageViewStyle where Self == VerticalFlaggedMessageViewStyle { - public static var vertical: Self { .init() } -} - -extension FlaggedMessageViewStyle where Self == HorizontalFlaggedMessageViewStyle { - public static var horizontal: Self { .init() } -} - -extension FlaggedMessageViewStyle where Self == AutomaticFlaggedMessageViewStyle { - public static func automatic(horizontalSizeClass: UserInterfaceSizeClass? = nil) -> Self { - .init(horizontalSizeClass: horizontalSizeClass) - } -} - -private struct FlaggedMessageViewStyleKey: EnvironmentKey { - static var defaultValue = AnyFlaggedMessageViewStyle( - AutomaticFlaggedMessageViewStyle(horizontalSizeClass: nil) - ) -} - -extension EnvironmentValues { - public var flaggedMessageViewStyle: AnyFlaggedMessageViewStyle { - get { self[FlaggedMessageViewStyleKey.self] } - set { self[FlaggedMessageViewStyleKey.self] = newValue } - } -} - -extension View { - public func flaggedMessageViewStyle(_ style: AnyFlaggedMessageViewStyle) -> some View { - environment(\.flaggedMessageViewStyle, style) - } - - public func flaggedMessageViewStyle(_ style: S) -> some View { - flaggedMessageViewStyle(AnyFlaggedMessageViewStyle(style)) - } -} - -extension Flagged.CheckResult.Status { - - public var color: Color { - switch self { - case .good: - return .green - case .warning: - return .yellow - case .error: - return .red - } - } -} diff --git a/Sources/FlaggedViews/Styles/FlaggedStatusLabelStyle.swift b/Sources/FlaggedViews/Styles/FlaggedStatusLabelStyle.swift deleted file mode 100644 index c1a636f..0000000 --- a/Sources/FlaggedViews/Styles/FlaggedStatusLabelStyle.swift +++ /dev/null @@ -1,132 +0,0 @@ -import SharedModels -import Styleguide -import SwiftUI - -public protocol FlaggedStatusLabelStyle { - associatedtype Body: View - typealias Configuration = FlaggedStatusLabelStyleConfiguration - - func makeBody(configuration: Self.Configuration) -> Self.Body -} - -public struct FlaggedStatusLabelStyleConfiguration { - public let status: Flagged.CheckResult.Status -} - -public struct AnyFlaggedStatusLabelStyle: FlaggedStatusLabelStyle { - private let _makeBody: (Configuration) -> AnyView - - internal init(makeBody: @escaping (Configuration) -> AnyView) { - self._makeBody = makeBody - } - - public init(_ style: Style) { - self.init { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - public func makeBody(configuration: Configuration) -> some View { - _makeBody(configuration) - } -} - -public struct FlaggedStatusTextLabelStyle: FlaggedStatusLabelStyle { - - let textLabelStyle: AnyTextLabelStyle? - - public func makeBody(configuration: Configuration) -> some View { - TextLabel(configuration.status.title) - .textLabelStyle( - textLabelStyle - ?? AnyTextLabelStyle( - style: .colored(configuration.status.color).font(.caption, fontWeight: .bold) - ) - ) - } -} - -private struct FlaggedStatusLabelStyleKey: EnvironmentKey { - static var defaultValue = AnyFlaggedStatusLabelStyle( - FlaggedStatusTextLabelStyle( - textLabelStyle: AnyTextLabelStyle( - style: .font(.callout, fontWeight: .bold) - ) - ) - ) -} - -extension FlaggedStatusLabelStyle where Self == FlaggedStatusTextLabelStyle { - - public static var textLabel: Self { textLabel() } - - public static func textLabel( - _ style: AnyTextLabelStyle? = nil - ) -> Self { - .init(textLabelStyle: style) - } - - public static func textLabel( - _ style: S - ) -> Self { - .init(textLabelStyle: AnyTextLabelStyle(style: style)) - } -} - -public struct FlaggedStatusPillStyle: FlaggedStatusLabelStyle { - let labelStyle: AnyTextLabelStyle? - let opacity: Double - - public func makeBody(configuration: Configuration) -> some View { - TextLabel(configuration.status.title) - .textLabelStyle(labelStyle ?? AnyTextLabelStyle( - style: .font(.caption, fontWeight: .bold).combining(.colored(.white)) - ) - ) - .padding(.horizontal, 10) - .padding(.vertical, 5) - .background { - Capsule() - .foregroundStyle(configuration.status.color.opacity(opacity)) - } - } -} - -extension FlaggedStatusLabelStyle where Self == FlaggedStatusPillStyle { - - public static var pill: Self { .pill() } - - public static func pill( - _ style: AnyTextLabelStyle? = nil, - opacity: Double = 1 - ) -> Self { - .init(labelStyle: style, opacity: opacity) - } - - public static func pill(_ style: S, opacity: Double = 1) -> Self { - .init(labelStyle: AnyTextLabelStyle(style: style), opacity: opacity) - } -} - -extension EnvironmentValues { - public var flaggedStatusLabelStyle: AnyFlaggedStatusLabelStyle { - get { self[FlaggedStatusLabelStyleKey.self] } - set { self[FlaggedStatusLabelStyleKey.self] = newValue } - } -} - -extension View { - public func flaggedStatusLabelStyle( - _ style: AnyFlaggedStatusLabelStyle - ) -> some View { - environment( - \.flaggedStatusLabelStyle, - style - ) - - } - - public func flaggedStatusLabelStyle(_ style: S) -> some View { - flaggedStatusLabelStyle(AnyFlaggedStatusLabelStyle(style)) - } -} diff --git a/Sources/FlaggedViews/Styles/FlaggedViewStyle.swift b/Sources/FlaggedViews/Styles/FlaggedViewStyle.swift deleted file mode 100644 index 24a5c2b..0000000 --- a/Sources/FlaggedViews/Styles/FlaggedViewStyle.swift +++ /dev/null @@ -1,179 +0,0 @@ -import SharedModels -import SwiftUI - -public protocol FlaggedViewStyle { - associatedtype Body: View - typealias Configuration = FlaggedViewStyleConfiguration - - @ViewBuilder - func makeBody(configuration: Self.Configuration) -> Self.Body -} - -public struct FlaggedViewStyleConfiguration { - public let flagged: Flagged - public let label: Label - - /// A type erased label for a flagged view. - public struct Label: View { - public let body: AnyView - - public init(_ content: Content) { - self.body = AnyView(content) - } - } -} - -public struct AnyFlaggedViewStyle: FlaggedViewStyle { - private var _makeBody: (Configuration) -> AnyView - - internal init(makeBody: @escaping (Configuration) -> AnyView) { - self._makeBody = makeBody - } - - public init(style: S) { - self.init { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - public func makeBody(configuration: Configuration) -> some View { - _makeBody(configuration) - } -} - -public struct DefaultFlagViewStyle: FlaggedViewStyle { - - let alignment: HorizontalAlignment - let fractionLength: Int - let spacing: CGFloat - - init( - alignment: HorizontalAlignment = .leading, - fractionLength: Int = 2, - spacing: CGFloat = 8 - ) { - self.alignment = alignment - self.fractionLength = fractionLength - self.spacing = spacing - } - - public func makeBody(configuration: Configuration) -> some View { - VStack(alignment: alignment, spacing: spacing) { - HStack { - configuration.label - Spacer() - Text( - configuration.flagged.wrappedValue, - format: .number.precision(.fractionLength(fractionLength)) - ) - configuration.flagged.flagImage - } - FlaggedMessageView(flagged: configuration.flagged) - } - } -} - -public struct FlagAndMessageOnlyStyle: FlaggedViewStyle { - - public enum StackStyle { - case horizontal - case vertical - } - - let stackStyle: StackStyle - - @ViewBuilder - public func makeBody(configuration: Configuration) -> some View { - switch stackStyle { - case .horizontal: - HStack { - FlaggedMessageView(flagged: configuration.flagged) - configuration.flagged.flagImage - } - case .vertical: - VStack { - configuration.flagged.flagImage - FlaggedMessageView(flagged: configuration.flagged) - } - } - } -} - -extension FlaggedViewStyle where Self == FlagAndMessageOnlyStyle { - public static func flagAndMessageOnly(_ stackStyle: FlagAndMessageOnlyStyle.StackStyle = .vertical) -> Self { - .init(stackStyle: stackStyle) - } -} - -public struct FlaggedGridRowStyle: FlaggedViewStyle { - - let fractionLength: Int - - public func makeBody(configuration: Configuration) -> some View { - GridRow { - VStack(alignment: .leading, spacing: 8) { - configuration.label - .foregroundStyle(Color.secondary) - FlaggedMessageView(flagged: configuration.flagged) - } - Spacer() - HStack(spacing: 10) { - Text( - configuration.flagged.wrappedValue, - format: .number.precision(.fractionLength(fractionLength)) - ) - configuration.flagged.flagImage - } - .gridCellAnchor(.trailing) - } - } -} - -extension FlaggedViewStyle where Self == FlaggedGridRowStyle { - public static func gridRow(fractionLength: Int = 2) -> Self { - .init(fractionLength: fractionLength) - } -} - -private struct FlaggedViewStyleKey: EnvironmentKey { - static let defaultValue = AnyFlaggedViewStyle(style: DefaultFlagViewStyle()) -} - -extension EnvironmentValues { - public var flaggedViewStyle: AnyFlaggedViewStyle { - get { self[FlaggedViewStyleKey.self] } - set { self[FlaggedViewStyleKey.self] = newValue } - } -} - -extension FlaggedViewStyle where Self == DefaultFlagViewStyle { - public static func `default`(alignment: HorizontalAlignment = .leading, fractionLength: Int = 2, spacing: CGFloat = 8) -> Self { - .init(alignment: alignment, fractionLength: fractionLength, spacing: spacing) - } -} - - -extension Flagged { - public var statusColor: Color { self.status.color } - - public var flagImage: some View { - Image(systemName: "flag.fill") - .foregroundStyle(statusColor) - } - - public var messageView: some View { - FlaggedMessageView(flagged: self) - } -} - -extension View { - public func flaggedViewStyle(_ style: AnyFlaggedViewStyle) -> some View { - environment(\.flaggedViewStyle, style) - } - - public func flaggedViewStyle( - _ style: S - ) -> some View { - flaggedViewStyle(AnyFlaggedViewStyle(style: style)) - } -}