import CodexBarCore import SwiftUI @MainActor struct DisplayPane: View { private static let maxOverviewProviders = SettingsStore.mergedOverviewProviderLimit static func overviewProviderLimitText(limit: Int = Self.maxOverviewProviders) -> String { L("overview_choose_providers", String(limit)) } @State private var isOverviewProviderPopoverPresented = false @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(L("section_menu_bar")) .font(.caption) .foregroundStyle(.secondary) .textCase(.uppercase) PreferenceToggleRow( title: L("merge_icons_title"), subtitle: L("merge_icons_subtitle"), binding: self.$settings.mergeIcons) PreferenceToggleRow( title: L("switcher_shows_icons_title"), subtitle: L("switcher_shows_icons_subtitle"), binding: self.$settings.switcherShowsIcons) .disabled(!self.settings.mergeIcons) .opacity(self.settings.mergeIcons ? 1 : 0.5) PreferenceToggleRow( title: L("show_most_used_provider_title"), subtitle: L("show_most_used_provider_subtitle"), binding: self.$settings.menuBarShowsHighestUsage) .disabled(!self.settings.mergeIcons) .opacity(self.settings.mergeIcons ? 1 : 0.5) PreferenceToggleRow( title: L("menu_bar_shows_percent_title"), subtitle: L("menu_bar_shows_percent_subtitle"), binding: self.$settings.menuBarShowsBrandIconWithPercent) HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text(L("display_mode_title")) .font(.body) Text(L("display_mode_subtitle")) .font(.footnote) .foregroundStyle(.tertiary) } Spacer() Picker(L("Display mode"), selection: self.$settings.menuBarDisplayMode) { ForEach(MenuBarDisplayMode.allCases) { mode in Text(mode.label).tag(mode) } } .labelsHidden() .pickerStyle(.menu) .frame(maxWidth: 200) } .disabled(!self.settings.menuBarShowsBrandIconWithPercent) .opacity(self.settings.menuBarShowsBrandIconWithPercent ? 1 : 0.5) } Divider() SettingsSection(contentSpacing: 12) { Text(L("section_menu_content")) .font(.caption) .foregroundStyle(.secondary) .textCase(.uppercase) PreferenceToggleRow( title: L("show_usage_as_used_title"), subtitle: L("show_usage_as_used_subtitle"), binding: self.$settings.usageBarsShowUsed) PreferenceToggleRow( title: L("show_quota_warning_markers_title"), subtitle: L("show_quota_warning_markers_subtitle"), binding: self.$settings.quotaWarningMarkersVisible) HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text(L("weekly_progress_work_days_title")) .font(.body) Text(L("weekly_progress_work_days_subtitle")) .font(.footnote) .foregroundStyle(.tertiary) } Spacer() Picker(L("weekly_progress_work_days_title"), selection: self.$settings.weeklyProgressWorkDays) { Text(L("Off")).tag(nil as Int?) Text(L("4 days")).tag(4 as Int?) Text(L("5 days")).tag(5 as Int?) Text(L("7 days")).tag(7 as Int?) } .labelsHidden() .pickerStyle(.menu) .frame(maxWidth: 100) } PreferenceToggleRow( title: L("show_reset_time_as_clock_title"), subtitle: L("show_reset_time_as_clock_subtitle"), binding: self.$settings.resetTimesShowAbsolute) PreferenceToggleRow( title: L("show_provider_changelog_links_title"), subtitle: L("show_provider_changelog_links_subtitle"), binding: self.$settings.providerChangelogLinksEnabled) PreferenceToggleRow( title: L("show_credits_extra_usage_title"), subtitle: L("show_credits_extra_usage_subtitle"), binding: self.$settings.showOptionalCreditsAndExtraUsage) HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text(L("multi_account_layout_title")) .font(.body) Text(L("multi_account_layout_subtitle")) .font(.footnote) .foregroundStyle(.tertiary) } Spacer() Picker(L("multi_account_layout_title"), selection: self.$settings.multiAccountMenuLayout) { ForEach(MultiAccountMenuLayout.allCases) { layout in Text(layout.label).tag(layout) } } .labelsHidden() .pickerStyle(.menu) .frame(maxWidth: 200) } self.overviewProviderSelector } } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 20) .padding(.vertical, 12) .onAppear { self.reconcileOverviewSelection() } .onChange(of: self.settings.mergeIcons) { _, isEnabled in guard isEnabled else { self.isOverviewProviderPopoverPresented = false return } self.reconcileOverviewSelection() } .onChange(of: self.activeProvidersInOrder) { _, _ in if self.activeProvidersInOrder.isEmpty { self.isOverviewProviderPopoverPresented = false } self.reconcileOverviewSelection() } } } private var overviewProviderSelector: some View { VStack(alignment: .leading, spacing: 6) { HStack(alignment: .center, spacing: 12) { Text(L("overview_tab_providers_title")) .font(.body) Spacer(minLength: 0) if self.showsOverviewConfigureButton { Button(L("configure")) { self.isOverviewProviderPopoverPresented = true } .offset(y: 1) .popover(isPresented: self.$isOverviewProviderPopoverPresented, arrowEdge: .bottom) { self.overviewProviderPopover } } } if !self.settings.mergeIcons { Text(L("overview_enable_merge_icons_hint")) .font(.footnote) .foregroundStyle(.tertiary) } else if self.activeProvidersInOrder.isEmpty { Text(L("overview_no_providers_hint")) .font(.footnote) .foregroundStyle(.tertiary) } else { Text(self.overviewProviderSelectionSummary) .font(.footnote) .foregroundStyle(.tertiary) .lineLimit(2) .truncationMode(.tail) } } } private var overviewProviderPopover: some View { VStack(alignment: .leading, spacing: 10) { Text(Self.overviewProviderLimitText()) .font(.headline) Text(L("overview_rows_follow_order")) .font(.footnote) .foregroundStyle(.tertiary) ScrollView(.vertical, showsIndicators: true) { VStack(alignment: .leading, spacing: 6) { ForEach(self.activeProvidersInOrder, id: \.self) { provider in Toggle( isOn: Binding( get: { self.overviewSelectedProviders.contains(provider) }, set: { shouldSelect in self.setOverviewProviderSelection(provider: provider, isSelected: shouldSelect) })) { Text(self.providerDisplayName(provider)) .font(.body) } .toggleStyle(.checkbox) .disabled( !self.overviewSelectedProviders.contains(provider) && self.overviewSelectedProviders.count >= Self.maxOverviewProviders) } } } .frame(maxHeight: 220) } .padding(12) .frame(width: 280) } private var activeProvidersInOrder: [UsageProvider] { self.store.enabledProviders() } private var overviewSelectedProviders: [UsageProvider] { self.settings.resolvedMergedOverviewProviders( activeProviders: self.activeProvidersInOrder, maxVisibleProviders: Self.maxOverviewProviders) } private var showsOverviewConfigureButton: Bool { self.settings.mergeIcons && !self.activeProvidersInOrder.isEmpty } private var overviewProviderSelectionSummary: String { let selectedNames = self.overviewSelectedProviders.map(self.providerDisplayName) guard !selectedNames.isEmpty else { return L("overview_no_providers_selected") } return selectedNames.joined(separator: ", ") } private func providerDisplayName(_ provider: UsageProvider) -> String { ProviderDescriptorRegistry.descriptor(for: provider).metadata.displayName } private func setOverviewProviderSelection(provider: UsageProvider, isSelected: Bool) { _ = self.settings.setMergedOverviewProviderSelection( provider: provider, isSelected: isSelected, activeProviders: self.activeProvidersInOrder, maxVisibleProviders: Self.maxOverviewProviders) } private func reconcileOverviewSelection() { _ = self.settings.reconcileMergedOverviewSelectedProviders( activeProviders: self.activeProvidersInOrder, maxVisibleProviders: Self.maxOverviewProviders) } }