Parcourir la source

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 il y a 3 mois
Parent
commit
af60d123d5
1 fichiers modifiés avec 97 ajouts et 11 suppressions
  1. 97 11
      zoom_app/ViewController.swift

+ 97 - 11
zoom_app/ViewController.swift

@@ -430,6 +430,7 @@ class ViewController: NSViewController {
         let end: Date?
         let host: String
         let source: String
+        let webURL: URL?
     }
 
     @MainActor
@@ -480,7 +481,12 @@ class ViewController: NSViewController {
         emptyMeetingLabel?.isHidden = true
         meetingsStatusLabel?.stringValue = "Upcoming meetings"
         for meeting in ordered {
-            stack.addArrangedSubview(makeMeetingRowCard(meeting))
+            let card = makeMeetingRowCard(meeting)
+            stack.addArrangedSubview(card)
+            // Make each meeting card span the full list width (like Zoom).
+            if card.constraints.contains(where: { $0.firstAttribute == .width }) == false {
+                card.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
+            }
         }
     }
 
@@ -655,10 +661,12 @@ class ViewController: NSViewController {
 
     private func fetchZoomScheduledMeetings(accessToken: String) async throws -> [ScheduledMeeting] {
         struct ZoomMeeting: Decodable {
+            let id: Int?
             let topic: String?
             let start_time: String?
             let duration: Int?
             let host_id: String?
+            let join_url: String?
         }
 
         struct ZoomMeetingsPage: Decodable {
@@ -677,12 +685,22 @@ class ViewController: NSViewController {
                 let start = iso.date(from: startRaw) ?? fallbackISO.date(from: startRaw)
                 guard let start else { return nil }
                 let end = meeting.duration.map { start.addingTimeInterval(TimeInterval($0 * 60)) }
+                let webURL: URL? = {
+                    if let join = meeting.join_url, let url = URL(string: join), url.scheme != nil {
+                        return url
+                    }
+                    if let id = meeting.id {
+                        return URL(string: "https://zoom.us/j/\(id)")
+                    }
+                    return nil
+                }()
                 return ScheduledMeeting(
                     title: meeting.topic?.isEmpty == false ? meeting.topic! : "Zoom meeting",
                     start: start,
                     end: end,
                     host: meeting.host_id ?? "Zoom Host",
-                    source: "Zoom"
+                    source: "Zoom",
+                    webURL: webURL
                 )
             }
         }
@@ -1011,8 +1029,8 @@ class ViewController: NSViewController {
         
         let todaysDateFormatter = DateFormatter()
         todaysDateFormatter.dateFormat = "EEEE, MMM d"
-        let panelHeader = makeLabel(todaysDateFormatter.string(from: Date()), size: 21, color: primaryText, weight: .semibold, centered: false)
-        let meetingsStatus = makeLabel("Upcoming meetings", size: 12, color: secondaryText, weight: .medium, centered: false)
+        let panelHeader = makeLabel(todaysDateFormatter.string(from: Date()), size: 18, color: primaryText, weight: .semibold, centered: false)
+        let meetingsStatus = makeLabel("Upcoming meetings", size: 11, color: secondaryText, weight: .medium, centered: false)
         
         let meetingsDayNav = NSStackView()
         meetingsDayNav.orientation = .horizontal
@@ -1023,7 +1041,7 @@ class ViewController: NSViewController {
         let nextDayButton = makeNavGlyphButton(symbol: "chevron.right", action: #selector(meetingsNextDayTapped), dimension: 14, pointSize: 7, toolTip: "Next day")
         [todayButton, prevDayButton, nextDayButton].forEach { meetingsDayNav.addArrangedSubview($0) }
         
-        let noMeeting = makeLabel("No meetings scheduled for today.", size: 18, color: secondaryText, weight: .regular, centered: true)
+        let noMeeting = makeLabel("No meetings scheduled for today.", size: 15, color: secondaryText, weight: .regular, centered: true)
         let meetingsScrollView = NSScrollView()
         meetingsScrollView.drawsBackground = false
         meetingsScrollView.hasVerticalScroller = true
@@ -1033,7 +1051,7 @@ class ViewController: NSViewController {
         let meetingsDocument = NSView()
         let meetingsStack = NSStackView()
         meetingsStack.orientation = .vertical
-        meetingsStack.spacing = 10
+        meetingsStack.spacing = 14
         meetingsStack.alignment = .leading
         let openRecordings = NSButton(title: "Open recordings", target: nil, action: nil)
         openRecordings.isBordered = false
@@ -1572,7 +1590,7 @@ class ViewController: NSViewController {
     }
 
     private func makeMeetingRowCard(_ meeting: ScheduledMeeting) -> NSView {
-        let card = NSView()
+        let card = MeetingCardView(url: meeting.webURL)
         card.wantsLayer = true
         card.layer?.backgroundColor = meetingCardBackground.cgColor
         card.layer?.cornerRadius = 13
@@ -1589,10 +1607,10 @@ class ViewController: NSViewController {
         let endText = meeting.end.map { timeFormatter.string(from: $0) } ?? ""
         let range = endText.isEmpty ? startText : "\(startText) - \(endText)"
 
-        let title = makeLabel(meeting.title, size: 26, color: primaryText, weight: .regular, centered: false)
-        let detail = makeLabel("\(dateFormatter.string(from: meeting.start))\n\(range)", size: 14, color: secondaryText, weight: .regular, centered: false)
+        let title = makeLabel(meeting.title, size: 18, color: primaryText, weight: .semibold, centered: false)
+        let detail = makeLabel("\(dateFormatter.string(from: meeting.start))\n\(range)", size: 12, color: secondaryText, weight: .regular, centered: false)
         detail.maximumNumberOfLines = 2
-        let host = makeLabel("Host: \(meeting.host) • \(meeting.source)", size: 13, color: secondaryText, weight: .regular, centered: false)
+        let host = makeLabel("Host: \(meeting.host) • \(meeting.source)", size: 11, color: secondaryText, weight: .regular, centered: false)
 
         [title, detail, host].forEach {
             $0.translatesAutoresizingMaskIntoConstraints = false
@@ -1610,7 +1628,8 @@ class ViewController: NSViewController {
 
             host.topAnchor.constraint(equalTo: detail.bottomAnchor, constant: 7),
             host.leadingAnchor.constraint(equalTo: title.leadingAnchor),
-            host.trailingAnchor.constraint(equalTo: title.trailingAnchor)
+            host.trailingAnchor.constraint(equalTo: title.trailingAnchor),
+            host.bottomAnchor.constraint(lessThanOrEqualTo: card.bottomAnchor, constant: -12)
         ])
 
         return card
@@ -1681,6 +1700,73 @@ private final class SearchPillTextField: NSTextField {
     }
 }
 
+private final class MeetingCardView: NSView {
+    private let url: URL?
+    private var tracking: NSTrackingArea?
+    private var isHovering = false
+    private var normalBackgroundColor: CGColor?
+    private var normalBorderColor: CGColor?
+
+    init(url: URL?) {
+        self.url = url
+        super.init(frame: .zero)
+    }
+
+    required init?(coder: NSCoder) {
+        self.url = nil
+        super.init(coder: coder)
+    }
+
+    override func updateTrackingAreas() {
+        super.updateTrackingAreas()
+        if let tracking { removeTrackingArea(tracking) }
+        let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeInKeyWindow, .inVisibleRect]
+        let area = NSTrackingArea(rect: .zero, options: options, owner: self, userInfo: nil)
+        addTrackingArea(area)
+        tracking = area
+    }
+
+    override func resetCursorRects() {
+        super.resetCursorRects()
+        if url != nil {
+            addCursorRect(bounds, cursor: .pointingHand)
+        }
+    }
+
+    override func mouseEntered(with event: NSEvent) {
+        super.mouseEntered(with: event)
+        guard url != nil else { return }
+        isHovering = true
+        applyHoverState()
+    }
+
+    override func mouseExited(with event: NSEvent) {
+        super.mouseExited(with: event)
+        guard url != nil else { return }
+        isHovering = false
+        applyHoverState()
+    }
+
+    override func mouseUp(with event: NSEvent) {
+        super.mouseUp(with: event)
+        guard let url else { return }
+        NSWorkspace.shared.open(url)
+    }
+
+    private func applyHoverState() {
+        guard let layer else { return }
+        if normalBackgroundColor == nil { normalBackgroundColor = layer.backgroundColor }
+        if normalBorderColor == nil { normalBorderColor = layer.borderColor }
+        if isHovering {
+            layer.backgroundColor = NSColor.white.withAlphaComponent(0.075).cgColor
+            layer.borderColor = NSColor.white.withAlphaComponent(0.12).cgColor
+        } else {
+            layer.backgroundColor = normalBackgroundColor
+            layer.borderColor = normalBorderColor
+        }
+    }
+}
+
 struct GoogleOAuthTokens: Codable, Equatable {
     var accessToken: String
     var refreshToken: String?