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) } } 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.CheckResult.Key { public var flagColor: Color { switch self { case .good: return .green case .warning: return .yellow case .error: return .red } } } extension Flagged { public var flagColor: Color { projectedValue.key.flagColor } public var flagImage: some View { Image(systemName: "flag.fill") .foregroundStyle(flagColor) } public var messageView: some View { flaggedMessageView(flagged: self) } } @ViewBuilder private func flaggedMessageView(flagged: Flagged) -> some View { if let message = flagged.message { HStack { Text(flagged.projectedValue.key.title) .bold() .foregroundStyle(flagged.projectedValue.key.flagColor) TextLabel(message) } .font(.caption) } } extension View { public func flaggedViewStyle(_ style: AnyFlaggedViewStyle) -> some View { environment(\.flaggedViewStyle, style) } public func flaggedViewStyle( _ style: S ) -> some View { environment(\.flaggedViewStyle, AnyFlaggedViewStyle(style: style)) } }