177 lines
4.2 KiB
Swift
177 lines
4.2 KiB
Swift
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: View>(_ 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<S: FlaggedViewStyle>(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<S: FlaggedViewStyle>(
|
|
_ style: S
|
|
) -> some View {
|
|
environment(\.flaggedViewStyle, AnyFlaggedViewStyle(style: style))
|
|
}
|
|
}
|