diff --git a/Firmware/cardboy-companion/cardboy-companion/cardboy-companion/ContentView.swift b/Firmware/cardboy-companion/cardboy-companion/cardboy-companion/ContentView.swift index da11b6f..55a6ff8 100644 --- a/Firmware/cardboy-companion/cardboy-companion/cardboy-companion/ContentView.swift +++ b/Firmware/cardboy-companion/cardboy-companion/cardboy-companion/ContentView.swift @@ -216,6 +216,14 @@ private struct FileManagerTabView: View { if let operation = manager.activeFileOperation { FileOperationHUD(operation: operation) } + + if manager.connectionState != .ready { + ConnectionOverlay( + state: manager.connectionState, + statusMessage: manager.statusMessage, + retryAction: manager.forceRescan + ) + } } .fileImporter( isPresented: $showingImporter, @@ -371,6 +379,65 @@ private struct FileOperationHUD: View { } } +private struct ConnectionOverlay: View { + let state: TimeSyncManager.ConnectionState + let statusMessage: String + let retryAction: () -> Void + + private var showsSpinner: Bool { + switch state { + case .scanning, .connecting, .discovering: + return true + default: + return false + } + } + + private var canRetry: Bool { + switch state { + case .failed, .idle: + return true + default: + return false + } + } + + var body: some View { + ZStack { + Color.black.opacity(0.35) + .ignoresSafeArea() + + VStack(spacing: 16) { + if showsSpinner { + ProgressView() + .progressViewStyle(.circular) + .scaleEffect(1.2) + } else { + Image(systemName: "exclamationmark.triangle") + .font(.system(size: 42, weight: .semibold)) + .foregroundColor(.yellow) + } + + Text(statusMessage) + .multilineTextAlignment(.center) + .font(.headline) + .foregroundColor(.primary) + + if canRetry { + Button("Try Again", action: retryAction) + .buttonStyle(.borderedProminent) + } + } + .padding(28) + .frame(maxWidth: 320) + .background(.ultraThinMaterial) + .cornerRadius(20) + .shadow(radius: 12) + } + .transition(.opacity) + } +} + extension URL: Identifiable { public var id: String { absoluteString } }