1190 lines · 44.4 KB · Swift
RawDownload
import CodexBarCoreimport SwiftUIimport WidgetKit extension EnvironmentValues {    @Entry fileprivate var widgetUsageShowsUsed: Bool = false} struct CodexBarUsageWidgetView: View {    @Environment(\.widgetFamily) private var family    let entry: CodexBarWidgetEntry     var body: some View {        let providerEntry = self.entry.snapshot.entries.first { $0.provider == self.entry.provider }        ZStack {            Color.black.opacity(0.02)            if let providerEntry {                self.content(providerEntry: providerEntry)            } else {                self.emptyState            }        }        .containerBackground(.fill.tertiary, for: .widget)        .environment(\.widgetUsageShowsUsed, self.entry.snapshot.usageBarsShowUsed)    }     @ViewBuilder    private func content(providerEntry: WidgetSnapshot.ProviderEntry) -> some View {        switch self.family {        case .systemSmall:            SmallUsageView(entry: providerEntry)        case .systemMedium:            MediumUsageView(entry: providerEntry)        default:            LargeUsageView(entry: providerEntry)        }    }     private var emptyState: some View {        VStack(alignment: .leading, spacing: 6) {            Text("Open CodexBar")                .font(.body)                .fontWeight(.semibold)            Text("Usage data will appear once the app refreshes.")                .font(.caption)                .foregroundStyle(.secondary)        }        .padding(12)    }} struct CodexBarHistoryWidgetView: View {    @Environment(\.widgetFamily) private var family    let entry: CodexBarWidgetEntry     var body: some View {        let providerEntry = self.entry.snapshot.entries.first { $0.provider == self.entry.provider }        ZStack {            Color.black.opacity(0.02)            if let providerEntry {                HistoryView(entry: providerEntry, isLarge: self.family == .systemLarge)            } else {                self.emptyState            }        }        .containerBackground(.fill.tertiary, for: .widget)    }     private var emptyState: some View {        VStack(alignment: .leading, spacing: 6) {            Text("Open CodexBar")                .font(.body)                .fontWeight(.semibold)            Text("Usage history will appear after a refresh.")                .font(.caption)                .foregroundStyle(.secondary)        }        .padding(12)    }} struct CodexBarCompactWidgetView: View {    let entry: CodexBarCompactEntry     var body: some View {        let providerEntry = self.entry.snapshot.entries.first { $0.provider == self.entry.provider }        ZStack {            Color.black.opacity(0.02)            if let providerEntry {                CompactMetricView(entry: providerEntry, metric: self.entry.metric)            } else {                self.emptyState            }        }        .containerBackground(.fill.tertiary, for: .widget)    }     private var emptyState: some View {        VStack(alignment: .leading, spacing: 6) {            Text("Open CodexBar")                .font(.body)                .fontWeight(.semibold)            Text("Usage data will appear once the app refreshes.")                .font(.caption)                .foregroundStyle(.secondary)        }        .padding(12)    }} struct CodexBarSwitcherWidgetView: View {    @Environment(\.widgetFamily) private var family    let entry: CodexBarSwitcherEntry     var body: some View {        let providerEntry = self.entry.snapshot.entries.first { $0.provider == self.entry.provider }        ZStack {            Color.black.opacity(0.02)            VStack(alignment: .leading, spacing: 10) {                ProviderSwitcherRow(                    providers: self.entry.availableProviders,                    selected: self.entry.provider,                    updatedAt: providerEntry?.updatedAt ?? Date(),                    compact: self.family == .systemSmall,                    showsTimestamp: self.family != .systemSmall)                if let providerEntry {                    self.content(providerEntry: providerEntry)                } else {                    self.emptyState                }            }            .padding(12)        }        .containerBackground(.fill.tertiary, for: .widget)        .environment(\.widgetUsageShowsUsed, self.entry.snapshot.usageBarsShowUsed)    }     @ViewBuilder    private func content(providerEntry: WidgetSnapshot.ProviderEntry) -> some View {        switch self.family {        case .systemSmall:            SwitcherSmallUsageView(entry: providerEntry)        case .systemMedium:            SwitcherMediumUsageView(entry: providerEntry)        default:            SwitcherLargeUsageView(entry: providerEntry)        }    }     private var emptyState: some View {        VStack(alignment: .leading, spacing: 6) {            Text("Open CodexBar")                .font(.caption)                .foregroundStyle(.secondary)            Text("Usage data appears after a refresh.")                .font(.caption2)                .foregroundStyle(.secondary)        }    }} private struct CompactMetricView: View {    let entry: WidgetSnapshot.ProviderEntry    let metric: CompactMetric     var body: some View {        let display = CompactMetricFormatter.display(for: self.entry, metric: self.metric)        VStack(alignment: .leading, spacing: 8) {            HeaderView(provider: self.entry.provider, updatedAt: self.entry.updatedAt)            VStack(alignment: .leading, spacing: 2) {                Text(display.value)                    .font(.title2)                    .fontWeight(.semibold)                Text(display.label)                    .font(.caption2)                    .foregroundStyle(.secondary)                if let detail = display.detail {                    Text(detail)                        .font(.caption2)                        .foregroundStyle(.secondary)                }            }        }        .padding(12)    }} struct CompactMetricDisplay: Equatable {    let value: String    let label: String    let detail: String?} enum CompactMetricFormatter {    static func display(for entry: WidgetSnapshot.ProviderEntry, metric: CompactMetric) -> CompactMetricDisplay {        switch metric {        case .credits:            if let cost = WidgetBalanceFormatter.extraUsageCost(for: entry) {                return CompactMetricDisplay(                    value: WidgetFormat.currency(cost.used, code: cost.currencyCode),                    label: "Extra usage balance",                    detail: nil)            }            let value = entry.creditsRemaining.map(WidgetFormat.credits) ?? "—"            return CompactMetricDisplay(value: value, label: "Credits left", detail: nil)        case .todayCost:            let value = entry.tokenUsage.map { token in                token.sessionCostUSD.map { WidgetFormat.currency($0, code: token.currencyCode) } ?? "—"            } ?? "—"            let detail = entry.tokenUsage?.sessionTokens.map(WidgetFormat.tokenCount)            let label = entry.tokenUsage.map {                WidgetFormat.tokenRowTitle(                    Self.costMetricLabel($0.sessionLabel, provider: entry.provider),                    summary: $0,                    entryUpdatedAt: entry.updatedAt)            } ?? "Today cost"            return CompactMetricDisplay(value: value, label: label, detail: detail)        case .last30DaysCost:            let value = entry.tokenUsage.map { token in                token.last30DaysCostUSD.map { WidgetFormat.currency($0, code: token.currencyCode) } ?? "—"            } ?? "—"            let detail = entry.tokenUsage?.last30DaysTokens.map(WidgetFormat.tokenCount)            let label = entry.tokenUsage.map {                WidgetFormat.tokenRowTitle(                    Self.costMetricLabel($0.last30DaysLabel, provider: entry.provider),                    summary: $0,                    entryUpdatedAt: entry.updatedAt)            } ?? "30d cost"            return CompactMetricDisplay(value: value, label: label, detail: detail)        }    }     static func costMetricLabel(_ label: String, provider: UsageProvider) -> String {        guard provider == .codex else { return "\(label) cost" }        // Existing widget timelines may predate the estimate labels. Do not leave a bare        // dollar value until the app next republishes it.        guard !label.contains("API est.") else { return label }        return "\(label) API est. · not billed"    }} private struct ProviderSwitcherRow: View {    let providers: [UsageProvider]    let selected: UsageProvider    let updatedAt: Date    let compact: Bool    let showsTimestamp: Bool     var body: some View {        HStack(spacing: self.compact ? 4 : 6) {            ForEach(self.providers, id: \.self) { provider in                ProviderSwitchChip(                    provider: provider,                    selected: provider == self.selected,                    compact: self.compact)            }            if self.showsTimestamp {                Spacer(minLength: 6)                Text(WidgetFormat.relativeDate(self.updatedAt))                    .font(.caption2)                    .foregroundStyle(.secondary)            }        }    }} private struct ProviderSwitchChip: View {    let provider: UsageProvider    let selected: Bool    let compact: Bool     var body: some View {        let label = self.compact ? self.shortLabel : self.longLabel        let background = self.selected            ? WidgetColors.color(for: self.provider).opacity(0.2)            : Color.primary.opacity(0.08)         if let choice = ProviderChoice(provider: self.provider) {            Button(intent: SwitchWidgetProviderIntent(provider: choice)) {                Text(label)                    .font(self.compact ? .caption2.weight(.semibold) : .caption.weight(.semibold))                    .foregroundStyle(self.selected ? Color.primary : Color.secondary)                    .padding(.horizontal, self.compact ? 6 : 8)                    .padding(.vertical, self.compact ? 3 : 4)                    .background(Capsule().fill(background))            }            .buttonStyle(.plain)        } else {            Text(label)                .font(self.compact ? .caption2.weight(.semibold) : .caption.weight(.semibold))                .foregroundStyle(self.selected ? Color.primary : Color.secondary)                .padding(.horizontal, self.compact ? 6 : 8)                .padding(.vertical, self.compact ? 3 : 4)                .background(Capsule().fill(background))        }    }     private var longLabel: String {        ProviderDefaults.metadata[self.provider]?.displayName ?? self.provider.rawValue.capitalized    }     private var shortLabel: String {        switch self.provider {        case .codex: "Codex"        case .openai: "OpenAI"        case .azureopenai: "Azure OpenAI"        case .claude: "Claude"        case .gemini: "Gemini"        case .antigravity: "Anti"        case .cursor: "Cursor"        case .opencode: "OpenCode"        case .opencodego: "OpenCode Go"        case .alibaba: "Alibaba"        case .alibabatokenplan: "Token Plan"        case .zai: "z.ai"        case .factory: "Droid"        case .copilot: "Copilot"        case .devin: "Devin"        case .minimax: "MiniMax"        case .manus: "Manus"        case .vertexai: "Vertex"        case .kilo: "Kilo"        case .kiro: "Kiro"        case .augment: "Augment"        case .jetbrains: "JetBrains"        case .kimi: "Kimi"        case .kimik2: "Kimi K2"        case .moonshot: "Moonshot"        case .amp: "Amp"        case .t3chat: "T3 Chat"        case .ollama: "Ollama"        case .synthetic: "Synthetic"        case .openrouter: "OpenRouter"        case .crossmodel: "CrossModel"        case .clawrouter: "ClawRouter"        case .sub2api: "sub2api"        case .wayfinder: "Wayfinder"        case .elevenlabs: "ElevenLabs"        case .warp: "Warp"        case .windsurf: "Windsurf"        case .perplexity: "Pplx"        case .mimo: "MiMo"        case .doubao: "Doubao"        case .sakana: "Sakana"        case .abacus: "Abacus"        case .mistral: "Mistral"        case .deepseek: "DeepSeek"        case .codebuff: "Codebuff"        case .crof: "Crof"        case .venice: "Venice"        case .commandcode: "Command Code"        case .qoder: "Qoder"        case .stepfun: "StepFun"        case .bedrock: "Bedrock"        case .grok: "Grok"        case .groq: "Groq"        case .llmproxy: "LLM Proxy"        case .litellm: "LiteLLM"        case .deepgram: "Deepgram"        case .poe: "Poe"        case .chutes: "Chutes"        case .zed: "Zed"        case .zenmux: "ZenMux"        }    }} private struct SwitcherSmallUsageView: View {    let entry: WidgetSnapshot.ProviderEntry     var body: some View {        VStack(alignment: .leading, spacing: 8) {            ForEach(WidgetUsageRow.rows(                for: self.entry,                limit: WidgetUsageRow.smallWidgetRowLimit(for: self.entry)))            { row in                UsageBarRow(                    title: row.title,                    percentLeft: row.percentLeft,                    color: WidgetColors.color(for: self.entry.provider))            }            if let codeReview = entry.codeReviewRemainingPercent {                UsageBarRow(                    title: "Code review",                    percentLeft: codeReview,                    color: WidgetColors.color(for: self.entry.provider))            }            if let token = WidgetUsageRow.compactTokenUsage(for: self.entry) {                ValueLine(                    title: WidgetFormat.tokenRowTitle(                        token.sessionLabel,                        summary: token,                        entryUpdatedAt: self.entry.updatedAt),                    value: WidgetFormat.costAndTokens(                        cost: token.sessionCostUSD,                        tokens: token.sessionTokens,                        currencyCode: token.currencyCode))            }            if let balance = extraUsageBalanceLine(for: entry) {                balance            }        }    }} private struct SwitcherMediumUsageView: View {    let entry: WidgetSnapshot.ProviderEntry     var body: some View {        VStack(alignment: .leading, spacing: 10) {            ForEach(WidgetUsageRow.rows(                for: self.entry,                limit: WidgetUsageRow.mediumWidgetRowLimit(for: self.entry)))            { row in                UsageBarRow(                    title: row.title,                    percentLeft: row.percentLeft,                    color: WidgetColors.color(for: self.entry.provider))            }            if let credits = entry.creditsRemaining {                ValueLine(title: "Credits", value: WidgetFormat.credits(credits))            }            if let token = entry.tokenUsage {                ValueLine(                    title: WidgetFormat.tokenRowTitle(                        token.sessionLabel,                        summary: token,                        entryUpdatedAt: self.entry.updatedAt),                    value: WidgetFormat.costAndTokens(                        cost: token.sessionCostUSD,                        tokens: token.sessionTokens,                        currencyCode: token.currencyCode))            }            if let balance = extraUsageBalanceLine(for: entry) {                balance            }        }    }} private struct SwitcherLargeUsageView: View {    let entry: WidgetSnapshot.ProviderEntry     var body: some View {        VStack(alignment: .leading, spacing: 12) {            ForEach(WidgetUsageRow.rows(for: self.entry)) { row in                UsageBarRow(                    title: row.title,                    percentLeft: row.percentLeft,                    color: WidgetColors.color(for: self.entry.provider))            }            if let codeReview = entry.codeReviewRemainingPercent {                UsageBarRow(                    title: "Code review",                    percentLeft: codeReview,                    color: WidgetColors.color(for: self.entry.provider))            }            if let credits = entry.creditsRemaining {                ValueLine(title: "Credits", value: WidgetFormat.credits(credits))            }            if let token = entry.tokenUsage {                VStack(alignment: .leading, spacing: 4) {                    ValueLine(                        title: WidgetFormat.tokenRowTitle(                            token.sessionLabel,                            summary: token,                            entryUpdatedAt: self.entry.updatedAt),                        value: WidgetFormat.costAndTokens(                            cost: token.sessionCostUSD,                            tokens: token.sessionTokens,                            currencyCode: token.currencyCode))                    ValueLine(                        title: WidgetFormat.tokenRowTitle(                            token.last30DaysLabel,                            summary: token,                            entryUpdatedAt: self.entry.updatedAt),                        value: WidgetFormat.costAndTokens(                            cost: token.last30DaysCostUSD,                            tokens: token.last30DaysTokens,                            currencyCode: token.currencyCode))                }            }            if let balance = extraUsageBalanceLine(for: entry) {                balance            }            UsageHistoryChart(                points: self.entry.dailyUsage,                color: WidgetColors.color(for: self.entry.provider),                currencyCode: self.entry.tokenUsage?.currencyCode)                .frame(height: 50)        }    }} private struct SmallUsageView: View {    let entry: WidgetSnapshot.ProviderEntry     var body: some View {        VStack(alignment: .leading, spacing: 8) {            HeaderView(provider: self.entry.provider, updatedAt: self.entry.updatedAt)

Lines 1–500 of 1190.