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:
Trey T
2026-04-16 20:21:14 -05:00
parent fcad267493
commit 8e01068ad3
2 changed files with 77 additions and 26 deletions

View File

@@ -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()

View File

@@ -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: {