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

138 lines
5.0 KiB
Swift
Raw Normal View History

import AppKit
import VoiceCatCore
// UserPickerSheet — a modal sheet for picking one user from the list of all connected server
// users. Used by the "Messages → New Private Message…" menu item so the user can start a PM
// with anyone on the server, not just the current channel. Mirrors the Windows client's
// `UserPickerDialog` (clients/windows/VoiceCat.App/Forms/UserPickerDialog.cs), adapted to the
// Mac sheet pattern used by the rest of the macOS client (InputSheet, MoveUserSheet, etc.).
final class UserPickerSheet: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
/// Called with the selected user's ID, or `nil` if the user cancelled.
var onComplete: ((UInt32?) -> Void)?
private let users: [User]
private let tableView = NSTableView()
private var okButton: NSButton?
private var selectedRow: Int = -1
init(users: [User]) {
self.users = users
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError() }
override func loadView() {
view = NSView(frame: NSRect(x: 0, y: 0, width: 300, height: 320))
}
override func viewDidLoad() {
super.viewDidLoad()
buildUI()
}
// MARK: - UI
private func buildUI() {
let titleLabel = NSTextField(labelWithString: "Select a user:")
titleLabel.font = .boldSystemFont(ofSize: 13)
titleLabel.setAccessibilityLabel("Select a user")
let col = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("user"))
tableView.addTableColumn(col)
tableView.headerView = nil
tableView.dataSource = self
tableView.delegate = self
tableView.doubleAction = #selector(okClicked)
tableView.target = self
tableView.setAccessibilityLabel("User list")
let scroll = NSScrollView()
scroll.documentView = tableView
scroll.hasVerticalScroller = true
scroll.borderType = .bezelBorder
scroll.translatesAutoresizingMaskIntoConstraints = false
let cancelButton = NSButton(title: "Cancel", target: self, action: #selector(cancelClicked))
cancelButton.bezelStyle = .rounded
let okButton = NSButton(title: "OK", target: self, action: #selector(okClicked))
okButton.bezelStyle = .rounded
okButton.keyEquivalent = "\r"
okButton.isEnabled = false
self.okButton = okButton
let buttonRow = NSStackView(views: [NSView(), cancelButton, okButton])
buttonRow.orientation = .horizontal
buttonRow.spacing = 8
let stack = NSStackView(views: [titleLabel, scroll, buttonRow])
stack.orientation = .vertical
stack.spacing = 8
stack.edgeInsets = NSEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
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),
])
}
override func viewDidAppear() {
super.viewDidAppear()
if users.count == 1 {
tableView.selectRowIndexes(IndexSet(integer: 0), byExtendingSelection: false)
}
}
// MARK: - Actions
@objc private func okClicked() {
guard selectedRow >= 0, selectedRow < users.count else { return }
let userId = users[selectedRow].id
dismiss(nil)
onComplete?(userId)
}
@objc private func cancelClicked() {
dismiss(nil)
onComplete?(nil)
}
// MARK: - NSTableViewDataSource / Delegate
func numberOfRows(in tableView: NSTableView) -> Int { users.count }
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let id = NSUserInterfaceItemIdentifier("userCell")
let cell = tableView.makeView(withIdentifier: id, owner: nil) as? NSTableCellView
?? makeCellView(identifier: id)
cell.textField?.stringValue = users[row].nickname
return cell
}
func tableViewSelectionDidChange(_ notification: Notification) {
selectedRow = tableView.selectedRow
okButton?.isEnabled = selectedRow >= 0
}
private func makeCellView(identifier: NSUserInterfaceItemIdentifier) -> NSTableCellView {
let cell = NSTableCellView()
cell.identifier = identifier
let tf = NSTextField(labelWithString: "")
tf.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(tf)
cell.textField = tf
NSLayoutConstraint.activate([
tf.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 4),
tf.trailingAnchor.constraint(equalTo: cell.trailingAnchor, constant: -4),
tf.centerYAnchor.constraint(equalTo: cell.centerYAnchor),
])
return cell
}
}