From 62a361b7476ba47a3262f5ebf842d66148ef8e4b Mon Sep 17 00:00:00 2001 From: Arul Sharma <31745423+arul28@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:06:53 -0400 Subject: [PATCH 1/3] Fix mobile connection badge layouts --- .../Account/AccountConnectionsSection.swift | 7 +-- apps/ios/ADE/Views/Hub/HubComponents.swift | 24 ++------- .../Settings/ConnectionSettingsView.swift | 53 ++++++++----------- .../Settings/SettingsConnectionHeader.swift | 36 ++++++------- 4 files changed, 45 insertions(+), 75 deletions(-) diff --git a/apps/ios/ADE/Views/Account/AccountConnectionsSection.swift b/apps/ios/ADE/Views/Account/AccountConnectionsSection.swift index a86e1238e..583d479f3 100644 --- a/apps/ios/ADE/Views/Account/AccountConnectionsSection.swift +++ b/apps/ios/ADE/Views/Account/AccountConnectionsSection.swift @@ -17,12 +17,7 @@ struct AccountConnectionsSection: View { Group { if account.isConfigured { VStack(alignment: .leading, spacing: 12) { - SettingsSectionHeader( - label: "ACCOUNT", - hint: account.isSignedIn - ? "Your Macs on other networks" - : "Sign in or create an account for internet connections" - ) + SettingsSectionHeader(label: "ACCOUNT") switch account.phase { case .signedIn: diff --git a/apps/ios/ADE/Views/Hub/HubComponents.swift b/apps/ios/ADE/Views/Hub/HubComponents.swift index 85e6de500..d59522bf4 100644 --- a/apps/ios/ADE/Views/Hub/HubComponents.swift +++ b/apps/ios/ADE/Views/Hub/HubComponents.swift @@ -117,11 +117,6 @@ struct HubConnectionPill: View { } } - private var transportLabel: String? { - guard syncService.connectionHealth.transport.isConnected else { return nil } - return syncTransportBadgeText(routeKind: syncService.lastConnectedRouteKind) - } - private var connectionAccessibilityLabel: String { let state = switch syncService.connectionHealth.transport { case .connected: "connected" @@ -129,8 +124,7 @@ struct HubConnectionPill: View { case .unreachable: "unreachable" case .disconnected: "offline" } - let route = transportLabel.map { ", \($0)" } ?? "" - return "Machine connection: \(label), \(state)\(route)" + return "Machine connection: \(label), \(state)" } /// Name of the project currently switching in, if any — drives the progress @@ -160,18 +154,10 @@ struct HubConnectionPill: View { } else { HStack(spacing: 6) { Circle().fill(tint).frame(width: 7, height: 7) - VStack(alignment: .leading, spacing: 0) { - Text(label) - .font(.system(.caption, design: .rounded).weight(.semibold)) - .foregroundStyle(ADEColor.textPrimary) - .lineLimit(1) - if let transportLabel { - Text(transportLabel) - .font(.caption2.weight(.medium)) - .foregroundStyle(ADEColor.textSecondary) - .lineLimit(1) - } - } + Text(label) + .font(.system(.caption, design: .rounded).weight(.semibold)) + .foregroundStyle(ADEColor.textPrimary) + .lineLimit(1) } .id("machine") .transition(.opacity) diff --git a/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift b/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift index d72d697c2..f9df22668 100644 --- a/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift +++ b/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift @@ -64,7 +64,7 @@ struct ConnectionSettingsView: View { // 2. Connection status. VStack(alignment: .leading, spacing: 12) { - SettingsSectionHeader(label: "CONNECTION", hint: "Your current Mac connection") + SettingsSectionHeader(label: "CONNECTION") SettingsConnectionHeader( snapshot: presentationModel.connectionSnapshot, @@ -804,7 +804,7 @@ struct SettingsMachinesSection: View { var body: some View { let all = entries VStack(alignment: .leading, spacing: 12) { - SettingsSectionHeader(label: "MACHINES", hint: "Macs you can connect to") + SettingsSectionHeader(label: "MACHINES") if all.isEmpty { Text("No machines yet. Add one below.") @@ -882,39 +882,30 @@ struct SettingsMachinesSection: View { let tappable = !entry.isCurrent && connectingId == nil VStack(alignment: .leading, spacing: 0) { - ZStack(alignment: .trailing) { - Button { - connect(entry) - } label: { - MachineRowView( - deviceSymbol: deviceSymbol(entry), - title: entry.name, - routeHint: entry.routeHint, - online: entry.online, - isAuthenticatedCurrent: entry.isCurrent, - statusPill: entry.isCurrent ? .connected : nil, - affordance: rowAffordance(entry, isConnecting: isConnecting), - surface: .row - ) - } - .buttonStyle(ADEScaleButtonStyle()) - .disabled(!tappable) - .accessibilityLabel("\(entry.name), \(entry.routeHint)") - .accessibilityHint(tappable ? "Connect." : "") - + Button { + connect(entry) + } label: { + MachineRowView( + deviceSymbol: deviceSymbol(entry), + title: entry.name, + routeHint: entry.routeHint, + online: entry.online, + isAuthenticatedCurrent: entry.isCurrent, + statusPill: entry.isCurrent ? .connected : nil, + affordance: rowAffordance(entry, isConnecting: isConnecting), + surface: .row + ) + } + .buttonStyle(ADEScaleButtonStyle()) + .accessibilityLabel("\(entry.name), \(entry.routeHint)") + .accessibilityHint(tappable ? "Connect." : "") + .contextMenu { if let machine = accountMachine(from: entry) { Button { renamingMachine = machine } label: { - Image(systemName: "pencil") - .font(.system(size: 13, weight: .semibold)) - .foregroundStyle(ADEColor.textSecondary) - .frame(width: 44, height: 44) - .contentShape(Rectangle()) + Label("Rename", systemImage: "pencil") } - .buttonStyle(.plain) - .padding(.trailing, 58) - .accessibilityLabel("Rename \(entry.name)") } } .opacity(tappable || entry.isCurrent ? 1 : 0.72) @@ -978,7 +969,7 @@ struct SettingsMachinesSection: View { } private func connect(_ entry: Entry) { - guard connectingId == nil else { return } + guard !entry.isCurrent, connectingId == nil else { return } connectingId = entry.id rowErrors[entry.id] = nil Task { @MainActor in diff --git a/apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift b/apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift index 657db5d8b..a6030e061 100644 --- a/apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift +++ b/apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift @@ -53,25 +53,13 @@ struct SettingsConnectionHeader: View { pulsing: pulsing, reduceMotion: reduceMotion ) - VStack(alignment: .leading, spacing: 1) { - HStack(spacing: 7) { - Text(SettingsConnectionPresentation.statusLabel( - for: health, - canReconnectToSavedHost: snapshot.canReconnectToSavedHost - )) - .font(.system(.body, design: .rounded).weight(.semibold)) - .foregroundStyle(ADEColor.textPrimary) - if health.transport.isConnected, - let routeLabel = syncTransportBadgeText(routeKind: snapshot.routeKind) { - Text(routeLabel) - .font(.caption2.weight(.semibold)) - .foregroundStyle(ADEColor.textSecondary) - .padding(.horizontal, 7) - .padding(.vertical, 3) - .background(ADEColor.recessedBackground, in: Capsule()) - .overlay(Capsule().stroke(ADEColor.border.opacity(0.7), lineWidth: 0.7)) - } - } + VStack(alignment: .leading, spacing: 4) { + Text(SettingsConnectionPresentation.statusLabel( + for: health, + canReconnectToSavedHost: snapshot.canReconnectToSavedHost + )) + .font(.system(.body, design: .rounded).weight(.semibold)) + .foregroundStyle(ADEColor.textPrimary) if let detail = stateDetailLine { Text(detail) .font(.caption) @@ -79,6 +67,16 @@ struct SettingsConnectionHeader: View { .lineLimit(2) .fixedSize(horizontal: false, vertical: true) } + if health.transport.isConnected, + let routeLabel = syncTransportBadgeText(routeKind: snapshot.routeKind) { + Text(routeLabel) + .font(.caption2.weight(.semibold)) + .foregroundStyle(ADEColor.textSecondary) + .padding(.horizontal, 7) + .padding(.vertical, 3) + .background(ADEColor.recessedBackground, in: Capsule()) + .overlay(Capsule().stroke(ADEColor.border.opacity(0.7), lineWidth: 0.7)) + } } Spacer(minLength: 0) SettingsConnectionQuickAction( From bf72d13a57c661f87baae5d23895fe7b5e6035e4 Mon Sep 17 00:00:00 2001 From: Arul Sharma <31745423+arul28@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:12:54 -0400 Subject: [PATCH 2/3] Organize mobile settings into detail pages --- .../Settings/ConnectionSettingsView.swift | 128 +++++++++++++++++- .../Settings/SettingsDiagnosticsSection.swift | 66 +++++---- .../Settings/SettingsPairingSection.swift | 38 +++--- .../SettingsPushDeliverySection.swift | 92 ++++++------- 4 files changed, 221 insertions(+), 103 deletions(-) diff --git a/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift b/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift index f9df22668..b0eaa0062 100644 --- a/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift +++ b/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift @@ -103,13 +103,60 @@ struct ConnectionSettingsView: View { SettingsUsageQuotaSection(syncService: syncService) .padding(.horizontal, 16) - SettingsDiagnosticsSection(snapshot: presentationModel.diagnosticsSnapshot) - .padding(.horizontal, 16) - - SettingsPushDeliverySection( - snapshot: presentationModel.pushDeliverySnapshot, - pushService: PushNotificationService.shared - ) + VStack(spacing: 8) { + SettingsNavigationRow( + title: "Connection details", + subtitle: "Route and connection performance", + systemImage: "point.3.connected.trianglepath.dotted" + ) { + SettingsDestinationPage(title: "Connection details") { + SettingsDiagnosticsSection( + snapshot: presentationModel.diagnosticsSnapshot, + content: .connection + ) + } + } + + SettingsNavigationRow( + title: "About", + subtitle: "App, machine, and device information", + systemImage: "info.circle" + ) { + SettingsDestinationPage(title: "About") { + SettingsDiagnosticsSection( + snapshot: presentationModel.diagnosticsSnapshot, + content: .about + ) + } + } + + SettingsNavigationRow( + title: "Push delivery", + subtitle: "Notifications and Live Activities", + systemImage: "bell.badge" + ) { + SettingsDestinationPage(title: "Push delivery") { + SettingsPushDeliverySection( + snapshot: presentationModel.pushDeliverySnapshot, + pushService: PushNotificationService.shared + ) + } + } + + SettingsNavigationRow( + title: "Delivery diagnostics", + subtitle: "Push registration and relay status", + systemImage: "stethoscope" + ) { + SettingsDestinationPage(title: "Delivery diagnostics") { + SettingsPushDeliverySection( + snapshot: presentationModel.pushDeliverySnapshot, + pushService: PushNotificationService.shared, + content: .diagnostics + ) + } + } + } .padding(.horizontal, 16) } @@ -248,6 +295,73 @@ struct ConnectionSettingsView: View { } } +private struct SettingsNavigationRow: View { + let title: String + let subtitle: String + let systemImage: String + @ViewBuilder let destination: () -> Destination + + var body: some View { + NavigationLink(destination: destination) { + HStack(spacing: 14) { + Image(systemName: systemImage) + .font(.system(size: 15, weight: .semibold)) + .foregroundStyle(ADEColor.purpleAccent) + .frame(width: 34, height: 34) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(ADEColor.purpleAccent.opacity(0.14)) + ) + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.body.weight(.medium)) + .foregroundStyle(ADEColor.textPrimary) + Text(subtitle) + .font(.caption) + .foregroundStyle(ADEColor.textSecondary) + } + + Spacer(minLength: 8) + + Image(systemName: "chevron.right") + .font(.system(size: 12, weight: .semibold)) + .foregroundStyle(ADEColor.purpleAccent.opacity(0.65)) + } + .padding(.horizontal, 14) + .padding(.vertical, 12) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(ADEColor.surfaceBackground.opacity(0.5)) + ) + .glassEffect(in: .rect(cornerRadius: 14)) + .overlay( + RoundedRectangle(cornerRadius: 14, style: .continuous) + .stroke(ADEColor.glassBorder, lineWidth: 0.75) + ) + } + .buttonStyle(ADEScaleButtonStyle()) + } +} + +private struct SettingsDestinationPage: View { + let title: String + @ViewBuilder let content: () -> Content + + var body: some View { + ScrollView { + content() + .padding(.horizontal, 16) + .padding(.vertical, 12) + } + .background(SettingsAuroraBackground().ignoresSafeArea()) + .adeNavigationGlass() + .navigationTitle(title) + .navigationBarTitleDisplayMode(.inline) + } +} + struct SettingsConnectionSnapshot: Equatable { var health: SyncConnectionHealth var connectionState: RemoteConnectionState diff --git a/apps/ios/ADE/Views/Settings/SettingsDiagnosticsSection.swift b/apps/ios/ADE/Views/Settings/SettingsDiagnosticsSection.swift index 15a35abf8..911bcd3cd 100644 --- a/apps/ios/ADE/Views/Settings/SettingsDiagnosticsSection.swift +++ b/apps/ios/ADE/Views/Settings/SettingsDiagnosticsSection.swift @@ -5,11 +5,19 @@ func settingsVersionLabel(marketingVersion: String, build: String) -> String { } struct SettingsDiagnosticsSection: View { + enum Content: Equatable { + case all + case connection + case about + } + let snapshot: SettingsDiagnosticsSnapshot + var content: Content = .all var body: some View { VStack(alignment: .leading, spacing: 18) { - if snapshot.connectionRoute != nil || snapshot.connectionPerformance != nil { + if content != .about, + snapshot.connectionRoute != nil || snapshot.connectionPerformance != nil { VStack(alignment: .leading, spacing: 10) { SettingsSectionHeader(label: "CONNECTION DETAILS") @@ -33,38 +41,40 @@ struct SettingsDiagnosticsSection: View { } } - VStack(alignment: .leading, spacing: 10) { - SettingsSectionHeader(label: "ABOUT") - - VStack(spacing: 10) { - SettingsDetailRow( - symbol: "app.badge", - label: "ADE", - value: Self.appVersionString - ) + if content != .connection { + VStack(alignment: .leading, spacing: 10) { + SettingsSectionHeader(label: "ABOUT") - if let identity = snapshot.pairedMachineIdentity { + VStack(spacing: 10) { SettingsDetailRow( - symbol: "desktopcomputer.and.arrow.down", - label: "Paired machine", - value: identity + symbol: "app.badge", + label: "ADE", + value: Self.appVersionString ) - } - if let lastSync = snapshot.lastSyncDescription { - SettingsDetailRow( - symbol: "clock.arrow.circlepath", - label: "Last sync", - value: lastSync - ) - } + if let identity = snapshot.pairedMachineIdentity { + SettingsDetailRow( + symbol: "desktopcomputer.and.arrow.down", + label: "Paired machine", + value: identity + ) + } - if let deviceId = snapshot.deviceIdentity { - SettingsDetailRow( - symbol: "iphone", - label: "This device", - value: deviceId - ) + if let lastSync = snapshot.lastSyncDescription { + SettingsDetailRow( + symbol: "clock.arrow.circlepath", + label: "Last sync", + value: lastSync + ) + } + + if let deviceId = snapshot.deviceIdentity { + SettingsDetailRow( + symbol: "iphone", + label: "This device", + value: deviceId + ) + } } } } diff --git a/apps/ios/ADE/Views/Settings/SettingsPairingSection.swift b/apps/ios/ADE/Views/Settings/SettingsPairingSection.swift index 7b9053db1..c792c7bd5 100644 --- a/apps/ios/ADE/Views/Settings/SettingsPairingSection.swift +++ b/apps/ios/ADE/Views/Settings/SettingsPairingSection.swift @@ -3,31 +3,29 @@ import SwiftUI struct SettingsPairingSection: View { let snapshot: SettingsPairingSnapshot @Binding var presentedSheet: SettingsPairSheetRoute? - @State private var showsOtherWays = false + @State private var showsAddMachine = false @ObservedObject private var accountService = AccountService.shared var body: some View { VStack(alignment: .leading, spacing: 12) { - VStack(spacing: 8) { - SettingsPairActionRow( - icon: "qrcode.viewfinder", - title: "Scan a pairing code", - subtitle: "Scan the code shown in ADE on your Mac" - ) { - presentedSheet = .scan - } + DisclosureGroup(isExpanded: $showsAddMachine) { + VStack(spacing: 8) { + SettingsPairActionRow( + icon: "qrcode.viewfinder", + title: "Scan a pairing code", + subtitle: "Scan the code shown in ADE on your Mac" + ) { + presentedSheet = .scan + } - SettingsPairActionRow( - icon: "dot.radiowaves.left.and.right", - title: "Find a nearby Mac", - subtitle: discoverSubtitle - ) { - presentedSheet = .discover - } - } + SettingsPairActionRow( + icon: "dot.radiowaves.left.and.right", + title: "Find a nearby Mac", + subtitle: discoverSubtitle + ) { + presentedSheet = .discover + } - DisclosureGroup(isExpanded: $showsOtherWays) { - VStack(spacing: 8) { SettingsPairActionRow( icon: "terminal", title: "Set up with SSH", @@ -38,7 +36,7 @@ struct SettingsPairingSection: View { } .padding(.top, 8) } label: { - Label("Other ways to connect", systemImage: "ellipsis.circle") + Label("Add new machine", systemImage: "plus.circle") .font(.subheadline) .foregroundStyle(ADEColor.textSecondary) .frame(maxWidth: .infinity, minHeight: 44, alignment: .leading) diff --git a/apps/ios/ADE/Views/Settings/SettingsPushDeliverySection.swift b/apps/ios/ADE/Views/Settings/SettingsPushDeliverySection.swift index 033332971..0a7eccabc 100644 --- a/apps/ios/ADE/Views/Settings/SettingsPushDeliverySection.swift +++ b/apps/ios/ADE/Views/Settings/SettingsPushDeliverySection.swift @@ -46,66 +46,62 @@ struct SettingsPushDeliverySnapshot: Equatable { } struct SettingsPushDeliverySection: View { + enum Content: Equatable { + case controls + case diagnostics + } + let snapshot: SettingsPushDeliverySnapshot /// Observed for instant toggle / prefs feedback (the snapshot is throttled). @ObservedObject var pushService: PushNotificationService - - /// The low-level delivery diagnostics are collapsed by default (M8) — most - /// users only care about the toggles above. - @State private var showsDiagnostics = false + var content: Content = .controls var body: some View { VStack(alignment: .leading, spacing: 10) { - SettingsSectionHeader( - label: "PUSH DELIVERY", - hint: "Remote notifications and Live Activities" - ) + if content == .controls { + SettingsSectionHeader( + label: "PUSH DELIVERY", + hint: "Remote notifications and Live Activities" + ) - enableNotificationsControl + enableNotificationsControl - VStack(spacing: 8) { - PushToggleRow( - symbol: "bell.badge", - title: "Notifications", - subtitle: "Approvals, replies, and failures", - isOn: notificationsBinding - ) - PushToggleRow( - symbol: "square.stack.3d.up", - title: "Live Activities", - subtitle: "Agent runs on the Lock Screen", - isOn: liveActivitiesBinding - ) - PushToggleRow( - symbol: "eye.slash", - title: "Hide details", - subtitle: "Use private Lock Screen previews", - isOn: hideDetailsBinding - ) - PushToggleRow( - symbol: "moon", - title: "Quiet hours", - subtitle: pushService.prefs.quietHoursEnabled - ? "\(pushService.prefs.quietHoursStart)–\(pushService.prefs.quietHoursEnd) · \(Self.shortTimezone(pushService.prefs.quietHoursTimezone))" - : "Mute pushes on a schedule", - isOn: quietHoursBinding - ) + VStack(spacing: 8) { + PushToggleRow( + symbol: "bell.badge", + title: "Notifications", + subtitle: "Approvals, replies, and failures", + isOn: notificationsBinding + ) + PushToggleRow( + symbol: "square.stack.3d.up", + title: "Live Activities", + subtitle: "Agent runs on the Lock Screen", + isOn: liveActivitiesBinding + ) + PushToggleRow( + symbol: "eye.slash", + title: "Hide details", + subtitle: "Use private Lock Screen previews", + isOn: hideDetailsBinding + ) + PushToggleRow( + symbol: "moon", + title: "Quiet hours", + subtitle: pushService.prefs.quietHoursEnabled + ? "\(pushService.prefs.quietHoursStart)–\(pushService.prefs.quietHoursEnd) · \(Self.shortTimezone(pushService.prefs.quietHoursTimezone))" + : "Mute pushes on a schedule", + isOn: quietHoursBinding + ) - if pushService.prefs.quietHoursEnabled { - quietHoursPickers + if pushService.prefs.quietHoursEnabled { + quietHoursPickers + } } - } - - DisclosureGroup(isExpanded: $showsDiagnostics) { + } else { + SettingsSectionHeader(label: "DELIVERY DIAGNOSTICS") diagnosticsContent - .padding(.top, 8) - } label: { - Label("Delivery diagnostics", systemImage: "stethoscope") - .font(.subheadline) - .foregroundStyle(ADEColor.textSecondary) - .frame(maxWidth: .infinity, minHeight: 44, alignment: .leading) } - .tint(ADEColor.textSecondary) } .task { await pushService.refreshNotificationSettings() From de5afa1d22933d460e797cf7847b53cbce4da610 Mon Sep 17 00:00:00 2001 From: Arul Sharma <31745423+arul28@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:30:46 -0400 Subject: [PATCH 3/3] =?UTF-8?q?ship:=20iteration=201=20=E2=80=94=20address?= =?UTF-8?q?=20mobile=20settings=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/ios/ADE/Views/Hub/HubComponents.swift | 2 +- .../Settings/ConnectionSettingsView.swift | 41 +++++++----- .../Settings/SettingsDiagnosticsSection.swift | 63 ++++++++++++++----- .../Settings/SettingsPairingSection.swift | 12 +++- 4 files changed, 84 insertions(+), 34 deletions(-) diff --git a/apps/ios/ADE/Views/Hub/HubComponents.swift b/apps/ios/ADE/Views/Hub/HubComponents.swift index d59522bf4..962c351b4 100644 --- a/apps/ios/ADE/Views/Hub/HubComponents.swift +++ b/apps/ios/ADE/Views/Hub/HubComponents.swift @@ -164,7 +164,7 @@ struct HubConnectionPill: View { } } .padding(.horizontal, 11) - .padding(.vertical, transportLabel == nil ? 8 : 5) + .padding(.vertical, 8) .background(ADEColor.cardBackground.opacity(0.62), in: Capsule()) .overlay(Capsule().stroke(ADEColor.border.opacity(0.8), lineWidth: 1)) } diff --git a/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift b/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift index b0eaa0062..c716b10ab 100644 --- a/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift +++ b/apps/ios/ADE/Views/Settings/ConnectionSettingsView.swift @@ -48,7 +48,8 @@ struct ConnectionSettingsView: View { SettingsPairingSection( snapshot: presentationModel.pairingSnapshot, - presentedSheet: $presentedSheet + presentedSheet: $presentedSheet, + initiallyExpanded: true ) } .padding(.horizontal, 16) @@ -996,21 +997,18 @@ struct SettingsMachinesSection: View { let tappable = !entry.isCurrent && connectingId == nil VStack(alignment: .leading, spacing: 0) { - Button { - connect(entry) - } label: { - MachineRowView( - deviceSymbol: deviceSymbol(entry), - title: entry.name, - routeHint: entry.routeHint, - online: entry.online, - isAuthenticatedCurrent: entry.isCurrent, - statusPill: entry.isCurrent ? .connected : nil, - affordance: rowAffordance(entry, isConnecting: isConnecting), - surface: .row - ) + Group { + if tappable { + Button { + connect(entry) + } label: { + machineRowLabel(entry, isConnecting: isConnecting) + } + .buttonStyle(ADEScaleButtonStyle()) + } else { + machineRowLabel(entry, isConnecting: isConnecting) + } } - .buttonStyle(ADEScaleButtonStyle()) .accessibilityLabel("\(entry.name), \(entry.routeHint)") .accessibilityHint(tappable ? "Connect." : "") .contextMenu { @@ -1053,6 +1051,19 @@ struct SettingsMachinesSection: View { } } + private func machineRowLabel(_ entry: Entry, isConnecting: Bool) -> some View { + MachineRowView( + deviceSymbol: deviceSymbol(entry), + title: entry.name, + routeHint: entry.routeHint, + online: entry.online, + isAuthenticatedCurrent: entry.isCurrent, + statusPill: entry.isCurrent ? .connected : nil, + affordance: rowAffordance(entry, isConnecting: isConnecting), + surface: .row + ) + } + private func accountMachine(from entry: Entry) -> AccountMachine? { guard case .account(let machine) = entry.kind else { return nil } return machine diff --git a/apps/ios/ADE/Views/Settings/SettingsDiagnosticsSection.swift b/apps/ios/ADE/Views/Settings/SettingsDiagnosticsSection.swift index 911bcd3cd..f19e88ac9 100644 --- a/apps/ios/ADE/Views/Settings/SettingsDiagnosticsSection.swift +++ b/apps/ios/ADE/Views/Settings/SettingsDiagnosticsSection.swift @@ -16,26 +16,29 @@ struct SettingsDiagnosticsSection: View { var body: some View { VStack(alignment: .leading, spacing: 18) { - if content != .about, - snapshot.connectionRoute != nil || snapshot.connectionPerformance != nil { + if content != .about { VStack(alignment: .leading, spacing: 10) { SettingsSectionHeader(label: "CONNECTION DETAILS") - VStack(spacing: 10) { - if let route = snapshot.connectionRoute { - SettingsDetailRow( - symbol: "point.3.connected.trianglepath.dotted", - label: "Route", - value: route - ) - } - - if let performance = snapshot.connectionPerformance { - SettingsDetailRow( - symbol: "timer", - label: "Last connection", - value: performance - ) + if snapshot.connectionRoute == nil, snapshot.connectionPerformance == nil { + SettingsConnectionDetailsEmptyState() + } else { + VStack(spacing: 10) { + if let route = snapshot.connectionRoute { + SettingsDetailRow( + symbol: "point.3.connected.trianglepath.dotted", + label: "Route", + value: route + ) + } + + if let performance = snapshot.connectionPerformance { + SettingsDetailRow( + symbol: "timer", + label: "Last connection", + value: performance + ) + } } } } @@ -89,6 +92,32 @@ struct SettingsDiagnosticsSection: View { } } +private struct SettingsConnectionDetailsEmptyState: View { + var body: some View { + HStack(spacing: 12) { + Image(systemName: "desktopcomputer.trianglebadge.exclamationmark") + .font(.system(size: 14, weight: .semibold)) + .foregroundStyle(ADEColor.textSecondary) + + Text("Connect to a machine to see route and performance details.") + .font(.subheadline) + .foregroundStyle(ADEColor.textSecondary) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.horizontal, 14) + .padding(.vertical, 16) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(ADEColor.surfaceBackground.opacity(0.55)) + ) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(ADEColor.border.opacity(0.45), lineWidth: 0.75) + ) + } +} + struct SettingsDetailRow: View { let symbol: String let label: String diff --git a/apps/ios/ADE/Views/Settings/SettingsPairingSection.swift b/apps/ios/ADE/Views/Settings/SettingsPairingSection.swift index c792c7bd5..324223711 100644 --- a/apps/ios/ADE/Views/Settings/SettingsPairingSection.swift +++ b/apps/ios/ADE/Views/Settings/SettingsPairingSection.swift @@ -3,9 +3,19 @@ import SwiftUI struct SettingsPairingSection: View { let snapshot: SettingsPairingSnapshot @Binding var presentedSheet: SettingsPairSheetRoute? - @State private var showsAddMachine = false + @State private var showsAddMachine: Bool @ObservedObject private var accountService = AccountService.shared + init( + snapshot: SettingsPairingSnapshot, + presentedSheet: Binding, + initiallyExpanded: Bool = false + ) { + self.snapshot = snapshot + self._presentedSheet = presentedSheet + self._showsAddMachine = State(initialValue: initiallyExpanded) + } + var body: some View { VStack(alignment: .leading, spacing: 12) { DisclosureGroup(isExpanded: $showsAddMachine) {