mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
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:
parent
bb23cbcc32
commit
5142cf1eb6
5 changed files with 85 additions and 36 deletions
|
|
@ -97,7 +97,7 @@
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 1600;
|
LastSwiftUpdateCheck = 1600;
|
||||||
LastUpgradeCheck = 1600;
|
LastUpgradeCheck = 2600;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
788687F02DFF4FCB00B22C15 = {
|
788687F02DFF4FCB00B22C15 = {
|
||||||
CreatedOnToolsVersion = 16.0;
|
CreatedOnToolsVersion = 16.0;
|
||||||
|
|
@ -205,6 +205,7 @@
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
};
|
};
|
||||||
|
|
@ -329,6 +330,7 @@
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
|
// 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 {
|
guard let baseURL = baseURL else {
|
||||||
throw APIError.noServerConfigured
|
throw APIError.noServerConfigured
|
||||||
}
|
}
|
||||||
|
|
@ -300,11 +306,31 @@ class APIClient: APIClientProtocol {
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
request.httpMethod = "GET"
|
request.httpMethod = "GET"
|
||||||
|
|
||||||
|
// Add authentication header if needed
|
||||||
|
addAuthenticationIfNeeded(&request)
|
||||||
|
|
||||||
let (data, response) = try await session.data(for: 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)
|
try validateResponse(response)
|
||||||
|
|
||||||
let entries = try decoder.decode([FileEntry].self, from: data)
|
// Decode the response which includes absolutePath and files
|
||||||
return entries
|
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 {
|
func createDirectory(path: String) async throws {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -297,12 +297,11 @@ class FileBrowserViewModel: ObservableObject {
|
||||||
defer { isLoading = false }
|
defer { isLoading = false }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let fetchedEntries = try await apiClient.browseDirectory(path: path)
|
let result = try await apiClient.browseDirectory(path: path)
|
||||||
// For now, we'll use the requested path as the current path
|
// Use the absolute path returned by the server
|
||||||
// The Go server doesn't return the absolute path in the response
|
currentPath = result.absolutePath
|
||||||
currentPath = path
|
|
||||||
withAnimation(.easeInOut(duration: 0.2)) {
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
entries = fetchedEntries
|
entries = result.files
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
print("[FileBrowser] Failed to load directory: \(error)")
|
print("[FileBrowser] Failed to load directory: \(error)")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,23 @@
|
||||||
import SwiftUI
|
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 {
|
struct SessionCreateView: View {
|
||||||
@Binding var isPresented: Bool
|
@Binding var isPresented: Bool
|
||||||
let onCreated: (String) -> Void
|
let onCreated: (String) -> Void
|
||||||
|
|
@ -208,14 +226,29 @@ struct SessionCreateView: View {
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.safeAreaInset(edge: .top) {
|
.safeAreaInset(edge: .top) {
|
||||||
// Custom Navigation Bar with proper safe area handling
|
// 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 {
|
HStack {
|
||||||
Button("Cancel") {
|
Button(action: {
|
||||||
HapticFeedback.impact(.light)
|
HapticFeedback.impact(.light)
|
||||||
isPresented = false
|
isPresented = false
|
||||||
}
|
}) {
|
||||||
|
Text("Cancel")
|
||||||
.font(.system(size: 17))
|
.font(.system(size: 17))
|
||||||
.foregroundColor(Theme.Colors.errorAccent)
|
.foregroundColor(Theme.Colors.errorAccent)
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("New Session")
|
||||||
|
.font(.system(size: 17, weight: .semibold))
|
||||||
|
.foregroundColor(Theme.Colors.terminalForeground)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|
@ -230,16 +263,23 @@ struct SessionCreateView: View {
|
||||||
} else {
|
} else {
|
||||||
Text("Create")
|
Text("Create")
|
||||||
.font(.system(size: 17, weight: .semibold))
|
.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)
|
.disabled(isCreating || command.isEmpty)
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal, 20)
|
||||||
.padding(.vertical, 16)
|
.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 {
|
.onAppear {
|
||||||
loadDefaults()
|
loadDefaults()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue