fix(ios): Fix iOS session creation and file browser issues

- Fix working directory validation to fall back to home directory when iOS sends invalid paths
- Fix file browser API client to handle correct response format (absolutePath + files)
- Improve session creation header design with translucent material and fixed height
- Add authentication support placeholder for future API protection
- Add better error logging for debugging API issues
- Move TerminalTextFieldStyle to SessionCreateView for better organization

The iOS app can now create sessions successfully and browse directories without errors.
This commit is contained in:
Peter Steinberger 2025-06-20 05:44:46 +02:00
parent bb23cbcc32
commit 5142cf1eb6
5 changed files with 85 additions and 36 deletions

View file

@ -97,7 +97,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1600;
LastUpgradeCheck = 2600;
TargetAttributes = {
788687F02DFF4FCB00B22C15 = {
CreatedOnToolsVersion = 16.0;
@ -205,6 +205,7 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
@ -329,6 +330,7 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
@ -377,4 +379,4 @@
/* End XCSwiftPackageProductDependency section */
};
rootObject = 0DD83B4196D66608380E46BA /* Project object */;
}
}

View file

@ -283,9 +283,15 @@ class APIClient: APIClientProtocol {
}
}
private func addAuthenticationIfNeeded(_ request: inout URLRequest) {
// For now, we don't have authentication configured in the iOS app
// This is a placeholder for future authentication support
// The server might be running without password protection
}
// MARK: - File System Operations
func browseDirectory(path: String) async throws -> [FileEntry] {
func browseDirectory(path: String) async throws -> (absolutePath: String, files: [FileEntry]) {
guard let baseURL = baseURL else {
throw APIError.noServerConfigured
}
@ -300,11 +306,31 @@ class APIClient: APIClientProtocol {
var request = URLRequest(url: url)
request.httpMethod = "GET"
// Add authentication header if needed
addAuthenticationIfNeeded(&request)
let (data, response) = try await session.data(for: request)
// Log response for debugging
if let httpResponse = response as? HTTPURLResponse {
print("[APIClient] Browse directory response: \(httpResponse.statusCode)")
if httpResponse.statusCode >= 400 {
if let errorString = String(data: data, encoding: .utf8) {
print("[APIClient] Error response body: \(errorString)")
}
}
}
try validateResponse(response)
let entries = try decoder.decode([FileEntry].self, from: data)
return entries
// Decode the response which includes absolutePath and files
struct BrowseResponse: Codable {
let absolutePath: String
let files: [FileEntry]
}
let browseResponse = try decoder.decode(BrowseResponse.self, from: data)
return (absolutePath: browseResponse.absolutePath, files: browseResponse.files)
}
func createDirectory(path: String) async throws {

View file

@ -182,21 +182,3 @@ struct ServerConfigForm: View {
}
}
}
// Custom text field style
struct TerminalTextFieldStyle: TextFieldStyle {
func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.font(Theme.Typography.terminalSystem(size: 16))
.foregroundColor(Theme.Colors.terminalForeground)
.padding(Theme.Spacing.md)
.background(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.fill(Theme.Colors.cardBackground)
)
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(Theme.Colors.primaryAccent.opacity(0.3), lineWidth: 1)
)
}
}

View file

@ -297,12 +297,11 @@ class FileBrowserViewModel: ObservableObject {
defer { isLoading = false }
do {
let fetchedEntries = try await apiClient.browseDirectory(path: path)
// For now, we'll use the requested path as the current path
// The Go server doesn't return the absolute path in the response
currentPath = path
let result = try await apiClient.browseDirectory(path: path)
// Use the absolute path returned by the server
currentPath = result.absolutePath
withAnimation(.easeInOut(duration: 0.2)) {
entries = fetchedEntries
entries = result.files
}
} catch {
print("[FileBrowser] Failed to load directory: \(error)")

View file

@ -1,5 +1,23 @@
import SwiftUI
// Custom text field style for terminal-like appearance
struct TerminalTextFieldStyle: TextFieldStyle {
func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.font(Theme.Typography.terminalSystem(size: 16))
.foregroundColor(Theme.Colors.terminalForeground)
.padding()
.background(
RoundedRectangle(cornerRadius: Theme.CornerRadius.small)
.fill(Theme.Colors.cardBackground)
)
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.small)
.stroke(Theme.Colors.cardBorder, lineWidth: 1)
)
}
}
struct SessionCreateView: View {
@Binding var isPresented: Bool
let onCreated: (String) -> Void
@ -208,14 +226,29 @@ struct SessionCreateView: View {
.navigationBarHidden(true)
.safeAreaInset(edge: .top) {
// Custom Navigation Bar with proper safe area handling
VStack(spacing: 0) {
ZStack {
// Background with blur and transparency
Rectangle()
.fill(.ultraThinMaterial)
.background(Theme.Colors.terminalBackground.opacity(0.5))
// Content
HStack {
Button("Cancel") {
Button(action: {
HapticFeedback.impact(.light)
isPresented = false
}) {
Text("Cancel")
.font(.system(size: 17))
.foregroundColor(Theme.Colors.errorAccent)
}
.font(.system(size: 17))
.foregroundColor(Theme.Colors.errorAccent)
.buttonStyle(PlainButtonStyle())
Spacer()
Text("New Session")
.font(.system(size: 17, weight: .semibold))
.foregroundColor(Theme.Colors.terminalForeground)
Spacer()
@ -230,16 +263,23 @@ struct SessionCreateView: View {
} else {
Text("Create")
.font(.system(size: 17, weight: .semibold))
.foregroundColor(command.isEmpty ? Theme.Colors.primaryAccent.opacity(0.5) : Theme.Colors.primaryAccent)
}
}
.foregroundColor(Theme.Colors.primaryAccent)
.buttonStyle(PlainButtonStyle())
.disabled(isCreating || command.isEmpty)
}
.padding(.horizontal)
.padding(.horizontal, 20)
.padding(.vertical, 16)
.frame(maxWidth: .infinity)
.background(.ultraThinMaterial.opacity(0.8))
}
.frame(height: 56) // Fixed height for the header
.overlay(
// Subtle bottom border
Rectangle()
.fill(Theme.Colors.cardBorder.opacity(0.15))
.frame(height: 0.5),
alignment: .bottom
)
}
.onAppear {
loadDefaults()