import CodexBarCoreimport SwiftUI @MainActorstruct ProviderDetailView: View { let provider: UsageProvider @Bindable var store: UsageStore @Binding var isEnabled: Bool let subtitle: String let model: UsageMenuCardView.Model let settingsPickers: [ProviderSettingsPickerDescriptor] let settingsToggles: [ProviderSettingsToggleDescriptor] let settingsFields: [ProviderSettingsFieldDescriptor] let settingsTokenAccounts: ProviderSettingsTokenAccountsDescriptor? let errorDisplay: ProviderErrorDisplay? @Binding var isErrorExpanded: Bool let onCopyError: (String) -> Void let onRefresh: () -> Void var body: some View { ScrollView { VStack(alignment: .leading, spacing: 16) { let labelWidth = self.detailLabelWidth ProviderDetailHeaderView( provider: self.provider, store: self.store, isEnabled: self.$isEnabled, subtitle: self.subtitle, model: self.model, labelWidth: labelWidth, onRefresh: self.onRefresh) ProviderMetricsInlineView( provider: self.provider, model: self.model, isEnabled: self.isEnabled, labelWidth: labelWidth) if let errorDisplay { ProviderErrorView( title: "Last \(self.store.metadata(for: self.provider).displayName) fetch failed:", display: errorDisplay, isExpanded: self.$isErrorExpanded, onCopy: { self.onCopyError(errorDisplay.full) }) } if self.hasSettings { ProviderSettingsSection(title: "Settings") { ForEach(self.settingsPickers) { picker in ProviderSettingsPickerRowView(picker: picker) } if let tokenAccounts = self.settingsTokenAccounts, tokenAccounts.isVisible?() ?? true { ProviderSettingsTokenAccountsRowView(descriptor: tokenAccounts) } ForEach(self.settingsFields) { field in ProviderSettingsFieldRowView(field: field) } } } if !self.settingsToggles.isEmpty { ProviderSettingsSection(title: "Options") { ForEach(self.settingsToggles) { toggle in ProviderSettingsToggleRowView(toggle: toggle) } } } } .frame(maxWidth: ProviderSettingsMetrics.detailMaxWidth, alignment: .leading) .padding(.vertical, 12) .padding(.horizontal, 8) } .frame(maxWidth: .infinity, alignment: .leading) } private var hasSettings: Bool { !self.settingsPickers.isEmpty || !self.settingsFields.isEmpty || self.settingsTokenAccounts != nil } private var detailLabelWidth: CGFloat { var infoLabels = ["State", "Source", "Version", "Updated"] if self.store.status(for: self.provider) != nil { infoLabels.append("Status") } if !self.model.email.isEmpty { infoLabels.append("Account") } if let plan = self.model.planText, !plan.isEmpty { infoLabels.append("Plan") } var metricLabels = self.model.metrics.map(\.title) if self.model.creditsText != nil { metricLabels.append("Credits") } if let providerCost = self.model.providerCost { metricLabels.append(providerCost.title) } if self.model.tokenUsage != nil { metricLabels.append("Cost") } let infoWidth = ProviderSettingsMetrics.labelWidth( for: infoLabels, font: ProviderSettingsMetrics.infoLabelFont()) let metricWidth = ProviderSettingsMetrics.labelWidth( for: metricLabels, font: ProviderSettingsMetrics.metricLabelFont()) return max(infoWidth, metricWidth) }} @MainActorprivate struct ProviderDetailHeaderView: View { let provider: UsageProvider @Bindable var store: UsageStore @Binding var isEnabled: Bool let subtitle: String let model: UsageMenuCardView.Model let labelWidth: CGFloat let onRefresh: () -> Void var body: some View { VStack(alignment: .leading, spacing: 12) { HStack(alignment: .center, spacing: 12) { ProviderDetailBrandIcon(provider: self.provider) VStack(alignment: .leading, spacing: 4) { Text(self.store.metadata(for: self.provider).displayName) .font(.title3.weight(.semibold)) Text(self.detailSubtitle) .font(.footnote) .foregroundStyle(.secondary) } Spacer(minLength: 12) Button { self.onRefresh() } label: { Image(systemName: "arrow.clockwise") } .buttonStyle(.bordered) .controlSize(.small) .help("Refresh") Toggle("", isOn: self.$isEnabled) .labelsHidden() .toggleStyle(.switch) .controlSize(.small) } ProviderDetailInfoGrid( provider: self.provider, store: self.store, isEnabled: self.isEnabled, model: self.model, labelWidth: self.labelWidth) } } private var detailSubtitle: String { let lines = self.subtitle.split(separator: "\n", omittingEmptySubsequences: false) guard lines.count >= 2 else { return self.subtitle } let first = lines[0] let rest = lines.dropFirst().joined(separator: "\n") let tail = rest.trimmingCharacters(in: .whitespacesAndNewlines) if tail.isEmpty { return String(first) } return "\(first) • \(tail)" }} @MainActorprivate struct ProviderDetailBrandIcon: View { let provider: UsageProvider var body: some View { if let brand = ProviderBrandIcon.image(for: self.provider) { Image(nsImage: brand) .resizable() .scaledToFit() .frame(width: 28, height: 28) .foregroundStyle(.secondary) .accessibilityHidden(true) } else { Image(systemName: "circle.dotted") .font(.system(size: 24, weight: .regular)) .foregroundStyle(.secondary) .accessibilityHidden(true) } }} @MainActorprivate struct ProviderDetailInfoGrid: View { let provider: UsageProvider @Bindable var store: UsageStore let isEnabled: Bool let model: UsageMenuCardView.Model let labelWidth: CGFloat var body: some View { let status = self.store.status(for: self.provider) let source = self.store.sourceLabel(for: self.provider) let version = self.store.version(for: self.provider) ?? "not detected" let updated = self.updatedText let email = self.model.email let plan = self.model.planText ?? "" let enabledText = self.isEnabled ? "Enabled" : "Disabled" Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 6) { ProviderDetailInfoRow(label: "State", value: enabledText, labelWidth: self.labelWidth) ProviderDetailInfoRow(label: "Source", value: source, labelWidth: self.labelWidth) ProviderDetailInfoRow(label: "Version", value: version, labelWidth: self.labelWidth) ProviderDetailInfoRow(label: "Updated", value: updated, labelWidth: self.labelWidth) if let status { ProviderDetailInfoRow( label: "Status", value: status.description ?? status.indicator.label, labelWidth: self.labelWidth) } if !email.isEmpty { ProviderDetailInfoRow(label: "Account", value: email, labelWidth: self.labelWidth) } if !plan.isEmpty { ProviderDetailInfoRow(label: "Plan", value: plan, labelWidth: self.labelWidth) } } .font(.footnote) .foregroundStyle(.secondary) } private var updatedText: String { if let updated = self.store.snapshot(for: self.provider)?.updatedAt { return UsageFormatter.updatedString(from: updated) } if self.store.refreshingProviders.contains(self.provider) { return "Refreshing" } return "Not fetched yet" }} private struct ProviderDetailInfoRow: View { let label: String let value: String let labelWidth: CGFloat var body: some View { GridRow { Text(self.label) .frame(width: self.labelWidth, alignment: .leading) Text(self.value) .lineLimit(2) } }} @MainActorstruct ProviderMetricsInlineView: View { let provider: UsageProvider let model: UsageMenuCardView.Model let isEnabled: Bool let labelWidth: CGFloat var body: some View { ProviderSettingsSection( title: "Usage", spacing: 8, verticalPadding: 6, horizontalPadding: 0) { if self.model.metrics.isEmpty, self.model.providerCost == nil, self.model.creditsText == nil, self.model.tokenUsage == nil { Text(self.placeholderText) .font(.footnote) .foregroundStyle(.secondary) } else { ForEach(self.model.metrics, id: \.id) { metric in ProviderMetricInlineRow( metric: metric, progressColor: self.model.progressColor, labelWidth: self.labelWidth) } if let credits = self.model.creditsText { ProviderMetricInlineTextRow( title: "Credits", value: credits, labelWidth: self.labelWidth) } if let providerCost = self.model.providerCost { ProviderMetricInlineCostRow( section: providerCost, progressColor: self.model.progressColor, labelWidth: self.labelWidth) } if let tokenUsage = self.model.tokenUsage { ProviderMetricInlineTextRow( title: "Cost", value: tokenUsage.sessionLine, labelWidth: self.labelWidth) ProviderMetricInlineTextRow( title: "", value: tokenUsage.monthLine, labelWidth: self.labelWidth) } } } } private var placeholderText: String { if !self.isEnabled { return "Disabled — no recent data" } return self.model.placeholder ?? "No usage yet" }} private struct ProviderMetricInlineRow: View { let metric: UsageMenuCardView.Model.Metric let progressColor: Color let labelWidth: CGFloat var body: some View { HStack(alignment: .top, spacing: 10) { Text(self.metric.title) .font(.subheadline.weight(.semibold)) .lineLimit(1) .frame(width: self.labelWidth, alignment: .leading) VStack(alignment: .leading, spacing: 4) { UsageProgressBar( percent: self.metric.percent, tint: self.progressColor, accessibilityLabel: self.metric.percentStyle.accessibilityLabel, pacePercent: self.metric.pacePercent, paceOnTop: self.metric.paceOnTop) .frame(minWidth: ProviderSettingsMetrics.metricBarWidth, maxWidth: .infinity) HStack(alignment: .firstTextBaseline, spacing: 8) { Text(self.metric.percentLabel) .font(.footnote) .foregroundStyle(.secondary) .monospacedDigit() Spacer(minLength: 8) if let resetText = self.metric.resetText, !resetText.isEmpty { Text(resetText) .font(.footnote) .foregroundStyle(.secondary) } } let hasLeftDetail = self.metric.detailLeftText?.isEmpty == false let hasRightDetail = self.metric.detailRightText?.isEmpty == false if hasLeftDetail || hasRightDetail { HStack(alignment: .firstTextBaseline, spacing: 8) { if let leftDetail = self.metric.detailLeftText, !leftDetail.isEmpty { Text(leftDetail) .font(.footnote) .foregroundStyle(.secondary) } Spacer(minLength: 8) if let rightDetail = self.metric.detailRightText, !rightDetail.isEmpty { Text(rightDetail) .font(.footnote) .foregroundStyle(.secondary) } } } if let detail = self.detailText, !detail.isEmpty { Text(detail) .font(.footnote) .foregroundStyle(.tertiary) } } .frame(maxWidth: .infinity, alignment: .leading) } .padding(.vertical, 2) } private var detailText: String? { guard let detailText = self.metric.detailText, !detailText.isEmpty else { return nil } return detailText }} private struct ProviderMetricInlineTextRow: View { let title: String let value: String let labelWidth: CGFloat var body: some View { HStack(alignment: .firstTextBaseline, spacing: 12) { Text(self.title) .font(.subheadline.weight(.semibold)) .frame(width: self.labelWidth, alignment: .leading) Text(self.value) .font(.footnote) .foregroundStyle(.secondary) Spacer(minLength: 0) } .padding(.vertical, 1) }} private struct ProviderMetricInlineCostRow: View { let section: UsageMenuCardView.Model.ProviderCostSection let progressColor: Color let labelWidth: CGFloat var body: some View { HStack(alignment: .top, spacing: 10) { Text(self.section.title) .font(.subheadline.weight(.semibold)) .frame(width: self.labelWidth, alignment: .leading) VStack(alignment: .leading, spacing: 4) { UsageProgressBar( percent: self.section.percentUsed, tint: self.progressColor, accessibilityLabel: "Usage used") .frame(minWidth: ProviderSettingsMetrics.metricBarWidth, maxWidth: .infinity) HStack(alignment: .firstTextBaseline, spacing: 8) { Text(String(format: "%.0f%% used", self.section.percentUsed)) .font(.footnote) .foregroundStyle(.secondary) .monospacedDigit() Spacer(minLength: 8) Text(self.section.spendLine) .font(.footnote) .foregroundStyle(.secondary) } } Spacer(minLength: 0) } .padding(.vertical, 2) }}
import CodexBarCore
import SwiftUI
@MainActor
struct ProviderDetailView: View {
let provider: UsageProvider
@Bindable var store: UsageStore
@Binding var isEnabled: Bool
let subtitle: String
let model: UsageMenuCardView.Model
let settingsPickers: [ProviderSettingsPickerDescriptor]
let settingsToggles: [ProviderSettingsToggleDescriptor]
let settingsFields: [ProviderSettingsFieldDescriptor]
let settingsTokenAccounts: ProviderSettingsTokenAccountsDescriptor?
let errorDisplay: ProviderErrorDisplay?
@Binding var isErrorExpanded: Bool
let onCopyError: (String) -> Void
let onRefresh: () -> Void
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
let labelWidth = self.detailLabelWidth
ProviderDetailHeaderView(
provider: self.provider,
store: self.store,
isEnabled: self.$isEnabled,
subtitle: self.subtitle,
model: self.model,
labelWidth: labelWidth,
onRefresh: self.onRefresh)
ProviderMetricsInlineView(
provider: self.provider,
model: self.model,
isEnabled: self.isEnabled,
labelWidth: labelWidth)
if let errorDisplay {
ProviderErrorView(
title: "Last \(self.store.metadata(for: self.provider).displayName) fetch failed:",
display: errorDisplay,
isExpanded: self.$isErrorExpanded,
onCopy: { self.onCopyError(errorDisplay.full) })
}
if self.hasSettings {
ProviderSettingsSection(title: "Settings") {
ForEach(self.settingsPickers) { picker in
ProviderSettingsPickerRowView(picker: picker)
}
if let tokenAccounts = self.settingsTokenAccounts,
tokenAccounts.isVisible?() ?? true
{
ProviderSettingsTokenAccountsRowView(descriptor: tokenAccounts)
}
ForEach(self.settingsFields) { field in
ProviderSettingsFieldRowView(field: field)
}
}
}
if !self.settingsToggles.isEmpty {
ProviderSettingsSection(title: "Options") {
ForEach(self.settingsToggles) { toggle in
ProviderSettingsToggleRowView(toggle: toggle)
}
}
}
}
.frame(maxWidth: ProviderSettingsMetrics.detailMaxWidth, alignment: .leading)
.padding(.vertical, 12)
.padding(.horizontal, 8)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
private var hasSettings: Bool {
!self.settingsPickers.isEmpty ||
!self.settingsFields.isEmpty ||
self.settingsTokenAccounts != nil
}
private var detailLabelWidth: CGFloat {
var infoLabels = ["State", "Source", "Version", "Updated"]
if self.store.status(for: self.provider) != nil {
infoLabels.append("Status")
}
if !self.model.email.isEmpty {
infoLabels.append("Account")
}
if let plan = self.model.planText, !plan.isEmpty {
infoLabels.append("Plan")
}
var metricLabels = self.model.metrics.map(\.title)
if self.model.creditsText != nil {
metricLabels.append("Credits")
}
if let providerCost = self.model.providerCost {
metricLabels.append(providerCost.title)
}
if self.model.tokenUsage != nil {
metricLabels.append("Cost")
}
let infoWidth = ProviderSettingsMetrics.labelWidth(
for: infoLabels,
font: ProviderSettingsMetrics.infoLabelFont())
let metricWidth = ProviderSettingsMetrics.labelWidth(
for: metricLabels,
font: ProviderSettingsMetrics.metricLabelFont())
return max(infoWidth, metricWidth)
}
}
@MainActor
private struct ProviderDetailHeaderView: View {
let provider: UsageProvider
@Bindable var store: UsageStore
@Binding var isEnabled: Bool
let subtitle: String
let model: UsageMenuCardView.Model
let labelWidth: CGFloat
let onRefresh: () -> Void
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .center, spacing: 12) {
ProviderDetailBrandIcon(provider: self.provider)
VStack(alignment: .leading, spacing: 4) {
Text(self.store.metadata(for: self.provider).displayName)
.font(.title3.weight(.semibold))
Text(self.detailSubtitle)
.font(.footnote)
.foregroundStyle(.secondary)
}
Spacer(minLength: 12)
Button {
self.onRefresh()
} label: {
Image(systemName: "arrow.clockwise")
}
.buttonStyle(.bordered)
.controlSize(.small)
.help("Refresh")
Toggle("", isOn: self.$isEnabled)
.labelsHidden()
.toggleStyle(.switch)
.controlSize(.small)
}
ProviderDetailInfoGrid(
provider: self.provider,
store: self.store,
isEnabled: self.isEnabled,
model: self.model,
labelWidth: self.labelWidth)
}
}
private var detailSubtitle: String {
let lines = self.subtitle.split(separator: "\n", omittingEmptySubsequences: false)
guard lines.count >= 2 else { return self.subtitle }
let first = lines[0]
let rest = lines.dropFirst().joined(separator: "\n")
let tail = rest.trimmingCharacters(in: .whitespacesAndNewlines)
if tail.isEmpty { return String(first) }
return "\(first) • \(tail)"
}
}
@MainActor
private struct ProviderDetailBrandIcon: View {
let provider: UsageProvider
var body: some View {
if let brand = ProviderBrandIcon.image(for: self.provider) {
Image(nsImage: brand)
.resizable()
.scaledToFit()
.frame(width: 28, height: 28)
.foregroundStyle(.secondary)
.accessibilityHidden(true)
} else {
Image(systemName: "circle.dotted")
.font(.system(size: 24, weight: .regular))
.foregroundStyle(.secondary)
.accessibilityHidden(true)
}
}
}
@MainActor
private struct ProviderDetailInfoGrid: View {
let provider: UsageProvider
@Bindable var store: UsageStore
let isEnabled: Bool
let model: UsageMenuCardView.Model
let labelWidth: CGFloat
var body: some View {
let status = self.store.status(for: self.provider)
let source = self.store.sourceLabel(for: self.provider)
let version = self.store.version(for: self.provider) ?? "not detected"
let updated = self.updatedText
let email = self.model.email
let plan = self.model.planText ?? ""
let enabledText = self.isEnabled ? "Enabled" : "Disabled"
Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 6) {
ProviderDetailInfoRow(label: "State", value: enabledText, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: "Source", value: source, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: "Version", value: version, labelWidth: self.labelWidth)
ProviderDetailInfoRow(label: "Updated", value: updated, labelWidth: self.labelWidth)
if let status {
ProviderDetailInfoRow(
label: "Status",
value: status.description ?? status.indicator.label,
labelWidth: self.labelWidth)
}
if !email.isEmpty {
ProviderDetailInfoRow(label: "Account", value: email, labelWidth: self.labelWidth)
}
if !plan.isEmpty {
ProviderDetailInfoRow(label: "Plan", value: plan, labelWidth: self.labelWidth)
}
}
.font(.footnote)
.foregroundStyle(.secondary)
}
private var updatedText: String {
if let updated = self.store.snapshot(for: self.provider)?.updatedAt {
return UsageFormatter.updatedString(from: updated)
}
if self.store.refreshingProviders.contains(self.provider) {
return "Refreshing"
}
return "Not fetched yet"
}
}
private struct ProviderDetailInfoRow: View {
let label: String
let value: String
let labelWidth: CGFloat
var body: some View {
GridRow {
Text(self.label)
.frame(width: self.labelWidth, alignment: .leading)
Text(self.value)
.lineLimit(2)
}
}
}
@MainActor
struct ProviderMetricsInlineView: View {
let provider: UsageProvider
let model: UsageMenuCardView.Model
let isEnabled: Bool
let labelWidth: CGFloat
var body: some View {
ProviderSettingsSection(
title: "Usage",
spacing: 8,
verticalPadding: 6,
horizontalPadding: 0)
{
if self.model.metrics.isEmpty, self.model.providerCost == nil,
self.model.creditsText == nil, self.model.tokenUsage == nil
{
Text(self.placeholderText)
.font(.footnote)
.foregroundStyle(.secondary)
} else {
ForEach(self.model.metrics, id: \.id) { metric in
ProviderMetricInlineRow(
metric: metric,
progressColor: self.model.progressColor,
labelWidth: self.labelWidth)
}
if let credits = self.model.creditsText {
ProviderMetricInlineTextRow(
title: "Credits",
value: credits,
labelWidth: self.labelWidth)
}
if let providerCost = self.model.providerCost {
ProviderMetricInlineCostRow(
section: providerCost,
progressColor: self.model.progressColor,
labelWidth: self.labelWidth)
}
if let tokenUsage = self.model.tokenUsage {
ProviderMetricInlineTextRow(
title: "Cost",
value: tokenUsage.sessionLine,
labelWidth: self.labelWidth)
ProviderMetricInlineTextRow(
title: "",
value: tokenUsage.monthLine,
labelWidth: self.labelWidth)
}
}
}
}
private var placeholderText: String {
if !self.isEnabled {
return "Disabled — no recent data"
}
return self.model.placeholder ?? "No usage yet"
}
}
private struct ProviderMetricInlineRow: View {
let metric: UsageMenuCardView.Model.Metric
let progressColor: Color
let labelWidth: CGFloat
var body: some View {
HStack(alignment: .top, spacing: 10) {
Text(self.metric.title)
.font(.subheadline.weight(.semibold))
.lineLimit(1)
.frame(width: self.labelWidth, alignment: .leading)
VStack(alignment: .leading, spacing: 4) {
UsageProgressBar(
percent: self.metric.percent,
tint: self.progressColor,
accessibilityLabel: self.metric.percentStyle.accessibilityLabel,
pacePercent: self.metric.pacePercent,
paceOnTop: self.metric.paceOnTop)
.frame(minWidth: ProviderSettingsMetrics.metricBarWidth, maxWidth: .infinity)
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(self.metric.percentLabel)
.font(.footnote)
.foregroundStyle(.secondary)
.monospacedDigit()
Spacer(minLength: 8)
if let resetText = self.metric.resetText, !resetText.isEmpty {
Text(resetText)
.font(.footnote)
.foregroundStyle(.secondary)
}
}
let hasLeftDetail = self.metric.detailLeftText?.isEmpty == false
let hasRightDetail = self.metric.detailRightText?.isEmpty == false
if hasLeftDetail || hasRightDetail {
HStack(alignment: .firstTextBaseline, spacing: 8) {
if let leftDetail = self.metric.detailLeftText, !leftDetail.isEmpty {
Text(leftDetail)
.font(.footnote)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
if let rightDetail = self.metric.detailRightText, !rightDetail.isEmpty {
Text(rightDetail)
.font(.footnote)
.foregroundStyle(.secondary)
}
}
}
if let detail = self.detailText, !detail.isEmpty {
Text(detail)
.font(.footnote)
.foregroundStyle(.tertiary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(.vertical, 2)
}
private var detailText: String? {
guard let detailText = self.metric.detailText, !detailText.isEmpty else { return nil }
return detailText
}
}
private struct ProviderMetricInlineTextRow: View {
let title: String
let value: String
let labelWidth: CGFloat
var body: some View {
HStack(alignment: .firstTextBaseline, spacing: 12) {
Text(self.title)
.font(.subheadline.weight(.semibold))
.frame(width: self.labelWidth, alignment: .leading)
Text(self.value)
.font(.footnote)
.foregroundStyle(.secondary)
Spacer(minLength: 0)
}
.padding(.vertical, 1)
}
}
private struct ProviderMetricInlineCostRow: View {
let section: UsageMenuCardView.Model.ProviderCostSection
let progressColor: Color
let labelWidth: CGFloat
var body: some View {
HStack(alignment: .top, spacing: 10) {
Text(self.section.title)
.font(.subheadline.weight(.semibold))
.frame(width: self.labelWidth, alignment: .leading)
VStack(alignment: .leading, spacing: 4) {
UsageProgressBar(
percent: self.section.percentUsed,
tint: self.progressColor,
accessibilityLabel: "Usage used")
.frame(minWidth: ProviderSettingsMetrics.metricBarWidth, maxWidth: .infinity)
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(String(format: "%.0f%% used", self.section.percentUsed))
.font(.footnote)
.foregroundStyle(.secondary)
.monospacedDigit()
Spacer(minLength: 8)
Text(self.section.spendLine)
.font(.footnote)
.foregroundStyle(.secondary)
}
}
Spacer(minLength: 0)
}
.padding(.vertical, 2)
}
}