import Observation import SwiftUI /// View for establishing connection to a VibeTunnel server. /// /// Displays the app branding and provides interface for entering /// server connection details with saved server management. struct ConnectionView: View { @Environment(ConnectionManager.self) var connectionManager @State private var networkMonitor = NetworkMonitor.shared @State private var viewModel = ConnectionViewModel() @State private var logoScale: CGFloat = 0.8 @State private var contentOpacity: Double = 0 var body: some View { NavigationStack { ScrollView { // Content VStack(spacing: Theme.Spacing.extraExtraLarge) { // Logo and Title VStack(spacing: Theme.Spacing.large) { ZStack { // Glow effect Image(systemName: "terminal.fill") .font(.system(size: 80)) .foregroundColor(Theme.Colors.primaryAccent) .blur(radius: 20) .opacity(0.5) // Main icon Image(systemName: "terminal.fill") .font(.system(size: 80)) .foregroundColor(Theme.Colors.primaryAccent) .glowEffect() } .scaleEffect(logoScale) .onAppear { withAnimation(Theme.Animation.smooth.delay(0.1)) { logoScale = 1.0 } } VStack(spacing: Theme.Spacing.small) { Text("VibeTunnel") .font(.system(size: 42, weight: .bold, design: .rounded)) .foregroundColor(Theme.Colors.terminalForeground) Text("Terminal Multiplexer") .font(Theme.Typography.terminalSystem(size: 16)) .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) .tracking(2) // Network status ConnectionStatusView() .padding(.top, Theme.Spacing.small) } } .padding(.top, 60) // Connection Form ServerConfigForm( host: $viewModel.host, port: $viewModel.port, name: $viewModel.name, password: $viewModel.password, isConnecting: viewModel.isConnecting, errorMessage: viewModel.errorMessage, onConnect: connectToServer ) .opacity(contentOpacity) .onAppear { withAnimation(Theme.Animation.smooth.delay(0.3)) { contentOpacity = 1.0 } } Spacer() } .padding() } .scrollBounceBehavior(.basedOnSize) .toolbar(.hidden, for: .navigationBar) .background { // Background Theme.Colors.terminalBackground .ignoresSafeArea() } } .navigationViewStyle(StackNavigationViewStyle()) .preferredColorScheme(.dark) .onAppear { viewModel.loadLastConnection() } .sheet(isPresented: $viewModel.showLoginView) { if let config = viewModel.pendingServerConfig, let authService = connectionManager.authenticationService { LoginView( isPresented: $viewModel.showLoginView, serverConfig: config, authenticationService: authService ) { // Authentication successful, mark as connected connectionManager.isConnected = true } } } } private func connectToServer() { guard networkMonitor.isConnected else { viewModel.errorMessage = "No internet connection available" return } Task { await viewModel.testConnection { config in connectionManager.saveConnection(config) // Show login view to authenticate viewModel.showLoginView = true } } } } /// View model for managing connection form state and validation. @Observable class ConnectionViewModel { var host: String = "127.0.0.1" var port: String = "4020" var name: String = "" var password: String = "" var isConnecting: Bool = false var errorMessage: String? var showLoginView: Bool = false var pendingServerConfig: ServerConfig? func loadLastConnection() { if let config = UserDefaults.standard.data(forKey: "savedServerConfig"), let serverConfig = try? JSONDecoder().decode(ServerConfig.self, from: config) { self.host = serverConfig.host self.port = String(serverConfig.port) self.name = serverConfig.name ?? "" } } @MainActor func testConnection(onSuccess: @escaping (ServerConfig) -> Void) async { errorMessage = nil guard !host.isEmpty else { errorMessage = "Please enter a server address" return } guard let portNumber = Int(port), portNumber > 0, portNumber <= 65_535 else { errorMessage = "Please enter a valid port number" return } isConnecting = true let config = ServerConfig( host: host, port: portNumber, name: name.isEmpty ? nil : name ) do { // Test basic connectivity by checking health endpoint let url = config.baseURL.appendingPathComponent("api/health") let request = URLRequest(url: url) let (_, response) = try await URLSession.shared.data(for: request) if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { // Connection successful, save config and trigger authentication pendingServerConfig = config onSuccess(config) } else { errorMessage = "Failed to connect to server" } } catch { if let urlError = error as? URLError { switch urlError.code { case .notConnectedToInternet: errorMessage = "No internet connection" case .cannotFindHost: errorMessage = "Cannot find server" case .cannotConnectToHost: errorMessage = "Cannot connect to server" case .timedOut: errorMessage = "Connection timed out" default: errorMessage = "Connection failed: \(error.localizedDescription)" } } else { errorMessage = "Connection failed: \(error.localizedDescription)" } } isConnecting = false } }