import SwiftUI struct ProviderSettingsSection<Content: View>: View { let title: String let spacing: CGFloat let verticalPadding: CGFloat let horizontalPadding: CGFloat @ViewBuilder let content: () -> Content init( title: String, spacing: CGFloat = 12, verticalPadding: CGFloat = 10, horizontalPadding: CGFloat = 4, @ViewBuilder content: @escaping () -> Content) { self.title = title self.spacing = spacing self.verticalPadding = verticalPadding self.horizontalPadding = horizontalPadding self.content = content } var body: some View { VStack(alignment: .leading, spacing: self.spacing) { Text(L(self.title)) .font(.headline) self.content() } .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, self.verticalPadding) .padding(.horizontal, self.horizontalPadding) }} @MainActorstruct ProviderSettingsToggleRowView: View { let toggle: ProviderSettingsToggleDescriptor var body: some View { let isEnabled = self.toggle.isEnabled?() ?? true VStack(alignment: .leading, spacing: 8) { HStack(alignment: .firstTextBaseline, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text(L(self.toggle.title)) .font(.subheadline.weight(.semibold)) .foregroundStyle(isEnabled ? .primary : .tertiary) Text(L(self.toggle.subtitle)) .font(.footnote) .foregroundStyle(isEnabled ? .secondary : .tertiary) .fixedSize(horizontal: false, vertical: true) } Spacer(minLength: 8) Toggle("", isOn: self.toggle.binding) .labelsHidden() .toggleStyle(.switch) } if self.toggle.binding.wrappedValue { if let status = self.toggle.statusText?(), !status.isEmpty { Text(status) .font(.footnote) .foregroundStyle(.secondary) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) } let actions = self.toggle.actions.filter { $0.isVisible?() ?? true } if !actions.isEmpty { HStack(spacing: 10) { ForEach(actions) { action in Button(L(action.title)) { Task { @MainActor in await action.perform() } } .applyProviderSettingsButtonStyle(action.style) .controlSize(.small) } } } } } .disabled(!isEnabled) .onChange(of: self.toggle.binding.wrappedValue) { _, enabled in guard let onChange = self.toggle.onChange else { return } Task { @MainActor in await onChange(enabled) } } .task(id: self.toggle.binding.wrappedValue) { guard self.toggle.binding.wrappedValue else { return } guard let onAppear = self.toggle.onAppearWhenEnabled else { return } await onAppear() } }} @MainActorstruct ProviderSettingsPickerRowView: View { let picker: ProviderSettingsPickerDescriptor var body: some View { let isEnabled = self.picker.isEnabled?() ?? true VStack(alignment: .leading, spacing: 6) { HStack(alignment: .firstTextBaseline, spacing: 10) { Text(L(self.picker.title)) .font(.subheadline.weight(.semibold)) .frame(width: ProviderSettingsMetrics.pickerLabelWidth, alignment: .leading) Picker("", selection: self.picker.binding) { ForEach(self.picker.options) { option in Text(L(option.title)).tag(option.id) } } .labelsHidden() .pickerStyle(.menu) .controlSize(.small) if let trailingText = self.picker.trailingText?(), !trailingText.isEmpty { Text(trailingText) .font(.footnote) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.tail) .padding(.leading, 4) } Spacer(minLength: 0) } let subtitle = self.picker.dynamicSubtitle?() ?? self.picker.subtitle if !subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(L(subtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } } .disabled(!isEnabled) .onChange(of: self.picker.binding.wrappedValue) { _, selection in guard let onChange = self.picker.onChange else { return } Task { @MainActor in await onChange(selection) } } }} @MainActorstruct ProviderSettingsFieldRowView: View { let field: ProviderSettingsFieldDescriptor var body: some View { VStack(alignment: .leading, spacing: 8) { let trimmedTitle = self.field.title.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedSubtitle = self.field.subtitle.trimmingCharacters(in: .whitespacesAndNewlines) let hasHeader = !trimmedTitle.isEmpty || !trimmedSubtitle.isEmpty if hasHeader { VStack(alignment: .leading, spacing: 4) { if !trimmedTitle.isEmpty { Text(L(trimmedTitle)) .font(.subheadline.weight(.semibold)) } if !trimmedSubtitle.isEmpty { Text(L(trimmedSubtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } } } switch self.field.kind { case .plain: TextField(L(self.field.placeholder ?? ""), text: self.field.binding) .textFieldStyle(.roundedBorder) .font(.footnote) .onTapGesture { self.field.onActivate?() } case .secure: SecureField(L(self.field.placeholder ?? ""), text: self.field.binding) .textFieldStyle(.roundedBorder) .font(.footnote) .onTapGesture { self.field.onActivate?() } } let actions = self.field.actions.filter { $0.isVisible?() ?? true } if !actions.isEmpty { HStack(spacing: 10) { ForEach(actions) { action in Button(L(action.title)) { Task { @MainActor in await action.perform() } } .applyProviderSettingsButtonStyle(action.style) .controlSize(.small) } } } if let footer = self.field.footerText, !footer.isEmpty { Text(L(footer)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } } }} @MainActorstruct ProviderSettingsActionsRowView: View { let descriptor: ProviderSettingsActionsDescriptor var body: some View { VStack(alignment: .leading, spacing: 8) { Text(L(self.descriptor.title)) .font(.subheadline.weight(.semibold)) if !self.descriptor.subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(L(self.descriptor.subtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } let actions = self.descriptor.actions.filter { $0.isVisible?() ?? true } if !actions.isEmpty { HStack(spacing: 10) { ForEach(actions) { action in Button(L(action.title)) { Task { @MainActor in await action.perform() } } .applyProviderSettingsButtonStyle(action.style) .controlSize(.small) } } } } }} @MainActorstruct ProviderSettingsTokenAccountsRowView: View { let descriptor: ProviderSettingsTokenAccountsDescriptor @State private var newLabel: String = "" @State private var newToken: String = "" @State private var newOrgID: String = "" var body: some View { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .center, spacing: 12) { Text(L(self.descriptor.title)) .font(.subheadline.weight(.semibold)) Spacer(minLength: 8) if let title = self.descriptor.primaryAddActionTitle, let action = self.descriptor.primaryAddAction { Button(L(title)) { Task { @MainActor in await action() } } .buttonStyle(.bordered) .controlSize(.small) } } if !self.descriptor.subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(L(self.descriptor.subtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } let accounts = self.descriptor.accounts() if accounts.isEmpty { Text(L("No token accounts yet.")) .font(.footnote) .foregroundStyle(.secondary) } else { VStack(alignment: .leading, spacing: 8) { ForEach(Array(accounts.enumerated()), id: \.element.id) { index, account in HStack(alignment: .center, spacing: 10) { Button { self.descriptor.setActiveIndex(index) } label: { HStack(alignment: .center, spacing: 8) { Image(systemName: self.isActive(index: index, accountCount: accounts.count) ? "checkmark.circle.fill" : "circle") .font(.system(size: 13, weight: .medium)) .foregroundStyle(self.isActive(index: index, accountCount: accounts.count) ? Color.accentColor : Color.secondary) Text(account.displayName) .font( .footnote.weight( self.isActive(index: index, accountCount: accounts.count) ? .semibold : .regular)) .foregroundStyle(.primary) Spacer(minLength: 0) } .contentShape(Rectangle()) } .buttonStyle(.plain) Button(L("Remove")) { self.descriptor.removeAccount(account.id) } .buttonStyle(.bordered) .controlSize(.small) } if index < accounts.count - 1 { Divider() } } } } if self.descriptor.primaryAddAction == nil { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 8) { TextField(L("Label"), text: self.$newLabel) .textFieldStyle(.roundedBorder) .font(.footnote) SecureField(L(self.descriptor.placeholder), text: self.$newToken) .textFieldStyle(.roundedBorder) .font(.footnote) Button(L("Add")) { let label = self.newLabel.trimmingCharacters(in: .whitespacesAndNewlines) let token = self.newToken.trimmingCharacters(in: .whitespacesAndNewlines) guard !label.isEmpty, !token.isEmpty else { return } let orgID = self.descriptor.showsOrganizationField ? self.newOrgID.trimmingCharacters(in: .whitespacesAndNewlines) : "" self.descriptor.addAccount(label, token, orgID.isEmpty ? nil : orgID) self.newLabel = "" self.newToken = "" self.newOrgID = "" } .buttonStyle(.bordered) .controlSize(.small) .disabled(self.newLabel.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || self.newToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) } if self.descriptor.showsOrganizationField { TextField(L("Org ID (optional)"), text: self.$newOrgID) .textFieldStyle(.roundedBorder) .font(.footnote) .help( L("Optional organization ID for accounts linked to multiple Anthropic organizations.")) } } } HStack(spacing: 10) { Button(L("Open token file")) { self.descriptor.openConfigFile() } .buttonStyle(.link) .controlSize(.small) Button(L("Reload")) { self.descriptor.reloadFromDisk() } .buttonStyle(.link) .controlSize(.small) } } } private func isActive(index: Int, accountCount: Int) -> Bool { guard accountCount > 0 else { return false } let selectedIndex = min(self.descriptor.activeIndex(), max(0, accountCount - 1)) return selectedIndex == index }} extension View { @ViewBuilder fileprivate func applyProviderSettingsButtonStyle(_ style: ProviderSettingsActionDescriptor.Style) -> some View { switch style { case .bordered: self.buttonStyle(.bordered) case .link: self.buttonStyle(.link) } }} @MainActorstruct ProviderSettingsOrganizationsRowView: View { let descriptor: ProviderSettingsOrganizationsDescriptor @State private var errorMessage: String? @State private var isRefreshing = false var body: some View { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .center, spacing: 12) { Text(L(self.descriptor.title)) .font(.subheadline.weight(.semibold)) Spacer(minLength: 8) } if let subtitle = self.descriptor.subtitle, !subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(L(subtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } let entries = self.descriptor.entries() if entries.allSatisfy(\.isLocked) { Text(L("No organizations loaded. Click Refresh after setting your API key.")) .font(.footnote) .foregroundStyle(.secondary) } else { VStack(alignment: .leading, spacing: 6) { ForEach(entries) { entry in Toggle(isOn: Binding( get: { entry.isEnabled }, set: { newValue in self.descriptor.onToggle(entry.id, newValue) })) { VStack(alignment: .leading, spacing: 1) { Text(entry.localizesTitle ? L(entry.title) : entry.title) .font(.footnote) if let subtitle = entry.subtitle, !subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(entry.localizesSubtitle ? L(subtitle) : subtitle) .font(.caption) .foregroundStyle(.secondary) } } } .toggleStyle(.checkbox) .disabled(entry.isLocked) } } } HStack(spacing: 10) { Button(L("Refresh organizations")) { Task { @MainActor in self.isRefreshing = true let result = await self.descriptor.onRefresh() self.isRefreshing = false self.errorMessage = result.success ? nil : result.errorMessage } } .buttonStyle(.bordered) .controlSize(.small) .disabled(!self.descriptor.canRefresh() || self.isRefreshing) if let errorMessage = self.errorMessage, !errorMessage.isEmpty { Text(errorMessage) .font(.caption) .foregroundStyle(.red) } } } }}
import SwiftUI
struct ProviderSettingsSection<Content: View>: View {
let title: String
let spacing: CGFloat
let verticalPadding: CGFloat
let horizontalPadding: CGFloat
@ViewBuilder let content: () -> Content
init(
title: String,
spacing: CGFloat = 12,
verticalPadding: CGFloat = 10,
horizontalPadding: CGFloat = 4,
@ViewBuilder content: @escaping () -> Content)
{
self.title = title
self.spacing = spacing
self.verticalPadding = verticalPadding
self.horizontalPadding = horizontalPadding
self.content = content
}
var body: some View {
VStack(alignment: .leading, spacing: self.spacing) {
Text(L(self.title))
.font(.headline)
self.content()
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, self.verticalPadding)
.padding(.horizontal, self.horizontalPadding)
}
}
@MainActor
struct ProviderSettingsToggleRowView: View {
let toggle: ProviderSettingsToggleDescriptor
var body: some View {
let isEnabled = self.toggle.isEnabled?() ?? true
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .firstTextBaseline, spacing: 12) {
VStack(alignment: .leading, spacing: 4) {
Text(L(self.toggle.title))
.font(.subheadline.weight(.semibold))
.foregroundStyle(isEnabled ? .primary : .tertiary)
Text(L(self.toggle.subtitle))
.font(.footnote)
.foregroundStyle(isEnabled ? .secondary : .tertiary)
.fixedSize(horizontal: false, vertical: true)
}
Spacer(minLength: 8)
Toggle("", isOn: self.toggle.binding)
.labelsHidden()
.toggleStyle(.switch)
}
if self.toggle.binding.wrappedValue {
if let status = self.toggle.statusText?(), !status.isEmpty {
Text(status)
.font(.footnote)
.foregroundStyle(.secondary)
.lineLimit(4)
.fixedSize(horizontal: false, vertical: true)
}
let actions = self.toggle.actions.filter { $0.isVisible?() ?? true }
if !actions.isEmpty {
HStack(spacing: 10) {
ForEach(actions) { action in
Button(L(action.title)) {
Task { @MainActor in
await action.perform()
}
}
.applyProviderSettingsButtonStyle(action.style)
.controlSize(.small)
}
}
}
}
}
.disabled(!isEnabled)
.onChange(of: self.toggle.binding.wrappedValue) { _, enabled in
guard let onChange = self.toggle.onChange else { return }
Task { @MainActor in
await onChange(enabled)
}
}
.task(id: self.toggle.binding.wrappedValue) {
guard self.toggle.binding.wrappedValue else { return }
guard let onAppear = self.toggle.onAppearWhenEnabled else { return }
await onAppear()
}
}
}
@MainActor
struct ProviderSettingsPickerRowView: View {
let picker: ProviderSettingsPickerDescriptor
var body: some View {
let isEnabled = self.picker.isEnabled?() ?? true
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .firstTextBaseline, spacing: 10) {
Text(L(self.picker.title))
.font(.subheadline.weight(.semibold))
.frame(width: ProviderSettingsMetrics.pickerLabelWidth, alignment: .leading)
Picker("", selection: self.picker.binding) {
ForEach(self.picker.options) { option in
Text(L(option.title)).tag(option.id)
}
}
.labelsHidden()
.pickerStyle(.menu)
.controlSize(.small)
if let trailingText = self.picker.trailingText?(), !trailingText.isEmpty {
Text(trailingText)
.font(.footnote)
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.tail)
.padding(.leading, 4)
}
Spacer(minLength: 0)
}
let subtitle = self.picker.dynamicSubtitle?() ?? self.picker.subtitle
if !subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(L(subtitle))
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
.disabled(!isEnabled)
.onChange(of: self.picker.binding.wrappedValue) { _, selection in
guard let onChange = self.picker.onChange else { return }
Task { @MainActor in
await onChange(selection)
}
}
}
}
@MainActor
struct ProviderSettingsFieldRowView: View {
let field: ProviderSettingsFieldDescriptor
var body: some View {
VStack(alignment: .leading, spacing: 8) {
let trimmedTitle = self.field.title.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedSubtitle = self.field.subtitle.trimmingCharacters(in: .whitespacesAndNewlines)
let hasHeader = !trimmedTitle.isEmpty || !trimmedSubtitle.isEmpty
if hasHeader {
VStack(alignment: .leading, spacing: 4) {
if !trimmedTitle.isEmpty {
Text(L(trimmedTitle))
.font(.subheadline.weight(.semibold))
}
if !trimmedSubtitle.isEmpty {
Text(L(trimmedSubtitle))
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
}
switch self.field.kind {
case .plain:
TextField(L(self.field.placeholder ?? ""), text: self.field.binding)
.textFieldStyle(.roundedBorder)
.font(.footnote)
.onTapGesture { self.field.onActivate?() }
case .secure:
SecureField(L(self.field.placeholder ?? ""), text: self.field.binding)
.textFieldStyle(.roundedBorder)
.font(.footnote)
.onTapGesture { self.field.onActivate?() }
}
let actions = self.field.actions.filter { $0.isVisible?() ?? true }
if !actions.isEmpty {
HStack(spacing: 10) {
ForEach(actions) { action in
Button(L(action.title)) {
Task { @MainActor in
await action.perform()
}
}
.applyProviderSettingsButtonStyle(action.style)
.controlSize(.small)
}
}
}
if let footer = self.field.footerText, !footer.isEmpty {
Text(L(footer))
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
}
}
@MainActor
struct ProviderSettingsActionsRowView: View {
let descriptor: ProviderSettingsActionsDescriptor
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(L(self.descriptor.title))
.font(.subheadline.weight(.semibold))
if !self.descriptor.subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(L(self.descriptor.subtitle))
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
let actions = self.descriptor.actions.filter { $0.isVisible?() ?? true }
if !actions.isEmpty {
HStack(spacing: 10) {
ForEach(actions) { action in
Button(L(action.title)) {
Task { @MainActor in
await action.perform()
}
}
.applyProviderSettingsButtonStyle(action.style)
.controlSize(.small)
}
}
}
}
}
}
@MainActor
struct ProviderSettingsTokenAccountsRowView: View {
let descriptor: ProviderSettingsTokenAccountsDescriptor
@State private var newLabel: String = ""
@State private var newToken: String = ""
@State private var newOrgID: String = ""
var body: some View {
VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .center, spacing: 12) {
Text(L(self.descriptor.title))
.font(.subheadline.weight(.semibold))
Spacer(minLength: 8)
if let title = self.descriptor.primaryAddActionTitle,
let action = self.descriptor.primaryAddAction
{
Button(L(title)) {
Task { @MainActor in
await action()
}
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
if !self.descriptor.subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(L(self.descriptor.subtitle))
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
let accounts = self.descriptor.accounts()
if accounts.isEmpty {
Text(L("No token accounts yet."))
.font(.footnote)
.foregroundStyle(.secondary)
} else {
VStack(alignment: .leading, spacing: 8) {
ForEach(Array(accounts.enumerated()), id: \.element.id) { index, account in
HStack(alignment: .center, spacing: 10) {
Button {
self.descriptor.setActiveIndex(index)
} label: {
HStack(alignment: .center, spacing: 8) {
Image(systemName: self.isActive(index: index, accountCount: accounts.count) ?
"checkmark.circle.fill" : "circle")
.font(.system(size: 13, weight: .medium))
.foregroundStyle(self.isActive(index: index, accountCount: accounts.count) ?
Color.accentColor : Color.secondary)
Text(account.displayName)
.font(
.footnote.weight(
self.isActive(index: index, accountCount: accounts.count) ?
.semibold : .regular))
.foregroundStyle(.primary)
Spacer(minLength: 0)
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
Button(L("Remove")) {
self.descriptor.removeAccount(account.id)
}
.buttonStyle(.bordered)
.controlSize(.small)
}
if index < accounts.count - 1 {
Divider()
}
}
}
}
if self.descriptor.primaryAddAction == nil {
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 8) {
TextField(L("Label"), text: self.$newLabel)
.textFieldStyle(.roundedBorder)
.font(.footnote)
SecureField(L(self.descriptor.placeholder), text: self.$newToken)
.textFieldStyle(.roundedBorder)
.font(.footnote)
Button(L("Add")) {
let label = self.newLabel.trimmingCharacters(in: .whitespacesAndNewlines)
let token = self.newToken.trimmingCharacters(in: .whitespacesAndNewlines)
guard !label.isEmpty, !token.isEmpty else { return }
let orgID = self.descriptor.showsOrganizationField
? self.newOrgID.trimmingCharacters(in: .whitespacesAndNewlines)
: ""
self.descriptor.addAccount(label, token, orgID.isEmpty ? nil : orgID)
self.newLabel = ""
self.newToken = ""
self.newOrgID = ""
}
.buttonStyle(.bordered)
.controlSize(.small)
.disabled(self.newLabel.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ||
self.newToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
if self.descriptor.showsOrganizationField {
TextField(L("Org ID (optional)"), text: self.$newOrgID)
.textFieldStyle(.roundedBorder)
.font(.footnote)
.help(
L("Optional organization ID for accounts linked to multiple Anthropic organizations."))
}
}
}
HStack(spacing: 10) {
Button(L("Open token file")) {
self.descriptor.openConfigFile()
}
.buttonStyle(.link)
.controlSize(.small)
Button(L("Reload")) {
self.descriptor.reloadFromDisk()
}
.buttonStyle(.link)
.controlSize(.small)
}
}
}
private func isActive(index: Int, accountCount: Int) -> Bool {
guard accountCount > 0 else { return false }
let selectedIndex = min(self.descriptor.activeIndex(), max(0, accountCount - 1))
return selectedIndex == index
}
}
extension View {
@ViewBuilder
fileprivate func applyProviderSettingsButtonStyle(_ style: ProviderSettingsActionDescriptor.Style) -> some View {
switch style {
case .bordered:
self.buttonStyle(.bordered)
case .link:
self.buttonStyle(.link)
}
}
}
@MainActor
struct ProviderSettingsOrganizationsRowView: View {
let descriptor: ProviderSettingsOrganizationsDescriptor
@State private var errorMessage: String?
@State private var isRefreshing = false
var body: some View {
VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .center, spacing: 12) {
Text(L(self.descriptor.title))
.font(.subheadline.weight(.semibold))
Spacer(minLength: 8)
}
if let subtitle = self.descriptor.subtitle,
!subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
Text(L(subtitle))
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
let entries = self.descriptor.entries()
if entries.allSatisfy(\.isLocked) {
Text(L("No organizations loaded. Click Refresh after setting your API key."))
.font(.footnote)
.foregroundStyle(.secondary)
} else {
VStack(alignment: .leading, spacing: 6) {
ForEach(entries) { entry in
Toggle(isOn: Binding(
get: { entry.isEnabled },
set: { newValue in
self.descriptor.onToggle(entry.id, newValue)
})) {
VStack(alignment: .leading, spacing: 1) {
Text(entry.localizesTitle ? L(entry.title) : entry.title)
.font(.footnote)
if let subtitle = entry.subtitle,
!subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
{
Text(entry.localizesSubtitle ? L(subtitle) : subtitle)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
.toggleStyle(.checkbox)
.disabled(entry.isLocked)
}
}
}
HStack(spacing: 10) {
Button(L("Refresh organizations")) {
Task { @MainActor in
self.isRefreshing = true
let result = await self.descriptor.onRefresh()
self.isRefreshing = false
self.errorMessage = result.success ? nil : result.errorMessage
}
}
.buttonStyle(.bordered)
.controlSize(.small)
.disabled(!self.descriptor.canRefresh() || self.isRefreshing)
if let errorMessage = self.errorMessage, !errorMessage.isEmpty {
Text(errorMessage)
.font(.caption)
.foregroundStyle(.red)
}
}
}
}
}