Files
swift-estimated-pressures-core/Sources/FlaggedViews/FlaggedView.swift

221 lines
5.4 KiB
Swift

import SharedModels
import SwiftUI
public struct FlaggedView<Label: View>: 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<S: StringProtocol>(_ title: S, flagged: Flagged) {
self.init(flagged: flagged) { Text(title) }
}
}
extension FlaggedView where Label == EmptyView {
public init(flagged: Flagged) {
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: 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)
}
}
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<S: FlaggedViewStyle>(
_ style: S
) -> some View {
flaggedViewStyle(AnyFlaggedViewStyle(style: style))
}
}