Replace the SF Symbol server.rack with the custom KisaniLogoMark extracted into a shared component used by both ConnectView and BackupView. Introduce ConnectStep enum (credentials → folder → ready) in ConnectViewModel. The primary CTA changes label and colour at each step: "Connect" → "Select Destination" → "Get Started". The folder row animates in only after successful authentication; its border goes green once a valid path is chosen. Status message updates live with the current step. Submitting the password field triggers verify(). Rename project.yml name to Kisani so xcodegen produces Kisani.xcodeproj. Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
117 lines
3.5 KiB
Swift
117 lines
3.5 KiB
Swift
import Foundation
|
|
|
|
enum ConnectField { case host, username, password }
|
|
|
|
enum ConnectStep: Equatable {
|
|
case credentials // not yet authenticated
|
|
case folder // authenticated, no destination chosen
|
|
case ready // authenticated + valid destination chosen
|
|
}
|
|
|
|
@MainActor
|
|
final class ConnectViewModel: ObservableObject {
|
|
@Published var host: String
|
|
@Published var username: String
|
|
@Published var password: String
|
|
@Published var remotePath: String
|
|
@Published var selectedProtocol: NASProtocol
|
|
@Published var isVerifying = false
|
|
@Published var isConnected: Bool
|
|
@Published var verifyError: String? = nil
|
|
@Published var showFolderBrowser = false
|
|
|
|
init() {
|
|
if let conn = ConnectionStore.shared.savedConnection {
|
|
host = conn.host
|
|
username = conn.username
|
|
password = conn.password
|
|
remotePath = conn.remotePath
|
|
selectedProtocol = conn.nasProtocol
|
|
isConnected = true
|
|
} else {
|
|
host = ""
|
|
username = ""
|
|
password = ""
|
|
remotePath = "/"
|
|
selectedProtocol = .smb
|
|
isConnected = false
|
|
}
|
|
}
|
|
|
|
var step: ConnectStep {
|
|
guard isConnected else { return .credentials }
|
|
return remotePath == "/" ? .folder : .ready
|
|
}
|
|
|
|
var canProceed: Bool {
|
|
switch step {
|
|
case .credentials:
|
|
return !host.trimmingCharacters(in: .whitespaces).isEmpty
|
|
&& !username.isEmpty
|
|
&& !password.isEmpty
|
|
&& !isVerifying
|
|
case .folder, .ready:
|
|
return true
|
|
}
|
|
}
|
|
|
|
var statusMessage: String {
|
|
switch step {
|
|
case .credentials:
|
|
return verifyError != nil ? verifyError! : "Enter your NAS credentials"
|
|
case .folder:
|
|
return "Authenticated · select a destination folder"
|
|
case .ready:
|
|
return "Ready to sync"
|
|
}
|
|
}
|
|
|
|
var statusIsError: Bool { verifyError != nil && step == .credentials }
|
|
var statusIsPositive: Bool { step == .folder || step == .ready }
|
|
|
|
func proceed() async {
|
|
switch step {
|
|
case .credentials:
|
|
await verify()
|
|
case .folder:
|
|
showFolderBrowser = true
|
|
case .ready:
|
|
break // navigation handled in the view
|
|
}
|
|
}
|
|
|
|
func verify() async {
|
|
let service: any NASTransferProtocol = selectedProtocol == .smb ? SMBService() : SFTPService()
|
|
await verifyWith(transfer: service)
|
|
}
|
|
|
|
func verifyWith(transfer: any NASTransferProtocol) async {
|
|
isVerifying = true
|
|
isConnected = false
|
|
verifyError = nil
|
|
|
|
let connection = NASConnection(
|
|
host: host.trimmingCharacters(in: .whitespaces),
|
|
nasProtocol: selectedProtocol,
|
|
username: username,
|
|
password: password,
|
|
remotePath: remotePath
|
|
)
|
|
|
|
do {
|
|
try await transfer.connect(
|
|
to: connection.host, port: connection.port,
|
|
username: connection.username, password: connection.password
|
|
)
|
|
transfer.disconnect()
|
|
isConnected = true
|
|
ConnectionStore.shared.savedConnection = connection
|
|
} catch let e as BackupError {
|
|
verifyError = e.errorDescription
|
|
} catch {
|
|
verifyError = error.localizedDescription
|
|
}
|
|
isVerifying = false
|
|
}
|
|
}
|