| 1 | #if DEBUG |
| 2 | import SwiftUI |
| 3 | |
| 4 | struct DebugView: View { |
| 5 | var body: some View { |
| 6 | ScrollView { |
| 7 | VStack(alignment: .leading, spacing: 18) { |
| 8 | #if os(tvOS) |
| 9 | TVPageHeader("Debug") |
| 10 | #endif |
| 11 | |
| 12 | SectionHeader("Debug") |
| 13 | |
| 14 | DebugPanelCard() |
| 15 | } |
| 16 | .padding(.horizontal, 20) |
| 17 | .padding(.vertical, 16) |
| 18 | .kleponReadableContentWidth() |
| 19 | } |
| 20 | .background(KleponColor.background.ignoresSafeArea()) |
| 21 | .navigationTitle("Debug") |
| 22 | #if os(tvOS) |
| 23 | .toolbar(.hidden, for: .navigationBar) |
| 24 | #else |
| 25 | .kleponInlineNavigationTitle() |
| 26 | #endif |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | struct DebugSettingsCard: View { |
| 31 | @EnvironmentObject private var accountStore: AccountStore |
| 32 | |
| 33 | var body: some View { |
| 34 | NavigationLink { |
| 35 | DebugView() |
| 36 | } label: { |
| 37 | KleponCard { |
| 38 | VStack(alignment: .leading, spacing: 12) { |
| 39 | HStack(alignment: .top, spacing: 12) { |
| 40 | Image(systemName: "ladybug.fill") |
| 41 | .font(.system(size: 24)) |
| 42 | .foregroundStyle(.orange) |
| 43 | |
| 44 | VStack(alignment: .leading, spacing: 6) { |
| 45 | Text("Debug") |
| 46 | .font(KleponTypography.cardTitle) |
| 47 | .foregroundStyle(KleponColor.textPrimary) |
| 48 | |
| 49 | } |
| 50 | |
| 51 | Spacer(minLength: 12) |
| 52 | |
| 53 | Image(systemName: "chevron.right") |
| 54 | .font(.footnote.weight(.semibold)) |
| 55 | .foregroundStyle(KleponColor.textSecondary) |
| 56 | } |
| 57 | |
| 58 | HStack(spacing: 10) { |
| 59 | KleponChip( |
| 60 | title: accountStore.proxyEnvironment.displayName, |
| 61 | icon: "network" |
| 62 | ) |
| 63 | |
| 64 | KleponChip( |
| 65 | title: accountStore.isSignedIn ? "Session active" : "Signed out", |
| 66 | icon: accountStore.isSignedIn |
| 67 | ? "checkmark.circle" : "xmark.circle" |
| 68 | ) |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | .kleponInteractiveButtonStyle() |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | private struct DebugPanelCard: View { |
| 78 | @EnvironmentObject private var accountStore: AccountStore |
| 79 | |
| 80 | var body: some View { |
| 81 | KleponCard { |
| 82 | VStack(alignment: .leading, spacing: 14) { |
| 83 | debugHeader |
| 84 | environmentPicker |
| 85 | activeEndpointRow |
| 86 | sessionRow |
| 87 | Divider() |
| 88 | .background(KleponColor.textSecondary.opacity(0.2)) |
| 89 | clearSessionButton |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | private var debugHeader: some View { |
| 95 | HStack(spacing: 8) { |
| 96 | Image(systemName: "ladybug.fill") |
| 97 | .foregroundStyle(.orange) |
| 98 | .font(.system(size: 14, weight: .semibold)) |
| 99 | Text("Debug") |
| 100 | .font(KleponTypography.cardTitle) |
| 101 | .foregroundStyle(KleponColor.textPrimary) |
| 102 | Spacer() |
| 103 | environmentBadge |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | private var environmentBadge: some View { |
| 108 | Text(accountStore.proxyEnvironment.displayName.uppercased()) |
| 109 | .font(.system(size: 10, weight: .bold, design: .monospaced)) |
| 110 | .foregroundStyle(.white) |
| 111 | .padding(.horizontal, 8) |
| 112 | .padding(.vertical, 3) |
| 113 | .background(environmentBadgeColor.cornerRadius(4)) |
| 114 | } |
| 115 | |
| 116 | private var environmentBadgeColor: Color { |
| 117 | accountStore.proxyEnvironment == .development ? .orange : .green |
| 118 | } |
| 119 | |
| 120 | private var environmentPicker: some View { |
| 121 | VStack(alignment: .leading, spacing: 8) { |
| 122 | Text("Environment") |
| 123 | .font(KleponTypography.bodySecondary) |
| 124 | .foregroundStyle(KleponColor.textSecondary) |
| 125 | |
| 126 | Picker( |
| 127 | "Environment", |
| 128 | selection: Binding( |
| 129 | get: { accountStore.proxyEnvironment }, |
| 130 | set: { accountStore.switchEnvironment($0) } |
| 131 | ) |
| 132 | ) { |
| 133 | ForEach(AccountProxyEnvironment.allCases) { environment in |
| 134 | Text(environment.displayName).tag(environment) |
| 135 | } |
| 136 | } |
| 137 | .pickerStyle(.segmented) |
| 138 | .applySegmentedPickerStyleForPlatform() |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | private var activeEndpointRow: some View { |
| 143 | VStack(alignment: .leading, spacing: 4) { |
| 144 | Text("Active endpoint") |
| 145 | .font(KleponTypography.bodySecondary) |
| 146 | .foregroundStyle(KleponColor.textSecondary) |
| 147 | Text(accountStore.proxyEnvironment.baseURL.absoluteString) |
| 148 | .font(.system(size: 12, weight: .regular, design: .monospaced)) |
| 149 | .foregroundStyle(environmentBadgeColor) |
| 150 | .lineLimit(1) |
| 151 | .truncationMode(.middle) |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | private var sessionRow: some View { |
| 156 | VStack(alignment: .leading, spacing: 4) { |
| 157 | Text("Session") |
| 158 | .font(KleponTypography.bodySecondary) |
| 159 | .foregroundStyle(KleponColor.textSecondary) |
| 160 | if accountStore.isSignedIn { |
| 161 | Text(accountStore.statusTitle) |
| 162 | .font(.system(size: 12, weight: .regular, design: .monospaced)) |
| 163 | .foregroundStyle(.green) |
| 164 | } else { |
| 165 | Text("No active session") |
| 166 | .font(.system(size: 12, weight: .regular, design: .monospaced)) |
| 167 | .foregroundStyle(KleponColor.textSecondary) |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | private var clearSessionButton: some View { |
| 173 | KleponActionButton( |
| 174 | title: "Clear session & sign out", |
| 175 | systemImage: "xmark.circle", |
| 176 | tone: .secondary |
| 177 | ) { |
| 178 | Task { await accountStore.logout() } |
| 179 | } |
| 180 | .disabled(!accountStore.isSignedIn) |
| 181 | .opacity(accountStore.isSignedIn ? 1 : 0.4) |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | extension View { |
| 186 | @ViewBuilder |
| 187 | fileprivate func applySegmentedPickerStyleForPlatform() -> some View { |
| 188 | #if os(macOS) |
| 189 | self.colorScheme(.light) |
| 190 | #else |
| 191 | self |
| 192 | #endif |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | #Preview { |
| 197 | NavigationStack { |
| 198 | DebugView() |
| 199 | .environmentObject(AccountStore()) |
| 200 | } |
| 201 | } |
| 202 | #endif |