ソースを参照

Improve Upcoming meetings cards

Make meeting cards full-width with more spacing, reduce panel/card text sizes, and add hover + click-to-open meeting in browser.

Made-with: Cursor
huzaifahayat12 5 日 前
コミット
af60d123d5
共有1 個のファイルを変更した97 個の追加11 個の削除を含む
  1. 97 11
      zoom_app/ViewController.swift

+ 97 - 11
zoom_app/ViewController.swift

@@ -430,6 +430,7 @@ class ViewController: NSViewController {
430 430
         let end: Date?
431 431
         let host: String
432 432
         let source: String
433
+        let webURL: URL?
433 434
     }
434 435
 
435 436
     @MainActor
@@ -480,7 +481,12 @@ class ViewController: NSViewController {
480 481
         emptyMeetingLabel?.isHidden = true
481 482
         meetingsStatusLabel?.stringValue = "Upcoming meetings"
482 483
         for meeting in ordered {
483
-            stack.addArrangedSubview(makeMeetingRowCard(meeting))
484
+            let card = makeMeetingRowCard(meeting)
485
+            stack.addArrangedSubview(card)
486
+            // Make each meeting card span the full list width (like Zoom).
487
+            if card.constraints.contains(where: { $0.firstAttribute == .width }) == false {
488
+                card.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
489
+            }
484 490
         }
485 491
     }
486 492
 
