300 lines
9.1 KiB
Swift
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
|
|
}
|