import SwiftUI struct BrowseView: View { let connection: NASConnection @Binding var selectedPath: String @Environment(\.dismiss) private var dismiss @State private var currentPath: String = "/" @State private var items: [NASItem] = [] @State private var isLoading = false @State private var error: String? = nil @State private var showCreateFolder = false @State private var newFolderName = "" var body: some View { NavigationStack { ZStack { AppTheme.background.ignoresSafeArea() VStack(spacing: 0) { // Path bar HStack(spacing: 8) { Image(systemName: "externaldrive") .font(.system(size: 12, weight: .medium)) .foregroundStyle(AppTheme.inkTertiary) Text(currentPath) .font(AppTheme.caption()) .foregroundStyle(AppTheme.inkSecondary) .lineLimit(1) .truncationMode(.middle) Spacer() } .padding(.horizontal, AppTheme.cardPad) .padding(.vertical, 10) .background(AppTheme.surfaceSunken) Rectangle() .fill(AppTheme.separator) .frame(height: 0.5) if isLoading { Spacer() VStack(spacing: 10) { ProgressView() .tint(AppTheme.inkTertiary) Text("Loading…") .font(AppTheme.caption()) .foregroundStyle(AppTheme.inkTertiary) } Spacer() } else if let error { Spacer() VStack(spacing: 12) { Image(systemName: "exclamationmark.triangle") .font(.system(size: 28, weight: .ultraLight)) .foregroundStyle(AppTheme.inkQuaternary) VStack(spacing: 4) { Text("Could not load folder") .font(.system(size: 14, weight: .medium)) .foregroundStyle(AppTheme.inkSecondary) Text(error) .font(AppTheme.caption()) .foregroundStyle(AppTheme.inkTertiary) .multilineTextAlignment(.center) } } .padding(.horizontal, 32) Spacer() } else { List { if currentPath != "/" { Button(action: navigateUp) { HStack(spacing: 10) { Image(systemName: "arrow.up") .font(.system(size: 13, weight: .medium)) .foregroundStyle(AppTheme.inkSecondary) .frame(width: 22) Text("Parent folder") .font(AppTheme.body()) .foregroundStyle(AppTheme.inkSecondary) Spacer() } .padding(.horizontal, 16) .padding(.vertical, 12) } .buttonStyle(PlainButtonStyle()) .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) } ForEach(items) { item in FolderRow( item: item, isSelected: selectedPath == item.path ) { if item.isDirectory { navigate(to: item.path) } else { selectedPath = item.path } } .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) } } .listStyle(.plain) } } } .navigationTitle("Choose folder") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { dismiss() } .font(AppTheme.body()) .foregroundStyle(AppTheme.inkSecondary) } ToolbarItem(placement: .navigationBarTrailing) { Button { newFolderName = "" showCreateFolder = true } label: { Image(systemName: "folder.badge.plus") .foregroundStyle(AppTheme.inkSecondary) } } ToolbarItem(placement: .bottomBar) { Button("Select this folder") { selectedPath = currentPath dismiss() } .font(.system(size: 15, weight: .semibold)) .foregroundStyle(AppTheme.ink) } } .alert("New folder", isPresented: $showCreateFolder) { TextField("Folder name", text: $newFolderName) Button("Create") { Task { await createFolder() } } Button("Cancel", role: .cancel) {} } } .task { await loadDirectory(path: currentPath) } } private func loadDirectory(path: String) async { isLoading = true error = nil do { let s: any NASTransferProtocol = connection.nasProtocol == .smb ? SMBService() : SFTPService() try await s.connect(to: connection.host, port: connection.port, username: connection.username, password: connection.password) items = try await s.listDirectory(at: path).filter { $0.isDirectory } s.disconnect() } catch let e as BackupError { self.error = e.errorDescription } catch { self.error = error.localizedDescription } isLoading = false } private func navigate(to path: String) { currentPath = path Task { await loadDirectory(path: path) } } private func navigateUp() { let parent = (currentPath as NSString).deletingLastPathComponent navigate(to: parent.isEmpty ? "/" : parent) } private func createFolder() async { guard !newFolderName.isEmpty else { return } let newPath = "\(currentPath)/\(newFolderName)" do { let s: any NASTransferProtocol = connection.nasProtocol == .smb ? SMBService() : SFTPService() try await s.connect(to: connection.host, port: connection.port, username: connection.username, password: connection.password) try await s.createDirectory(at: newPath) s.disconnect() await loadDirectory(path: currentPath) } catch {} } }