ソースを参照

Add name sorting to the print contacts list.

Let users sort contacts A–Z or Z–A from a button beside Select all below the search field.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 ヶ月 前
コミット
6ac9f8d50a
1 ファイル変更121 行追加3 行削除
  1. 121 3
      smart_printer/PrintContactsView.swift

+ 121 - 3
smart_printer/PrintContactsView.swift

@@ -19,6 +19,31 @@ struct PrintableContact: Equatable {
     }
 }
 
+private enum ContactSortOrder: String, CaseIterable {
+    case nameAscending
+    case nameDescending
+
+    var title: String {
+        switch self {
+        case .nameAscending: "Names A to Z"
+        case .nameDescending: "Names Z to A"
+        }
+    }
+
+    func sort(_ contacts: [PrintableContact]) -> [PrintableContact] {
+        switch self {
+        case .nameAscending:
+            contacts.sorted {
+                $0.displayName.localizedCaseInsensitiveCompare($1.displayName) == .orderedAscending
+            }
+        case .nameDescending:
+            contacts.sorted {
+                $0.displayName.localizedCaseInsensitiveCompare($1.displayName) == .orderedDescending
+            }
+        }
+    }
+}
+
 // MARK: - Contacts Service
 
 enum ContactsError: Error {
@@ -141,6 +166,7 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
     var onDismiss: (() -> Void)?
 
     private let backButton = PrintContactsToolbarButton(symbolName: "chevron.left", accessibilityLabel: "Back")
+    private let sortButton = PrintContactsSortButton()
     private let searchContainer = NSView()
     private let searchIconView = NSImageView()
     private let searchField = NSTextField()
@@ -158,6 +184,7 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
     private let contactRowHeight: CGFloat = 52
 
     private var allContacts: [PrintableContact] = []
+    private var sortOrder: ContactSortOrder = .nameAscending
     private var selectedIDs = Set<String>()
     private var rowViews: [ContactRowView] = []
 
@@ -195,6 +222,7 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
         searchIconView.contentTintColor = AppTheme.textSecondary
         emptyLabel.textColor = AppTheme.textSecondary
         backButton.refreshAppearance()
+        sortButton.refreshAppearance()
         selectAllRow.refreshAppearance()
         printButton.refreshAppearance()
         rowViews.forEach { $0.refreshAppearance() }
@@ -284,14 +312,21 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
         scrollView.documentView = listDocumentView
 
         backButton.translatesAutoresizingMaskIntoConstraints = false
+        sortButton.translatesAutoresizingMaskIntoConstraints = false
         printButton.translatesAutoresizingMaskIntoConstraints = false
 
         backButton.onClick = { [weak self] in self?.dismiss() }
+        sortButton.sortOrder = sortOrder
+        sortButton.onSortChange = { [weak self] order in
+            self?.sortOrder = order
+            self?.reloadList()
+        }
         selectAllRow.applyLayout(contentInset: rowContentInset, checkboxNameSpacing: checkboxNameSpacing)
         selectAllRow.onClick = { [weak self] in self?.toggleSelectAll() }
         printButton.onClick = { [weak self] in self?.printContacts() }
 
         addSubview(backButton)
+        addSubview(sortButton)
         addSubview(searchContainer)
         searchContainer.addSubview(searchIconView)
         searchContainer.addSubview(searchField)
@@ -321,10 +356,15 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
             searchField.centerYAnchor.constraint(equalTo: searchContainer.centerYAnchor),
 
             selectAllRow.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
-            selectAllRow.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
             selectAllRow.topAnchor.constraint(equalTo: searchContainer.bottomAnchor, constant: 12),
+            selectAllRow.trailingAnchor.constraint(lessThanOrEqualTo: sortButton.leadingAnchor, constant: -8),
             selectAllRow.heightAnchor.constraint(equalToConstant: 36),
 
+            sortButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
+            sortButton.centerYAnchor.constraint(equalTo: selectAllRow.centerYAnchor),
+            sortButton.widthAnchor.constraint(equalToConstant: 40),
+            sortButton.heightAnchor.constraint(equalToConstant: 40),
+
             scrollView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
             scrollView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
             scrollView.topAnchor.constraint(equalTo: selectAllRow.bottomAnchor, constant: 8),
@@ -365,8 +405,13 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
 
     private func filteredContacts() -> [PrintableContact] {
         let query = searchField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
-        guard !query.isEmpty else { return allContacts }
-        return allContacts.filter { $0.searchableText.contains(query) }
+        let contacts: [PrintableContact]
+        if query.isEmpty {
+            contacts = allContacts
+        } else {
+            contacts = allContacts.filter { $0.searchableText.contains(query) }
+        }
+        return sortOrder.sort(contacts)
     }
 
     private func reloadList() {
@@ -686,6 +731,79 @@ private final class PrintContactsSelectAllRow: NSControl, AppearanceRefreshable
     }
 }
 
