149 lines · 6.1 KB · Swift
RawDownload
import AppKitimport CodexBarCoreimport SwiftUI @MainActorstruct GeneralPane: View {    @Bindable var settings: SettingsStore    @Bindable var store: UsageStore     var body: some View {        ScrollView(.vertical, showsIndicators: true) {            VStack(alignment: .leading, spacing: 16) {                SettingsSection(contentSpacing: 12) {                    Text("System")                        .font(.caption)                        .foregroundStyle(.secondary)                        .textCase(.uppercase)                    PreferenceToggleRow(                        title: "Start at Login",                        subtitle: "Automatically opens CodexBar when you start your Mac.",                        binding: self.$settings.launchAtLogin)                }                 Divider()                 SettingsSection(contentSpacing: 12) {                    Text("Usage")                        .font(.caption)                        .foregroundStyle(.secondary)                        .textCase(.uppercase)                     VStack(alignment: .leading, spacing: 10) {                        VStack(alignment: .leading, spacing: 4) {                            Toggle(isOn: self.$settings.costUsageEnabled) {                                Text("Show cost summary")                                    .font(.body)                            }                            .toggleStyle(.checkbox)                             Text("Reads local usage logs. Shows today + last 30 days cost in the menu.")                                .font(.footnote)                                .foregroundStyle(.tertiary)                                .fixedSize(horizontal: false, vertical: true)                             if self.settings.costUsageEnabled {                                Text("Auto-refresh: hourly · Timeout: 10m")                                    .font(.footnote)                                    .foregroundStyle(.tertiary)                                 self.costStatusLine(provider: .claude)                                self.costStatusLine(provider: .codex)                            }                        }                    }                }                 Divider()                 SettingsSection(contentSpacing: 12) {                    Text("Status")                        .font(.caption)                        .foregroundStyle(.secondary)                        .textCase(.uppercase)                    PreferenceToggleRow(                        title: "Check provider status",                        subtitle: "Polls OpenAI/Claude status pages and Google Workspace for " +                            "Gemini/Antigravity, surfacing incidents in the icon and menu.",                        binding: self.$settings.statusChecksEnabled)                }                 Divider()                 SettingsSection(contentSpacing: 12) {                    Text("Notifications")                        .font(.caption)                        .foregroundStyle(.secondary)                        .textCase(.uppercase)                    PreferenceToggleRow(                        title: "Session quota notifications",                        subtitle: "Notifies when the 5-hour session quota hits 0% and when it becomes " +                            "available again.",                        binding: self.$settings.sessionQuotaNotificationsEnabled)                }                 Divider()                 SettingsSection(contentSpacing: 12) {                    HStack {                        Spacer()                        Button("Quit CodexBar") { NSApp.terminate(nil) }                            .buttonStyle(.borderedProminent)                            .controlSize(.large)                    }                }            }            .frame(maxWidth: .infinity, alignment: .leading)            .padding(.horizontal, 20)            .padding(.vertical, 12)        }    }     private func costStatusLine(provider: UsageProvider) -> some View {        let name = ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName         guard provider == .claude || provider == .codex else {            return Text("\(name): unsupported")                .font(.footnote)                .foregroundStyle(.tertiary)        }         if self.store.isTokenRefreshInFlight(for: provider) {            let elapsed: String = {                guard let startedAt = self.store.tokenLastAttemptAt(for: provider) else { return "" }                let seconds = max(0, Date().timeIntervalSince(startedAt))                let formatter = DateComponentsFormatter()                formatter.allowedUnits = seconds < 60 ? [.second] : [.minute, .second]                formatter.unitsStyle = .abbreviated                return formatter.string(from: seconds).map { " (\($0))" } ?? ""            }()            return Text("\(name): fetching…\(elapsed)")                .font(.footnote)                .foregroundStyle(.tertiary)        }        if let snapshot = self.store.tokenSnapshot(for: provider) {            let updated = UsageFormatter.updatedString(from: snapshot.updatedAt)            let cost = snapshot.last30DaysCostUSD.map { UsageFormatter.usdString($0) } ?? "—"            return Text("\(name): \(updated) · 30d \(cost)")                .font(.footnote)                .foregroundStyle(.tertiary)        }        if let error = self.store.tokenError(for: provider), !error.isEmpty {            let truncated = UsageFormatter.truncatedSingleLine(error, max: 120)            return Text("\(name): \(truncated)")                .font(.footnote)                .foregroundStyle(.tertiary)        }        if let lastAttempt = self.store.tokenLastAttemptAt(for: provider) {            let rel = RelativeDateTimeFormatter()            rel.unitsStyle = .abbreviated            let when = rel.localizedString(for: lastAttempt, relativeTo: Date())            return Text("\(name): last attempt \(when)")                .font(.footnote)                .foregroundStyle(.tertiary)        }        return Text("\(name): no data yet")            .font(.footnote)            .foregroundStyle(.tertiary)    }}