106 lines
2.9 KiB
Swift
106 lines
2.9 KiB
Swift
import SwiftUI
|
||
import LabWiseKit
|
||
|
||
// MARK: - Chemical row
|
||
|
||
struct ChemicalRowView: View {
|
||
let chemical: Chemical
|
||
|
||
var body: some View {
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
HStack {
|
||
Text(chemical.displayName)
|
||
.font(.headline)
|
||
Spacer()
|
||
if let state = chemical.physicalState, !state.isEmpty {
|
||
PhysicalStateBadge(state: state)
|
||
}
|
||
}
|
||
HStack(spacing: 6) {
|
||
if let cas = chemical.casNumber, !cas.isEmpty {
|
||
Text("CAS: \(cas)")
|
||
.font(.caption)
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
if chemical.isMissingKeyInfo {
|
||
MissingInfoBadge()
|
||
}
|
||
}
|
||
if let pct = chemical.percentageFull {
|
||
PercentageBar(value: pct / 100)
|
||
.frame(height: 4)
|
||
.padding(.top, 2)
|
||
}
|
||
}
|
||
.padding(.vertical, 2)
|
||
}
|
||
}
|
||
|
||
// MARK: - Physical state badge
|
||
|
||
struct PhysicalStateBadge: View {
|
||
let state: String
|
||
|
||
var color: Color {
|
||
switch state.lowercased() {
|
||
case "liquid": return Color(.brandPrimary)
|
||
case "solid": return Color(red: 0.42, green: 0.30, blue: 0.18)
|
||
case "gas": return Color(red: 0.22, green: 0.56, blue: 0.52)
|
||
default: return Color(.brandMutedForeground)
|
||
}
|
||
}
|
||
|
||
var body: some View {
|
||
Text(state.capitalized)
|
||
.font(.caption2.weight(.semibold))
|
||
.padding(.horizontal, 8)
|
||
.padding(.vertical, 3)
|
||
.background(color.opacity(0.15))
|
||
.foregroundStyle(color)
|
||
.clipShape(Capsule())
|
||
}
|
||
}
|
||
|
||
// MARK: - Missing info badge
|
||
|
||
/// Mirrors the web "Missing info" pill — amber, with a warning glyph.
|
||
struct MissingInfoBadge: View {
|
||
var body: some View {
|
||
HStack(spacing: 3) {
|
||
Image(systemName: "exclamationmark.triangle.fill")
|
||
.font(.caption2)
|
||
Text("Missing info")
|
||
.font(.caption2.weight(.semibold))
|
||
}
|
||
.padding(.horizontal, 6)
|
||
.padding(.vertical, 2)
|
||
.background(Color.orange.opacity(0.15))
|
||
.foregroundStyle(Color.orange)
|
||
.clipShape(Capsule())
|
||
}
|
||
}
|
||
|
||
// MARK: - Percentage bar
|
||
|
||
struct PercentageBar: View {
|
||
let value: Double // 0.0 – 1.0
|
||
|
||
var body: some View {
|
||
GeometryReader { geo in
|
||
ZStack(alignment: .leading) {
|
||
RoundedRectangle(cornerRadius: 2)
|
||
.fill(Color.secondary.opacity(0.2))
|
||
RoundedRectangle(cornerRadius: 2)
|
||
.fill(barColor)
|
||
.frame(width: geo.size.width * max(0, min(1, value)))
|
||
}
|
||
}
|
||
}
|
||
|
||
var barColor: Color {
|
||
if value > 0.6 { return .green }
|
||
if value > 0.25 { return .yellow }
|
||
return .red
|
||
}
|
||
}
|