import SharedModels import SwiftUI public struct FlaggedView: View { @Environment(\.flaggedViewStyle) private var flaggedViewStyle let label: () -> Label let flagged: Flagged public init( flagged: Flagged, @ViewBuilder label: @escaping () -> Label ) { self.label = label self.flagged = flagged } public var body: some View { flaggedViewStyle.makeBody( configuration: FlaggedViewStyleConfiguration( flagged: flagged, label: .init(label()) ) ) } } extension FlaggedView where Label == Text { public init(_ title: LocalizedStringKey, flagged: Flagged) { self.init(flagged: flagged) { Text(title) } } public init(_ title: S, flagged: Flagged) { self.init(flagged: flagged) { Text(title) } } } extension FlaggedView where Label == EmptyView { public init(flagged: Flagged) { self.init(flagged: flagged) { EmptyView() } } } @MainActor public protocol FlaggedViewStyle { associatedtype Body: View typealias Configuration = FlaggedViewStyleConfiguration @ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body } @MainActor 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) } } @MainActor 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 = MainActor.assumeIsolated { 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) } @MainActor 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)) } }