@@ -655,10 +661,12 @@ class ViewController: NSViewController {
655 661
 
656 662
     private func fetchZoomScheduledMeetings(accessToken: String) async throws -> [ScheduledMeeting] {
657 663
         struct ZoomMeeting: Decodable {
664
+            let id: Int?
658 665
             let topic: String?
659 666
             let start_time: String?
660 667
             let duration: Int?
661 668
             let host_id: String?
669
+            let join_url: String?
662 670
         }
663 671
 
664 672
         struct ZoomMeetingsPage: Decodable {
@@ -677,12 +685,22 @@ class ViewController: NSViewController {
677 685
                 let start = iso.date(from: startRaw) ?? fallbackISO.date(from: startRaw)
678 686
                 guard let start else { return nil }
679 687
                 let end = meeting.duration.map { start.addingTimeInterval(TimeInterval($0 * 60)) }
688
+                let webURL: URL? = {
689
+                    if let join = meeting.join_url, let url = URL(string: join), url.scheme != nil {
690
+                        return url
691
+                    }
692
+                    if let id = meeting.id {
693
+                        return URL(string: "https://zoom.us/j/\(id)")
694
+                    }
695
+                    return nil
696
+                }()
680 697
                 return ScheduledMeeting(
681 698
                     title: meeting.topic?.isEmpty == false ? meeting.topic! : "Zoom meeting",
682 699
                     start: start,
683 700
                     end: end,
684 701
                     host: meeting.host_id ?? "Zoom Host",
685
-                    source: "Zoom"
702
+                    source: "Zoom",
703
+                    webURL: webURL
686 704
                 )
687 705
             }
688 706
         }
@@ -1011,8 +1029,8 @@ class ViewController: NSViewController {
1011 1029
         
1012 1030
         let todaysDateFormatter = DateFormatter()
1013 1031
         todaysDateFormatter.dateFormat = "EEEE, MMM d"
1014
-        let panelHeader = makeLabel(todaysDateFormatter.string(from: Date()), size: 21, color: primaryText, weight: .semibold, centered: false)
1015
-        let meetingsStatus = makeLabel("Upcoming meetings", size: 12, color: secondaryText, weight: .medium, centered: false)
1032
+        let panelHeader = makeLabel(todaysDateFormatter.string(from: Date()), size: 18, color: primaryText, weight: .semibold, centered: false)
1033
+        let meetingsStatus = makeLabel("Upcoming meetings", size: 11, color: secondaryText, weight: .medium, centered: false)
1016 1034
         
1017 1035
         let meetingsDayNav = NSStackView()
1018 1036
         meetingsDayNav.orientation = .horizontal
@@ -1023,7 +1041,7 @@ class ViewController: NSViewController {
1023 1041
         let nextDayButton = makeNavGlyphButton(symbol: "chevron.right", action: #selector(meetingsNextDayTapped), dimension: 14, pointSize: 7, toolTip: "Next day")
1024 1042
         [todayButton, prevDayButton, nextDayButton].forEach { meetingsDayNav.addArrangedSubview($0) }
1025 1043
         
1026
-        let noMeeting = makeLabel("No meetings scheduled for today.", size: 18, color: secondaryText, weight: .regular, centered: true)
1044
+        let noMeeting = makeLabel("No meetings scheduled for today.", size: 15, color: secondaryText, weight: .regular, centered: true)
1027 1045
         let meetingsScrollView = NSScrollView()
1028 1046
         meetingsScrollView.drawsBackground = false
1029 1047
         meetingsScrollView.hasVerticalScroller = true
@@ -1033,7 +1051,7 @@ class ViewController: NSViewController {
1033 1051
         let meetingsDocument = NSView()
1034 1052
         let meetingsStack = NSStackView()
1035 1053
         meetingsStack.orientation = .vertical
1036
-        meetingsStack.spacing = 10
1054
+        meetingsStack.spacing = 14
1037 1055
         meetingsStack.alignment = .leading
1038 1056
         let openRecordings = NSButton(title: "Open recordings", target: nil, action: nil)
1039 1057
         openRecordings.isBordered = false
@@ -1572,7 +1590,7 @@ class ViewController: NSViewController {
1572 1590
     }
1573 1591
 
1574 1592
     private func makeMeetingRowCard(_ meeting: ScheduledMeeting) -> NSView {
1575
-        let card = NSView()
1593
+        let card = MeetingCardView(url: meeting.webURL)
1576 1594
         card.wantsLayer = true
1577 1595
         card.layer?.backgroundColor = meetingCardBackground.cgColor
1578 1596
         card.layer?.cornerRadius = 13
@@ -1589,10 +1607,10 @@ class ViewController: NSViewController {
1589 1607
         let endText = meeting.end.map { timeFormatter.string(from: $0) } ?? ""
1590 1608
         let range = endText.isEmpty ? startText : "\(startText) - \(endText)"
1591 1609
 
1592
-        let title = makeLabel(meeting.title, size: 26, color: primaryText, weight: .regular, centered: false)
1593
-        let detail = makeLabel("\(dateFormatter.string(from: meeting.start))\n\(range)", size: 14, color: secondaryText, weight: .regular, centered: false)
1610
+        let title = makeLabel(meeting.title, size: 18, color: primaryText, weight: .semibold, centered: false)
1611
+        let detail = makeLabel("\(dateFormatter.string(from: meeting.start))\n\(range)", size: 12, color: secondaryText, weight: .regular, centered: false)
1594 1612
         detail.maximumNumberOfLines = 2
1595
-        let host = makeLabel("Host: \(meeting.host) • \(meeting.source)", size: 13, color: secondaryText, weight: .regular, centered: false)
1613
+        let host = makeLabel("Host: \(meeting.host) • \(meeting.source)", size: 11, color: secondaryText, weight: .regular, centered: false)
1596 1614
 
1597 1615
         [title, detail, host].forEach {
1598 1616
             $0.translatesAutoresizingMaskIntoConstraints = false
@@ -1610,7 +1628,8 @@ class ViewController: NSViewController {
1610 1628
 
1611 1629
             host.topAnchor.constraint(equalTo: detail.bottomAnchor, constant: 7),
1612 1630
             host.leadingAnchor.constraint(equalTo: title.leadingAnchor),
1613
-            host.trailingAnchor.constraint(equalTo: title.trailingAnchor)
1631
+            host.trailingAnchor.constraint(equalTo: title.trailingAnchor),
1632
+            host.bottomAnchor.constraint(lessThanOrEqualTo: card.bottomAnchor, constant: -12)
1614 1633
         ])
1615 1634
 
1616 1635
         return card
@@ -1681,6 +1700,73 @@ private final class SearchPillTextField: NSTextField {
1681 1700
     }
1682 1701
 }
1683 1702
 
1703
+private final class MeetingCardView: NSView {
1704
+    private let url: URL?
1705
+    private var tracking: NSTrackingArea?
1706
+    private var isHovering = false
1707
+    private var normalBackgroundColor: CGColor?
1708
+    private var normalBorderColor: CGColor?
1709
+
1710
+    init(url: URL?) {
1711
+        self.url = url
1712
+        super.init(frame: .zero)
1713
+    }
1714
+
1715
+    required init?(coder: NSCoder) {
1716
+        self.url = nil
1717
+        super.init(coder: coder)
1718
+    }
1719
+
1720
+    override func updateTrackingAreas() {
1721
+        super.updateTrackingAreas()
1722
+        if let tracking { removeTrackingArea(tracking) }
1723
+        let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect]
1724
+        let area = NSTrackingArea(rect: .zero, options: options, owner: self, userInfo: nil)
1725
+        addTrackingArea(area)
1726
+        tracking = area
1727
+    }
1728
+
1729
+    override func resetCursorRects() {
1730
+        super.resetCursorRects()
1731
+        if url != nil {
1732
+            addCursorRect(bounds, cursor: .pointingHand)
1733
+        }
1734
+    }
1735
+
1736
+    override func mouseEntered(with event: NSEvent) {
1737
+        super.mouseEntered(with: event)
1738
+        guard url != nil else { return }
1739
+        isHovering = true
1740
+        applyHoverState()
1741
+    }
1742
+
1743
+    override func mouseExited(with event: NSEvent) {
1744
+        super.mouseExited(with: event)
1745
+        guard url != nil else { return }
1746
+        isHovering = false
1747
+        applyHoverState()
1748
+    }
1749
+
1750
+    override func mouseUp(with event: NSEvent) {
1751
+        super.mouseUp(with: event)
1752
+        guard let url else { return }
1753
+        NSWorkspace.shared.open(url)
1754
+    }
1755
+
1756
+    private func applyHoverState() {
1757
+        guard let layer else { return }
1758
+        if normalBackgroundColor == nil { normalBackgroundColor = layer.backgroundColor }
1759
+        if normalBorderColor == nil { normalBorderColor = layer.borderColor }
1760
+        if isHovering {
1761
+            layer.backgroundColor = NSColor.white.withAlphaComponent(0.075).cgColor
1762
+            layer.borderColor = NSColor.white.withAlphaComponent(0.12).cgColor
1763
+        } else {
1764
+            layer.backgroundColor = normalBackgroundColor
1765
+            layer.borderColor = normalBorderColor
1766
+        }
1767
+    }
1768
+}
1769
+
1684 1770
 struct GoogleOAuthTokens: Codable, Equatable {
1685 1771
     var accessToken: String
1686 1772
     var refreshToken: String?