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) } 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 { var flagColor: Color { self.status.flagColor } public var flagImage: some View { Image(systemName: "flag.fill") .foregroundStyle(flagColor) } 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)) } }