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
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
let prefill: AddConnectionPrefill?
|
let prefill: AddConnectionPrefill?
|
||||||
|
let editing: SavedConnection?
|
||||||
|
|
||||||
@State private var displayName = ""
|
@State private var displayName = ""
|
||||||
@State private var host = ""
|
@State private var host = ""
|
||||||
@@ -30,11 +31,15 @@ public struct AddConnectionView: View {
|
|||||||
@State private var clipboardSync = true
|
@State private var clipboardSync = true
|
||||||
@State private var viewOnly = false
|
@State private var viewOnly = false
|
||||||
@State private var notes = ""
|
@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.prefill = prefill
|
||||||
|
self.editing = editing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isEditing: Bool { editing != nil }
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
@@ -51,7 +56,8 @@ public struct AddConnectionView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
Section("Authentication") {
|
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).")
|
Text("Stored in iOS Keychain (this device only).")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -86,7 +92,7 @@ public struct AddConnectionView: View {
|
|||||||
.lineLimit(2...6)
|
.lineLimit(2...6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("New Connection")
|
.navigationTitle(isEditing ? "Edit Connection" : "New Connection")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
#endif
|
#endif
|
||||||
@@ -95,38 +101,66 @@ public struct AddConnectionView: View {
|
|||||||
Button("Cancel") { dismiss() }
|
Button("Cancel") { dismiss() }
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .confirmationAction) {
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
Button("Save") { save() }
|
Button(isEditing ? "Save" : "Add") { save() }
|
||||||
.disabled(displayName.isEmpty || host.isEmpty)
|
.disabled(displayName.isEmpty || host.isEmpty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear { applyPrefillIfNeeded() }
|
.onAppear { loadExistingIfNeeded() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func applyPrefillIfNeeded() {
|
private func loadExistingIfNeeded() {
|
||||||
guard let prefill, displayName.isEmpty, host.isEmpty else { return }
|
guard !hasLoadedExisting else { return }
|
||||||
displayName = prefill.displayName
|
hasLoadedExisting = true
|
||||||
host = prefill.host
|
if let existing = editing {
|
||||||
port = String(prefill.port)
|
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() {
|
private func save() {
|
||||||
let portInt = Int(port) ?? 5900
|
let portInt = Int(port) ?? 5900
|
||||||
let connection = SavedConnection(
|
if let existing = editing {
|
||||||
displayName: displayName,
|
existing.displayName = displayName
|
||||||
host: host,
|
existing.host = host
|
||||||
port: portInt,
|
existing.port = portInt
|
||||||
colorTag: colorTag,
|
existing.colorTag = colorTag
|
||||||
quality: quality,
|
existing.quality = quality
|
||||||
inputMode: inputMode,
|
existing.inputMode = inputMode
|
||||||
viewOnly: viewOnly,
|
existing.clipboardSyncEnabled = clipboardSync
|
||||||
curtainMode: false,
|
existing.viewOnly = viewOnly
|
||||||
clipboardSyncEnabled: clipboardSync,
|
existing.notes = notes
|
||||||
notes: notes
|
if !password.isEmpty {
|
||||||
)
|
try? KeychainService().storePassword(password, account: existing.keychainTag)
|
||||||
context.insert(connection)
|
}
|
||||||
if !password.isEmpty {
|
} else {
|
||||||
try? KeychainService().storePassword(password, account: connection.keychainTag)
|
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()
|
try? context.save()
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public struct ConnectionListView: View {
|
|||||||
@State private var showingAdd = false
|
@State private var showingAdd = false
|
||||||
@State private var showingSettings = false
|
@State private var showingSettings = false
|
||||||
@State private var addPrefill: AddConnectionPrefill?
|
@State private var addPrefill: AddConnectionPrefill?
|
||||||
|
@State private var editingConnection: SavedConnection?
|
||||||
@State private var path: [SessionRoute] = []
|
@State private var path: [SessionRoute] = []
|
||||||
@State private var resolvingHostID: String?
|
@State private var resolvingHostID: String?
|
||||||
|
|
||||||
@@ -59,6 +60,9 @@ public struct ConnectionListView: View {
|
|||||||
.sheet(isPresented: $showingAdd) {
|
.sheet(isPresented: $showingAdd) {
|
||||||
AddConnectionView(prefill: addPrefill)
|
AddConnectionView(prefill: addPrefill)
|
||||||
}
|
}
|
||||||
|
.sheet(item: $editingConnection) { connection in
|
||||||
|
AddConnectionView(editing: connection)
|
||||||
|
}
|
||||||
.sheet(isPresented: $showingSettings) {
|
.sheet(isPresented: $showingSettings) {
|
||||||
SettingsView()
|
SettingsView()
|
||||||
}
|
}
|
||||||
@@ -121,6 +125,11 @@ public struct ConnectionListView: View {
|
|||||||
ConnectionCard(connection: connection)
|
ConnectionCard(connection: connection)
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
|
Button {
|
||||||
|
editingConnection = connection
|
||||||
|
} label: {
|
||||||
|
Label("Edit", systemImage: "pencil")
|
||||||
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
Button {
|
Button {
|
||||||
openWindow(value: connection.id)
|
openWindow(value: connection.id)
|
||||||
@@ -134,7 +143,15 @@ public struct ConnectionListView: View {
|
|||||||
Label("Delete", systemImage: "trash")
|
Label("Delete", systemImage: "trash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.swipeActions {
|
.swipeActions(edge: .leading) {
|
||||||
|
Button {
|
||||||
|
editingConnection = connection
|
||||||
|
} label: {
|
||||||
|
Label("Edit", systemImage: "pencil")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
}
|
||||||
|
.swipeActions(edge: .trailing) {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
delete(connection)
|
delete(connection)
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
Reference in New Issue
Block a user