Files
voice-cat/clients/apple/macOS/VoiceCatMac/Sheets/AddServerSheet.swift

165 lines
6.3 KiB
Swift
Raw Permalink Normal View History

import AppKit
final class AddServerSheet: NSViewController {
var onComplete: ((SavedServer?) -> Void)?
private var editing: SavedServer?
private let hostField = NSTextField()
private let portField: NSTextField = {
let f = NSTextField()
f.stringValue = "7878"
return f
}()
private let authPicker = NSPopUpButton()
private let nicknameField = NSTextField()
private let usernameField = NSTextField()
private let passwordField = NSSecureTextField()
private let savePwCheckbox = NSButton(checkboxWithTitle: "Save password in Keychain", target: nil, action: nil)
init(editing: SavedServer?) {
self.editing = editing
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError() }
override func loadView() {
view = NSView(frame: NSRect(x: 0, y: 0, width: 380, height: 260))
}
override func viewDidLoad() {
super.viewDidLoad()
buildUI()
if let s = editing {
hostField.stringValue = s.host
portField.stringValue = "\(s.port)"
authPicker.selectItem(withTitle: s.authMode == .guest ? "Guest" : "Account")
nicknameField.stringValue = s.nickname ?? ""
usernameField.stringValue = s.savedUsername ?? ""
updateAuthVisibility()
}
}
private func buildUI() {
let title = NSTextField(labelWithString: editing == nil ? "Add Server" : "Edit Server")
title.font = .boldSystemFont(ofSize: 14)
let hostLabel = NSTextField(labelWithString: "Host:")
let portLabel = NSTextField(labelWithString: "Port:")
let authLabel = NSTextField(labelWithString: "Auth:")
let nickLabel = NSTextField(labelWithString: "Nickname:")
let userLabel = NSTextField(labelWithString: "Username:")
let pwLabel = NSTextField(labelWithString: "Password:")
hostField.placeholderString = "hostname or IP"
hostField.setAccessibilityLabel("Server hostname or IP address")
portField.setAccessibilityLabel("Server port")
authPicker.addItem(withTitle: "Guest")
authPicker.addItem(withTitle: "Account")
authPicker.target = self; authPicker.action = #selector(authChanged)
authPicker.setAccessibilityLabel("Authentication mode")
nicknameField.placeholderString = "leave blank to use your system name"
nicknameField.setAccessibilityLabel("Guest nickname (display name)")
usernameField.placeholderString = "username"
usernameField.setAccessibilityLabel("Username")
passwordField.placeholderString = "leave blank to enter at connect"
passwordField.setAccessibilityLabel("Password (optional — enter at connect time if blank)")
savePwCheckbox.setAccessibilityLabel("Save password in system Keychain")
let cancelButton = NSButton(title: "Cancel", target: self, action: #selector(cancelClicked))
cancelButton.bezelStyle = .rounded
let saveButton = NSButton(title: "Save", target: self, action: #selector(saveClicked))
saveButton.bezelStyle = .rounded
saveButton.keyEquivalent = "\r"
saveButton.setAccessibilityLabel("Save server")
let grid = NSGridView(views: [
[hostLabel, hostField],
[portLabel, portField],
[authLabel, authPicker],
[nickLabel, nicknameField],
[userLabel, usernameField],
[pwLabel, passwordField],
[NSView(), savePwCheckbox],
])
grid.rowSpacing = 8
grid.columnSpacing = 8
grid.column(at: 0).xPlacement = .trailing
let buttonRow = NSStackView(views: [NSView(), cancelButton, saveButton])
buttonRow.orientation = .horizontal; buttonRow.spacing = 8
let stack = NSStackView(views: [title, grid, buttonRow])
stack.orientation = .vertical
stack.spacing = 16
stack.edgeInsets = NSEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: view.topAnchor),
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor),
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor),
stack.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
updateAuthVisibility()
}
@objc private func authChanged() { updateAuthVisibility() }
private func updateAuthVisibility() {
let isAccount = authPicker.titleOfSelectedItem == "Account"
nicknameField.isEnabled = !isAccount
usernameField.isEnabled = isAccount
passwordField.isEnabled = isAccount
savePwCheckbox.isEnabled = isAccount
}
@objc private func saveClicked() {
let host = hostField.stringValue.trimmingCharacters(in: .whitespaces)
guard !host.isEmpty else {
hostField.becomeFirstResponder(); return
}
let portStr = portField.stringValue.trimmingCharacters(in: .whitespaces)
guard let port = UInt16(portStr), port > 0 else {
portField.becomeFirstResponder(); return
}
let isGuest = authPicker.titleOfSelectedItem == "Guest"
var server = editing ?? SavedServer(host: host, port: port,
authMode: isGuest ? .guest : .password)
server.host = host; server.port = port
server.authMode = isGuest ? .guest : .password
server.savedUsername = isGuest ? nil : usernameField.stringValue.trimmingCharacters(in: .whitespaces)
let nick = nicknameField.stringValue.trimmingCharacters(in: .whitespaces)
server.nickname = nick.isEmpty ? nil : nick
if !isGuest && savePwCheckbox.state == .on {
let pw = passwordField.stringValue
if !pw.isEmpty {
let tag = server.keychainTag ?? "voicecat.server.\(server.id.uuidString)"
server.keychainTag = tag
ServerListStore.savePassword(pw, tag: tag)
}
} else if isGuest {
if let tag = server.keychainTag { ServerListStore.deletePassword(tag: tag) }
server.keychainTag = nil
}
dismiss(nil)
onComplete?(server)
}
@objc private func cancelClicked() {
dismiss(nil)
onComplete?(nil)
}
}