diff --git a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift index 104bf85c..69250cd7 100644 --- a/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift +++ b/Sources/ColumbaApp/Views/Settings/BackgroundTransportView.swift @@ -196,24 +196,7 @@ struct BackgroundTransportView: View { @ViewBuilder private var actionButton: some View { - if isEnabledState { - // Disable: clear on-demand + stop (a bare stop() would auto-reconnect). - Button { - errorMessage = nil - Task { - do { try await tunnel.disable() } - catch { errorMessage = error.localizedDescription } - } - } label: { - Text("Disable Background Transport") - .font(.headline) - .foregroundStyle(.white) - .frame(maxWidth: .infinity) - .padding(.vertical, 16) - .background(Theme.error) - .clipShape(RoundedRectangle(cornerRadius: Theme.cornerRadiusLarge)) - } - } else { + if !isEnabledState { // Enable: install the profile (also arms on-demand) then start. Button { enable() diff --git a/Sources/ColumbaApp/Views/Settings/SettingsView.swift b/Sources/ColumbaApp/Views/Settings/SettingsView.swift index 2a241421..04051ef8 100644 --- a/Sources/ColumbaApp/Views/Settings/SettingsView.swift +++ b/Sources/ColumbaApp/Views/Settings/SettingsView.swift @@ -563,7 +563,7 @@ struct SettingsView: View { // profile (re-arms on-demand) and reconnects in this session. Button { guard !isReticulumActionWorking else { return } - let stopping = tunnel.isRunning + let stopping = reticulumIsOn(tunnel) isReticulumActionWorking = true Task { defer { isReticulumActionWorking = false } @@ -583,14 +583,14 @@ struct SettingsView: View { if isReticulumActionWorking { ProgressView().tint(.white) } else { - Text(tunnel.isRunning ? String(localized: "Stop") : String(localized: "Start")) + Text(reticulumIsOn(tunnel) ? String(localized: "Stop") : String(localized: "Start")) } } .font(.subheadline.weight(.semibold)) .foregroundStyle(.white) .frame(maxWidth: .infinity) .padding(.vertical, 14) - .background(tunnel.isRunning + .background(reticulumIsOn(tunnel) ? LinearGradient(colors: [Theme.error], startPoint: .top, endPoint: .bottom) : Theme.accentGradient) .clipShape(RoundedRectangle(cornerRadius: Theme.cornerRadiusMedium)) @@ -643,6 +643,24 @@ struct SettingsView: View { @unknown default: return Theme.textSecondary } } + + /// Whether the card's action button should present the "Stop" (disable) + /// affordance. Mirrors the explainer sheet's `isEnabledState`: the tunnel + /// counts as "on" whenever it is connected, mid-connect, reasserting, or + /// tearing down - so a user can cancel a slow connect / in-progress stop + /// instead of being stuck on "Start". (A bare `isRunning` is only true for + /// `.connected`, which hides the stop action during `.connecting` / + /// `.reasserting` - the gap PR #217's removed explainer button used to cover.) + private func reticulumIsOn(_ tunnel: TunnelManager) -> Bool { + switch tunnel.status { + case .connected, .connecting, .reasserting, .disconnecting: + return true + case .disconnected, .invalid: + return false + @unknown default: + return tunnel.isEnabled + } + } #endif // MARK: - Identity Card