Files
2026-07-13 12:22:33 +08:00

300 lines
9.1 KiB
Swift

import CodexBarCore
import Foundation
import SwiftUI
/// Settings UI context passed to provider implementations.
///
/// Providers use this to:
/// - bind to `SettingsStore` values
/// - read current provider state from `UsageStore`
/// - surface transient status text (e.g. "Importing cookies…")
/// - request a shared confirmation alert (no provider-specific UI)
@MainActor
struct ProviderSettingsContext {
let provider: UsageProvider
let settings: SettingsStore
let store: UsageStore
let boolBinding: (ReferenceWritableKeyPath<SettingsStore, Bool>) -> Binding<Bool>
let stringBinding: (ReferenceWritableKeyPath<SettingsStore, String>) -> Binding<String>
let statusText: (String) -> String?
let setStatusText: (String, String?) -> Void
let lastAppActiveRunAt: (String) -> Date?
let setLastAppActiveRunAt: (String, Date?) -> Void
let requestConfirmation: (ProviderSettingsConfirmation) -> Void
let runLoginFlow: () async -> Void
init(
provider: UsageProvider,
settings: SettingsStore,
store: UsageStore,
boolBinding: @escaping (ReferenceWritableKeyPath<SettingsStore, Bool>) -> Binding<Bool>,
stringBinding: @escaping (ReferenceWritableKeyPath<SettingsStore, String>) -> Binding<String>,
statusText: @escaping (String) -> String?,
setStatusText: @escaping (String, String?) -> Void,
lastAppActiveRunAt: @escaping (String) -> Date?,
setLastAppActiveRunAt: @escaping (String, Date?) -> Void,
requestConfirmation: @escaping (ProviderSettingsConfirmation) -> Void,
runLoginFlow: @escaping () async -> Void = {})
{
self.provider = provider
self.settings = settings
self.store = store
self.boolBinding = boolBinding
self.stringBinding = stringBinding
self.statusText = statusText
self.setStatusText = setStatusText
self.lastAppActiveRunAt = lastAppActiveRunAt
self.setLastAppActiveRunAt = setLastAppActiveRunAt
self.requestConfirmation = requestConfirmation
self.runLoginFlow = runLoginFlow
}
}
/// Shared confirmation alert descriptor.
///
/// Providers can request confirmations (e.g. permission prompts) without supplying custom UI.
@MainActor
struct ProviderSettingsConfirmation {
let title: String
let message: String
let confirmTitle: String
let onConfirm: () -> Void
}
/// Shared toggle descriptor rendered in the Providers settings pane.
@MainActor
struct ProviderSettingsToggleDescriptor: Identifiable {
let id: String
let title: String
let subtitle: String
let binding: Binding<Bool>
/// Optional short status text shown under the toggle when enabled.
let statusText: (() -> String?)?
/// Optional actions shown under the toggle when enabled.
let actions: [ProviderSettingsActionDescriptor]
/// Optional runtime visibility gate.
let isVisible: (() -> Bool)?
/// Optional runtime enabled gate.
let isEnabled: (() -> Bool)?
/// Called whenever the toggle changes.
let onChange: ((_ enabled: Bool) async -> Void)?
/// Called when the app becomes active (used for "retry after permission grant" flows).
let onAppDidBecomeActive: (() async -> Void)?
/// Called when the view appears while the toggle is enabled.
let onAppearWhenEnabled: (() async -> Void)?
init(
id: String,
title: String,
subtitle: String,
binding: Binding<Bool>,
statusText: (() -> String?)?,
actions: [ProviderSettingsActionDescriptor],
isVisible: (() -> Bool)?,
isEnabled: (() -> Bool)? = nil,
onChange: ((_ enabled: Bool) async -> Void)?,
onAppDidBecomeActive: (() async -> Void)?,
onAppearWhenEnabled: (() async -> Void)?)
{
self.id = id
self.title = title
self.subtitle = subtitle
self.binding = binding
self.statusText = statusText
self.actions = actions
self.isVisible = isVisible
self.isEnabled = isEnabled
self.onChange = onChange
self.onAppDidBecomeActive = onAppDidBecomeActive
self.onAppearWhenEnabled = onAppearWhenEnabled
}
}
/// Shared text field descriptor rendered in the Providers settings pane.
@MainActor
struct ProviderSettingsFieldDescriptor: Identifiable {
enum Kind {
case plain
case secure
}
let id: String
let title: String
let subtitle: String
var footerText: String?
let kind: Kind
let placeholder: String?
let binding: Binding<String>
let actions: [ProviderSettingsActionDescriptor]
let isVisible: (() -> Bool)?
let onActivate: (() -> Void)?
}
/// Shared action row descriptor rendered in the Providers settings pane.
@MainActor
struct ProviderSettingsActionsDescriptor: Identifiable {
let id: String
let title: String
let subtitle: String
let actions: [ProviderSettingsActionDescriptor]
let isVisible: (() -> Bool)?
}
/// Shared token account descriptor rendered in the Providers settings pane.
@MainActor
struct ProviderSettingsTokenAccountsDescriptor: Identifiable {
let id: String
let title: String
let subtitle: String
let placeholder: String
let provider: UsageProvider
let isVisible: (() -> Bool)?
let accounts: () -> [ProviderTokenAccount]
let activeIndex: () -> Int
let setActiveIndex: (Int) -> Void
let showsOrganizationField: Bool
let showsTeamModeControls: Bool
let addAccount: (
_ label: String,
_ token: String,
_ usageScope: String?,
_ organizationID: String?,
_ workspaceID: String?) -> Void
let updateAccount: (
_ accountID: UUID,
_ usageScope: String?,
_ organizationID: String?,
_ workspaceID: String?) -> Void
let removeAccount: (_ accountID: UUID) -> Void
let primaryAddActionTitle: String?
let primaryAddAction: (() async -> Void)?
let openConfigFile: () -> Void
let reloadFromDisk: () -> Void
}
/// Shared organizations descriptor rendered in the Providers settings pane.
///
/// Used by providers that let the user opt in to additional account scopes
/// (e.g. Kilo organizations) shown alongside the personal account.
@MainActor
struct ProviderSettingsOrganizationsDescriptor: Identifiable {
struct Entry: Identifiable {
let id: String
let title: String
let subtitle: String?
let localizesTitle: Bool
let localizesSubtitle: Bool
let isEnabled: Bool
let isLocked: Bool
init(
id: String,
title: String,
subtitle: String?,
localizesTitle: Bool = true,
localizesSubtitle: Bool = true,
isEnabled: Bool,
isLocked: Bool)
{
self.id = id
self.title = title
self.subtitle = subtitle
self.localizesTitle = localizesTitle
self.localizesSubtitle = localizesSubtitle
self.isEnabled = isEnabled
self.isLocked = isLocked
}
}
struct RefreshOutcome {
let success: Bool
let errorMessage: String?
}
let id: String
let title: String
let subtitle: String?
let entries: () -> [Entry]
let onToggle: (String, Bool) -> Void
let onRefresh: () async -> RefreshOutcome
let canRefresh: () -> Bool
}
/// Shared picker descriptor rendered in the Providers settings pane.
enum ProviderSettingsPickerPlacement: Equatable {
case menuBar
case connection
}
@MainActor
struct ProviderSettingsPickerDescriptor: Identifiable {
let id: String
let title: String
let subtitle: String
let placement: ProviderSettingsPickerPlacement
let dynamicSubtitle: (() -> String?)?
let binding: Binding<String>
let options: [ProviderSettingsPickerOption]
let isVisible: (() -> Bool)?
let isEnabled: (() -> Bool)?
let onChange: ((_ selection: String) async -> Void)?
let trailingText: (() -> String?)?
init(
id: String,
title: String,
subtitle: String,
placement: ProviderSettingsPickerPlacement = .connection,
dynamicSubtitle: (() -> String?)? = nil,
binding: Binding<String>,
options: [ProviderSettingsPickerOption],
isVisible: (() -> Bool)?,
isEnabled: (() -> Bool)? = nil,
onChange: ((_ selection: String) async -> Void)?,
trailingText: (() -> String?)? = nil)
{
self.id = id
self.title = title
self.subtitle = subtitle
self.placement = placement
self.dynamicSubtitle = dynamicSubtitle
self.binding = binding
self.options = options
self.isVisible = isVisible
self.isEnabled = isEnabled
self.onChange = onChange
self.trailingText = trailingText
}
}
struct ProviderSettingsPickerOption: Identifiable {
let id: String
let title: String
}
/// Shared action descriptor rendered under a settings toggle.
@MainActor
struct ProviderSettingsActionDescriptor: Identifiable {
enum Style {
case bordered
case link
}
let id: String
let title: String
let style: Style
let isVisible: (() -> Bool)?
let perform: () async -> Void
}