Selaa lähdekoodia

Add day navigation to Upcoming meetings panel

Show meetings for the selected day (default today) with previous/next navigation and a Today reset control.

Made-with: Cursor
huzaifahayat12 3 kuukautta sitten
vanhempi
sitoutus
8e2f8d60f9
1 muutettua tiedostoa jossa 124 lisäystä ja 7 poistoa
  1. 124 7
      zoom_app/ViewController.swift

+ 124 - 7
zoom_app/ViewController.swift

@@ -45,13 +45,18 @@ class ViewController: NSViewController {
     private weak var zoomSocialButton: NSButton?
     private weak var timeLabel: NSTextField?
     private weak var dateLabel: NSTextField?
+    private weak var meetingsDayHeaderLabel: NSTextField?
     private weak var emptyMeetingLabel: NSTextField?
     private weak var meetingsListStack: NSStackView?
     private weak var meetingsStatusLabel: NSTextField?
     private weak var meetingsScrollView: NSScrollView?
+    private weak var meetingsPrevDayButton: NSButton?
+    private weak var meetingsNextDayButton: NSButton?
+    private weak var meetingsTodayButton: NSButton?
     private weak var homeSearchField: NSTextField?
     private weak var homeSearchPill: NSView?
     private var allScheduledMeetings: [ScheduledMeeting] = []
+    private var selectedMeetingsDayStart: Date = Calendar.current.startOfDay(for: Date())
     private var searchTextObserver: NSObjectProtocol?
     private var searchShortcutMonitor: Any?
     private var searchOutsideClickMonitor: Any?
@@ -146,6 +151,7 @@ class ViewController: NSViewController {
         homeSearchField = nil
         homeSearchPill = nil
         homeView?.removeFromSuperview()
+        selectedMeetingsDayStart = Calendar.current.startOfDay(for: Date())
         homeView = makeHomeView(profile: profile)
         if let homeView { attachToRoot(homeView) }
         installSearchShortcutMonitor()
@@ -441,7 +447,12 @@ class ViewController: NSViewController {
         }
 
         let query = (homeSearchField?.stringValue ?? "").trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
-        let source = allScheduledMeetings
+        let calendar = Calendar.current
+        let dayStart = selectedMeetingsDayStart
+        let dayEnd = calendar.date(byAdding: .day, value: 1, to: dayStart) ?? dayStart.addingTimeInterval(60 * 60 * 24)
+        let source = allScheduledMeetings.filter { meeting in
+            meeting.start >= dayStart && meeting.start < dayEnd
+        }
         let filtered: [ScheduledMeeting]
         if query.isEmpty {
             filtered = source
@@ -457,22 +468,75 @@ class ViewController: NSViewController {
         if ordered.isEmpty {
             emptyMeetingLabel?.isHidden = false
             if source.isEmpty {
-                meetingsStatusLabel?.stringValue = "No upcoming Zoom meetings found."
-                emptyMeetingLabel?.stringValue = "No meetings scheduled for today."
+                meetingsStatusLabel?.stringValue = "Upcoming meetings"
+                emptyMeetingLabel?.stringValue = "No meetings scheduled for \(meetingsDayDisplayName(for: dayStart))."
             } else {
-                meetingsStatusLabel?.stringValue = "Zoom meetings"
+                meetingsStatusLabel?.stringValue = "Upcoming meetings"
                 emptyMeetingLabel?.stringValue = "No meetings match your search."
             }
             return
         }
 
         emptyMeetingLabel?.isHidden = true
-        meetingsStatusLabel?.stringValue = "Zoom meetings"
+        meetingsStatusLabel?.stringValue = "Upcoming meetings"
         for meeting in ordered {
             stack.addArrangedSubview(makeMeetingRowCard(meeting))
         }
     }
 
+    @MainActor
+    private func setSelectedMeetingsDayStart(_ newDayStart: Date) {
+        selectedMeetingsDayStart = Calendar.current.startOfDay(for: newDayStart)
+        updateMeetingsDayUI()
+        applyFilteredMeetings()
+    }
+
+    @MainActor
+    private func updateMeetingsDayUI() {
+        let dayStart = selectedMeetingsDayStart
+        let formatter = DateFormatter()
+        formatter.dateFormat = "EEEE, MMM d"
+        meetingsDayHeaderLabel?.stringValue = formatter.string(from: dayStart)
+
+        let isToday = Calendar.current.isDate(dayStart, inSameDayAs: Date())
+        meetingsTodayButton?.isEnabled = isToday == false
+        meetingsTodayButton?.alphaValue = isToday ? 0.55 : 1.0
+    }
+
+    private func meetingsDayDisplayName(for dayStart: Date) -> String {
+        let calendar = Calendar.current
+        if calendar.isDate(dayStart, inSameDayAs: Date()) { return "today" }
+        if let tomorrow = calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: Date())),
+           calendar.isDate(dayStart, inSameDayAs: tomorrow) { return "tomorrow" }
+        if let yesterday = calendar.date(byAdding: .day, value: -1, to: calendar.startOfDay(for: Date())),
+           calendar.isDate(dayStart, inSameDayAs: yesterday) { return "yesterday" }
+        let formatter = DateFormatter()
+        formatter.dateFormat = "EEEE, MMM d"
+        return formatter.string(from: dayStart)
+    }
+
+    @objc private func meetingsPrevDayTapped() {
+        let calendar = Calendar.current
+        let prev = calendar.date(byAdding: .day, value: -1, to: selectedMeetingsDayStart) ?? selectedMeetingsDayStart.addingTimeInterval(-60 * 60 * 24)
+        Task { @MainActor in
+            self.setSelectedMeetingsDayStart(prev)
+        }
+    }
+
+    @objc private func meetingsNextDayTapped() {
+        let calendar = Calendar.current
+        let next = calendar.date(byAdding: .day, value: 1, to: selectedMeetingsDayStart) ?? selectedMeetingsDayStart.addingTimeInterval(60 * 60 * 24)
+        Task { @MainActor in
+            self.setSelectedMeetingsDayStart(next)
+        }
+    }
+
+    @objc private func meetingsTodayTapped() {
+        Task { @MainActor in
+            self.setSelectedMeetingsDayStart(Date())
+        }
+    }
+
     private func loadScheduledMeetings() async {
         if isLoadingMeetings { return }
         isLoadingMeetings = true
@@ -949,6 +1013,16 @@ class ViewController: NSViewController {
         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 meetingsDayNav = NSStackView()
+        meetingsDayNav.orientation = .horizontal
+        meetingsDayNav.spacing = 4
+        meetingsDayNav.alignment = .centerY
+        let prevDayButton = makeNavGlyphButton(symbol: "chevron.left", action: #selector(meetingsPrevDayTapped), dimension: 14, pointSize: 7, toolTip: "Previous day")
+        let todayButton = makeMeetingsDayChipButton(title: "Today", symbol: "calendar", action: #selector(meetingsTodayTapped))
+        let nextDayButton = makeNavGlyphButton(symbol: "chevron.right", action: #selector(meetingsNextDayTapped), dimension: 14, pointSize: 7, toolTip: "Next day")
+        [prevDayButton, todayButton, nextDayButton].forEach { meetingsDayNav.addArrangedSubview($0) }
+        
         let noMeeting = makeLabel("No meetings scheduled for today.", size: 18, color: secondaryText, weight: .regular, centered: true)
         let meetingsScrollView = NSScrollView()
         meetingsScrollView.drawsBackground = false
@@ -989,7 +1063,7 @@ class ViewController: NSViewController {
         [searchIcon, searchField, searchHintLabel].forEach {
             searchPill.addSubview($0)
         }
-        [welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
+        [welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, meetingsDayNav, noMeeting, meetingsScrollView, openRecordings].forEach {
             $0.translatesAutoresizingMaskIntoConstraints = false
             contentColumn.addSubview($0)
         }
@@ -1071,7 +1145,9 @@ class ViewController: NSViewController {
             panelHeader.topAnchor.constraint(equalTo: panel.topAnchor, constant: 20),
             panelHeader.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 16),
             meetingsStatus.centerYAnchor.constraint(equalTo: panelHeader.centerYAnchor),
-            meetingsStatus.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -16),
+            meetingsDayNav.centerYAnchor.constraint(equalTo: panelHeader.centerYAnchor),
+            meetingsDayNav.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -16),
+            meetingsStatus.trailingAnchor.constraint(equalTo: meetingsDayNav.leadingAnchor, constant: -10),
             noMeeting.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
             noMeeting.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
             noMeeting.centerYAnchor.constraint(equalTo: panel.centerYAnchor),
@@ -1095,11 +1171,17 @@ class ViewController: NSViewController {
 
         timeLabel = timeTitle
         dateLabel = dateTitle
+        meetingsDayHeaderLabel = panelHeader
         meetingsListStack = meetingsStack
         meetingsStatusLabel = meetingsStatus
         emptyMeetingLabel = noMeeting
+        meetingsPrevDayButton = prevDayButton
+        meetingsTodayButton = todayButton
+        meetingsNextDayButton = nextDayButton
         observeMeetingsScrollEdges(in: meetingsScrollView)
         updateClock()
+        updateMeetingsDayUI()
+        applyFilteredMeetings()
 
         homeSearchField = searchField
         homeSearchPill = searchPill
@@ -1414,6 +1496,41 @@ class ViewController: NSViewController {
         button.heightAnchor.constraint(equalToConstant: dimension).isActive = true
         return button
     }
+    
+    private func makeMeetingsDayChipButton(title: String, symbol: String? = nil, action: Selector?) -> NSButton {
+        let button = NSButton(title: title, target: action == nil ? nil : self, action: action)
+        button.isBordered = false
+        button.bezelStyle = .shadowlessSquare
+        button.focusRingType = .none
+        button.wantsLayer = true
+        button.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.06).cgColor
+        button.layer?.cornerRadius = 10
+        button.layer?.borderWidth = 1
+        button.layer?.borderColor = NSColor.white.withAlphaComponent(0.08).cgColor
+        let tint = NSColor(calibratedWhite: 0.9, alpha: 1)
+        let font = NSFont.systemFont(ofSize: 11, weight: .semibold)
+        button.contentTintColor = tint
+        button.font = font
+        button.attributedTitle = NSAttributedString(string: title, attributes: [
+            .foregroundColor: tint,
+            .font: font
+        ])
+        if let symbol {
+            let symbolConfig = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
+            if let base = NSImage(systemSymbolName: symbol, accessibilityDescription: title),
+               let image = base.withSymbolConfiguration(symbolConfig) {
+                image.isTemplate = true
+                button.image = image
+                button.imagePosition = .imageLeading
+            }
+            button.imageHugsTitle = true
+            button.imageScaling = .scaleNone
+        }
+        button.translatesAutoresizingMaskIntoConstraints = false
+        button.heightAnchor.constraint(equalToConstant: 24).isActive = true
+        button.widthAnchor.constraint(greaterThanOrEqualToConstant: 92).isActive = true
+        return button
+    }
 
     private func makeActionTile(title: String, symbol: String, color: NSColor, action: Selector? = nil) -> NSView {
         let root = NSView()