Add edit-connection flow
Edit reuses AddConnectionView with an `editing:` parameter that prefills the
form and updates in-place; password field becomes optional ("leave blank to
keep current"). Surfaced via context menu and a leading-edge swipe action on
each saved row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ public struct AddConnectionView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
let prefill: AddConnectionPrefill?
|
||||
let editing: SavedConnection?
|
||||
|
||||
@State private var displayName = ""
|
||||
@State private var host = ""
|
||||
@@ -30,11 +31,15 @@ public struct AddConnectionView: View {
|
||||
@State private var clipboardSync = true
|
||||
@State private var viewOnly = false
|
||||
@State private var notes = ""
|
||||
@State private var hasLoadedExisting = false
|
||||
|
||||
public init(prefill: AddConnectionPrefill? = nil) {
|
||||
public init(prefill: AddConnectionPrefill? = nil, editing: SavedConnection? = nil) {
|
||||
self.prefill = prefill
|
||||
self.editing = editing
|
||||
}
|
||||
|
||||
private var isEditing: Bool { editing != nil }
|
||||
|
||||
public var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
@@ -51,7 +56,8 @@ public struct AddConnectionView: View {
|
||||
#endif
|
||||
}
|
||||
Section("Authentication") {
|
||||
SecureField("VNC password", text: $password)
|
||||
SecureField(isEditing ? "Replace password (leave blank to keep current)" : "VNC password",
|
||||
text: $password)
|
||||
Text("Stored in iOS Keychain (this device only).")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -86,7 +92,7 @@ public struct AddConnectionView: View {
|
||||
.lineLimit(2...6)
|
||||
}
|
||||
}
|
||||
.navigationTitle("New Connection")
|
||||
.navigationTitle(isEditing ? "Edit Connection" : "New Connection")
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
@@ -95,38 +101,66 @@ public struct AddConnectionView: View {
|
||||
Button("Cancel") { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("Save") { save() }
|
||||
Button(isEditing ? "Save" : "Add") { save() }
|
||||
.disabled(displayName.isEmpty || host.isEmpty)
|
||||
}
|
||||
}
|
||||
.onAppear { applyPrefillIfNeeded() }
|
||||
.onAppear { loadExistingIfNeeded() }
|
||||
}
|
||||
}
|
||||
|
||||
private func applyPrefillIfNeeded() {
|
||||
guard let prefill, displayName.isEmpty, host.isEmpty else { return }
|
||||
displayName = prefill.displayName
|
||||
host = prefill.host
|
||||
port = String(prefill.port)
|
||||
private func loadExistingIfNeeded() {
|
||||
guard !hasLoadedExisting else { return }
|
||||
hasLoadedExisting = true
|
||||
if let existing = editing {
|
||||
displayName = existing.displayName
|
||||
host = existing.host
|
||||
port = String(existing.port)
|
||||
colorTag = existing.colorTag
|
||||
quality = existing.quality
|
||||
inputMode = existing.inputMode
|
||||
clipboardSync = existing.clipboardSyncEnabled
|
||||
viewOnly = existing.viewOnly
|
||||
notes = existing.notes
|
||||
} else if let prefill, displayName.isEmpty, host.isEmpty {
|
||||
displayName = prefill.displayName
|
||||
host = prefill.host
|
||||
port = String(prefill.port)
|
||||
}
|
||||
}
|
||||
|
||||
private func save() {
|
||||
let portInt = Int(port) ?? 5900
|
||||
let connection = SavedConnection(
|
||||
displayName: displayName,
|
||||
host: host,
|
||||
port: portInt,
|
||||
colorTag: colorTag,
|
||||
quality: quality,
|
||||
inputMode: inputMode,
|
||||
viewOnly: viewOnly,
|
||||
curtainMode: false,
|
||||
clipboardSyncEnabled: clipboardSync,
|
||||
notes: notes
|
||||
)
|
||||
context.insert(connection)
|
||||
if !password.isEmpty {
|
||||
try? KeychainService().storePassword(password, account: connection.keychainTag)
|
||||
if let existing = editing {
|
||||
existing.displayName = displayName
|
||||
existing.host = host
|
||||
existing.port = portInt
|
||||
existing.colorTag = colorTag
|
||||
existing.quality = quality
|
||||
existing.inputMode = inputMode
|
||||
existing.clipboardSyncEnabled = clipboardSync
|
||||
existing.viewOnly = viewOnly
|
||||
existing.notes = notes
|
||||
if !password.isEmpty {
|
||||
try? KeychainService().storePassword(password, account: existing.keychainTag)
|
||||
}
|
||||
} else {
|
||||
let connection = SavedConnection(
|
||||
displayName: displayName,
|
||||
host: host,
|
||||
port: portInt,
|
||||
colorTag: colorTag,
|
||||
quality: quality,
|
||||
inputMode: inputMode,
|
||||
viewOnly: viewOnly,
|
||||
curtainMode: false,
|
||||
clipboardSyncEnabled: clipboardSync,
|
||||
notes: notes
|
||||
)
|
||||
context.insert(connection)
|
||||
if !password.isEmpty {
|
||||
try? KeychainService().storePassword(password, account: connection.keychainTag)
|
||||
}
|
||||
}
|
||||
try? context.save()
|
||||
dismiss()
|
||||
|
||||
@@ -10,6 +10,7 @@ public struct ConnectionListView: View {
|
||||
@State private var showingAdd = false
|
||||
@State private var showingSettings = false
|
||||
@State private var addPrefill: AddConnectionPrefill?
|
||||
@State private var editingConnection: SavedConnection?
|
||||
@State private var path: [SessionRoute] = []
|
||||
@State private var resolvingHostID: String?
|
||||
|
||||
@@ -59,6 +60,9 @@ public struct ConnectionListView: View {
|
||||
.sheet(isPresented: $showingAdd) {
|
||||
AddConnectionView(prefill: addPrefill)
|
||||
}
|
||||
.sheet(item: $editingConnection) { connection in
|
||||
AddConnectionView(editing: connection)
|
||||
}
|
||||
.sheet(isPresented: $showingSettings) {
|
||||
SettingsView()
|
||||
}
|
||||
@@ -121,6 +125,11 @@ public struct ConnectionListView: View {
|
||||
ConnectionCard(connection: connection)
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
editingConnection = connection
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
#if os(iOS)
|
||||
Button {
|
||||
openWindow(value: connection.id)
|
||||
@@ -134,7 +143,15 @@ public struct ConnectionListView: View {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
.swipeActions {
|
||||
.swipeActions(edge: .leading) {
|
||||
Button {
|
||||
editingConnection = connection
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
.tint(.blue)
|
||||
}
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(role: .destructive) {
|
||||
delete(connection)
|
||||
} label: {
|
||||
|
||||
Reference in New Issue
Block a user