+// MARK: - Sort Button
+
+private final class PrintContactsSortButton: NSControl, AppearanceRefreshable {
+    var sortOrder: ContactSortOrder = .nameAscending {
+        didSet { rebuildMenu() }
+    }
+    var onSortChange: ((ContactSortOrder) -> Void)?
+
+    private let iconView = NSImageView()
+    private let sortMenu = NSMenu()
+
+    init() {
+        super.init(frame: .zero)
+        toolTip = "Sort"
+
+        wantsLayer = true
+        layer?.cornerRadius = 12
+
+        if let image = NSImage(systemSymbolName: "arrow.up.arrow.down", accessibilityDescription: "Sort") {
+            let config = NSImage.SymbolConfiguration(pointSize: 15, weight: .semibold)
+            iconView.image = image.withSymbolConfiguration(config)
+        }
+        iconView.translatesAutoresizingMaskIntoConstraints = false
+
+        addSubview(iconView)
+        NSLayoutConstraint.activate([
+            iconView.centerXAnchor.constraint(equalTo: centerXAnchor),
+            iconView.centerYAnchor.constraint(equalTo: centerYAnchor),
+        ])
+        rebuildMenu()
+        refreshAppearance()
+    }
+
+    @available(*, unavailable)
+    required init?(coder: NSCoder) { nil }
+
+    func refreshAppearance() {
+        layer?.backgroundColor = AppTheme.elevatedBackground.cgColor
+        layer?.borderColor = AppTheme.paywallBorder.cgColor
+        layer?.borderWidth = 1.5
+        iconView.contentTintColor = AppTheme.textPrimary
+    }
+
+    private func rebuildMenu() {
+        sortMenu.removeAllItems()
+        for order in ContactSortOrder.allCases {
+            let item = NSMenuItem(title: order.title, action: #selector(menuItemSelected(_:)), keyEquivalent: "")
+            item.target = self
+            item.representedObject = order.rawValue
+            item.state = order == sortOrder ? .on : .off
+            sortMenu.addItem(item)
+        }
+    }
+
+    @objc private func menuItemSelected(_ sender: NSMenuItem) {
+        guard let raw = sender.representedObject as? String,
+              let order = ContactSortOrder(rawValue: raw) else { return }
+        sortOrder = order
+        rebuildMenu()
+        onSortChange?(order)
+    }
+
+    override func mouseDown(with event: NSEvent) {
+        guard bounds.contains(convert(event.locationInWindow, from: nil)) else { return }
+        rebuildMenu()
+        sortMenu.popUp(positioning: nil, at: NSPoint(x: 0, y: bounds.height + 4), in: self)
+    }
+
+    override func resetCursorRects() {
+        addCursorRect(bounds, cursor: .pointingHand)
+    }
+}
+
 // MARK: - Toolbar Button
 
 private final class PrintContactsToolbarButton: NSControl, AppearanceRefreshable {