From 38600193b7468d7f102af10832c65057fb0cc9ee Mon Sep 17 00:00:00 2001 From: Schwifty Date: Mon, 23 Mar 2026 04:16:04 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20auto-write=20config=20immediately=20afte?= =?UTF-8?q?r=20connect=20=E2=80=94=20no=20manual=20tap=20needed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the two-phase flow where the user had to confirm beacon flashing and tap "Write Config". Now goes straight from connect → write → register in one shot. The dxsmartConnectedView UI is removed. --- PayfritBeacon/Views/ScanView.swift | 112 +++++------------------------ 1 file changed, 17 insertions(+), 95 deletions(-) diff --git a/PayfritBeacon/Views/ScanView.swift b/PayfritBeacon/Views/ScanView.swift index 5b61d3a..4e7c55c 100644 --- a/PayfritBeacon/Views/ScanView.swift +++ b/PayfritBeacon/Views/ScanView.swift @@ -209,8 +209,8 @@ struct ScanView: View { progressView(title: "Connecting…", message: statusMessage) case .connected: - // DXSmart: beacon is flashing, show write button - dxsmartConnectedView + // Legacy — auto-write skips this state now + progressView(title: "Connected…", message: statusMessage) case .writing: progressView(title: "Writing Config…", message: statusMessage) @@ -319,51 +319,7 @@ struct ScanView: View { // MARK: - DXSmart Connected View - private var dxsmartConnectedView: some View { - VStack(spacing: 24) { - Spacer() - - Image(systemName: "light.beacon.max") - .font(.system(size: 64)) - .foregroundStyle(Color.payfritGreen) - .modifier(PulseEffectModifier()) - - Text("Connected — Beacon is Flashing") - .font(.title2.bold()) - - Text("Confirm the beacon LED is flashing, then tap Write Config to program it.\n\nThe beacon will timeout if you wait too long.") - .font(.subheadline) - .foregroundStyle(.secondary) - .multilineTextAlignment(.center) - .padding(.horizontal, 32) - - Button { - Task { await writeConfigToConnectedBeacon() } - } label: { - HStack { - Image(systemName: "arrow.down.doc") - Text("Write Config") - } - .frame(maxWidth: .infinity) - } - .buttonStyle(.borderedProminent) - .tint(Color.payfritGreen) - .controlSize(.large) - .padding(.horizontal, 32) - - Button("Cancel") { - cancelProvisioning() - } - .foregroundStyle(.secondary) - - // Show diagnostic log - if !provisionLog.entries.isEmpty { - diagnosticLogView - } - - Spacer() - } - } + // dxsmartConnectedView removed — auto-write skips the manual confirmation step // MARK: - Progress / Success / Failed Views @@ -614,6 +570,8 @@ struct ScanView: View { // Create appropriate provisioner let provisioner = makeProvisioner(for: beacon) + pendingProvisioner = provisioner + pendingConfig = config // Wire up real-time status updates from provisioner if let dxProvisioner = provisioner as? DXSmartProvisioner { @@ -640,14 +598,8 @@ struct ScanView: View { provisionLog?.log("disconnect", "Unexpected disconnect: \(reason)", isError: true) - // CP-28: disconnect during .connected is expected — beacon keeps - // flashing after BLE drops. We'll reconnect when user taps Write Config. - if self.provisioningState == .connected { - provisionLog?.log("disconnect", "CP-28 idle disconnect — beacon still flashing, ignoring") - return - } - // For all other active states, treat disconnect as failure - if self.provisioningState == .connecting || self.provisioningState == .connected || + // For all active states, treat disconnect as failure + if self.provisioningState == .connecting || self.provisioningState == .writing || self.provisioningState == .verifying { self.provisioningState = .failed self.errorMessage = "Beacon disconnected: \(reason)" @@ -659,48 +611,16 @@ struct ScanView: View { try await provisioner.connect() provisionLog.log("connect", "Connected and authenticated successfully") - // CP-28: stop at connected state, wait for user to confirm flashing - provisioningState = .connected - pendingConfig = config - pendingProvisioner = provisioner - - } catch { - provisionLog.log("error", "Provisioning failed after \(provisionLog.elapsed): \(error.localizedDescription)", isError: true) - provisioningState = .failed - errorMessage = error.localizedDescription - } - } - - // Store for DXSmart two-phase flow - @State private var pendingConfig: BeaconConfig? - @State private var pendingProvisioner: (any BeaconProvisioner)? - - private func writeConfigToConnectedBeacon() async { - guard let config = pendingConfig, - let provisioner = pendingProvisioner, - let sp = selectedServicePoint, - let ns = namespace, - let token = appState.token else { return } - - provisioningState = .writing - writesCompleted = false - statusMessage = "Writing config to DX-Smart…" - - do { - // Reconnect if the beacon dropped BLE during the "confirm flashing" wait - if !provisioner.isConnected { - provisionLog.log("write", "Beacon disconnected while waiting — reconnecting…") - statusMessage = "Reconnecting to beacon…" - try await provisioner.connect() - provisionLog.log("write", "Reconnected — writing config…") - statusMessage = "Writing config to DX-Smart…" - } + // Auto-fire write immediately — no pause needed + provisioningState = .writing + writesCompleted = false + statusMessage = "Writing config to DX-Smart…" + provisionLog.log("write", "Auto-writing config (no user tap needed)…") try await provisioner.writeConfig(config) writesCompleted = true // Give the beacon 200ms to process SaveConfig before dropping the BLE link. - // The beacon MCU needs the connection terminated to finalize its reboot/save cycle. try? await Task.sleep(nanoseconds: 200_000_000) provisioner.disconnect() @@ -719,14 +639,16 @@ struct ScanView: View { statusMessage = "\(sp.name) — DX-Smart\nUUID: \(config.formattedUUID.prefix(13))…\nMajor: \(config.major) Minor: \(config.minor)" } catch { + provisionLog.log("error", "Provisioning failed after \(provisionLog.elapsed): \(error.localizedDescription)", isError: true) provisioningState = .failed errorMessage = error.localizedDescription } - - pendingConfig = nil - pendingProvisioner = nil } + // Kept for cancel/reset and registerAnywayAfterFailure fallback + @State private var pendingConfig: BeaconConfig? + @State private var pendingProvisioner: (any BeaconProvisioner)? + private func registerAnywayAfterFailure() async { guard let sp = selectedServicePoint, let ns = namespace,