Files
Pulse/Sources/PulseUI/Views/KeyValueSectionView.swift
T
2021-11-08 16:45:55 -05:00

235 lines
6.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The MIT License (MIT)
//
// Copyright (c) 20202021 Alexander Grebenyuk (github.com/kean).
import SwiftUI
@available(iOS 13.0, tvOS 14.0, watchOS 6, *)
struct KeyValueSectionView: View {
let model: KeyValueSectionViewModel
var limit: Int = Int.max
private var actualTintColor: Color {
model.items.isEmpty ? .gray : model.color
}
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(model.title)
.font(.headline)
Spacer()
#if os(iOS)
if let action = model.action {
Button(action: action.action, label: {
Text(action.title)
Image(systemName: "chevron.right")
.foregroundColor(Color.gray)
.font(.caption)
.padding(.top, 2)
})
.padding(EdgeInsets(top: 7, leading: 11, bottom: 7, trailing: 11))
.background(Color.secondaryFill)
.cornerRadius(20)
}
#endif
}
#if os(watchOS)
KeyValueListView(model: model, limit: limit)
.padding(.top, 6)
.border(width: 2, edges: [.top], color: actualTintColor)
.padding(.top, 2)
if let action = model.action {
Spacer().frame(height: 10)
Button(action: action.action, label: {
Text(action.title)
Image(systemName: "chevron.right")
.foregroundColor(.separator)
.font(.caption)
})
}
#else
KeyValueListView(model: model, limit: limit)
.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 0))
.border(width: 2, edges: [.leading], color: actualTintColor)
.padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 0))
#endif
}
}
}
@available(iOS 13.0, tvOS 14.0, watchOS 6, *)
private struct KeyValueListView: View {
let model: KeyValueSectionViewModel
var limit: Int = Int.max
private var actualTintColor: Color {
model.items.isEmpty ? .gray : model.color
}
var items: [(String, String?)] {
var items = Array(model.items.prefix(limit))
if model.items.count > limit {
items.append(("And \(model.items.count - limit) more", "..."))
}
return items
}
#if os(macOS)
var body: some View {
if model.items.isEmpty {
HStack {
Text("Empty")
.foregroundColor(actualTintColor)
.font(.system(size: fontSize, weight: .medium))
}
} else {
Label(text: text)
.padding(.bottom, 5)
}
}
private var text: NSAttributedString {
let text = NSMutableAttributedString()
for (index, row) in items.enumerated() {
text.append(makeRow(row))
if index != items.indices.last {
text.append("\n")
}
}
return text
}
private func makeRow(_ row: (String, String?)) -> NSAttributedString {
let text = NSMutableAttributedString()
text.append(row.0 + ": ", [
.font: NSFont.systemFont(ofSize: fontSize, weight: .medium),
.foregroundColor: NSColor(actualTintColor),
.paragraphStyle: ps
])
text.append(row.1 ?? "", [
.font: NSFont.systemFont(ofSize: fontSize),
.foregroundColor: NSColor(Color.primary),
.paragraphStyle: ps
])
return text
}
#else
var body: some View {
if model.items.isEmpty {
HStack {
Text("Empty")
.foregroundColor(actualTintColor)
.font(.system(size: fontSize, weight: .medium))
}
} else {
VStack(spacing: 2) {
let rows = items.enumerated().map(Row.init)
ForEach(rows, id: \.index, content: makeRow)
}
}
}
private func makeRow(_ row: Row) -> some View {
HStack {
let title = Text(row.item.0 + ": ")
.foregroundColor(actualTintColor)
.font(.system(size: fontSize, weight: .medium))
let value = Text(row.item.1 ?? "")
.foregroundColor(.primary)
.font(.system(size: fontSize, weight: .regular))
#if os(watchOS)
VStack(alignment: .leading) {
title
value
.lineLimit(2)
}
#elseif os(tvOS)
(title + value)
.lineLimit(nil)
#else
(title + value)
.lineLimit(row.item.0 == "URL" ? 10 : 4)
.contextMenu(ContextMenu(menuItems: {
Button(action: {
UXPasteboard.general.string = "\(row.item.0): \(row.item.1 ?? "")"
runHapticFeedback()
}) {
Text("Copy")
Image(systemName: "doc.on.doc")
}
Button(action: {
UXPasteboard.general.string = row.item.1
runHapticFeedback()
}) {
Text("Copy Value")
Image(systemName: "doc.on.doc")
}
}))
#endif
Spacer()
}
}
#endif
}
#if os(macOS)
private struct Label: NSViewRepresentable {
let text: NSAttributedString
func makeNSView(context: Context) -> NSTextField {
let label = NSTextField.label()
label.isSelectable = true
label.attributedStringValue = text
label.allowsEditingTextAttributes = true
label.lineBreakMode = .byCharWrapping
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return label
}
func updateNSView(_ nsView: NSTextField, context: Context) {
}
}
private let ps: NSParagraphStyle = {
let ps = NSMutableParagraphStyle()
ps.minimumLineHeight = 20
ps.maximumLineHeight = 20
return ps
}()
#endif
private var fontSize: CGFloat {
#if os(iOS)
return 15
#elseif os(watchOS)
return 14
#elseif os(tvOS)
return 28
#else
return 12
#endif
}
@available(iOS 13.0, tvOS 14.0, watchOS 6, *)
struct KeyValueSectionViewModel {
let title: String
let color: Color
var action: ActionViewModel?
let items: [(String, String?)]
}
private struct Row {
let index: Int
let item: (String, String?)
}
struct ActionViewModel {
let action: () -> Void
let title: String
}