Przeglądaj źródła

Restrict Meetings filters to upcoming windows and enable auto-load on scroll.

This makes each preset (Today/Week/Month/Custom) return only upcoming meetings in its selected range and replaces manual load-more with infinite scrolling at the list end.

Made-with: Cursor
huzaifahayat12 3 miesięcy temu
rodzic
commit
dfcabe4c98
1 zmienionych plików z 99 dodań i 20 usunięć
  1. 99 20
      zoom_app/ViewController.swift

+ 99 - 20
zoom_app/ViewController.swift

@@ -228,6 +228,7 @@ class ViewController: NSViewController {
     private weak var meetingsPageListStack: NSStackView?
     private weak var meetingsPageEmptyLabel: NSTextField?
     private weak var meetingsPageLoadMoreButton: NSButton?
+    private weak var meetingsPageScrollView: NSScrollView?
     private var schedulerDayButtons: [Date: NSButton] = [:]
     private var schedulerDateByButtonTag: [Int: Date] = [:]
     private var nextSchedulerButtonTag: Int = 1
@@ -283,11 +284,14 @@ class ViewController: NSViewController {
     private var isPromptingZoomCredentials = false
     private var isLoadingMeetings = false
     private var meetingsScrollObserver: NSObjectProtocol?
+    private var meetingsPageScrollObserver: NSObjectProtocol?
     private var lastMeetingsRefreshAt = Date.distantPast
     private var lastScrollEdgeRefreshAt = Date.distantPast
+    private var lastMeetingsPageAutoLoadAt = Date.distantPast
     // Keep this conservative to avoid Zoom API rate limits.
     private let meetingsRefreshInterval: TimeInterval = 60
     private let scrollRefreshCooldown: TimeInterval = 3
+    private let meetingsPageAutoLoadCooldown: TimeInterval = 0.25
     private var meetingsRateLimitedUntil: Date?
     private var meetingsPageFilteredMeetings: [ScheduledMeeting] = []
     private var meetingsPageSelectedRangePreset: String = "All"
@@ -497,6 +501,7 @@ class ViewController: NSViewController {
         meetingsRefreshTimer?.invalidate()
         meetingsRefreshTimer = nil
         clearMeetingsScrollObserver()
+        clearMeetingsPageScrollObserver()
         removeSearchFieldObserver()
         removeSearchShortcutMonitor()
         homeSearchField = nil
@@ -534,6 +539,7 @@ class ViewController: NSViewController {
     private func showHomeView(profile: GoogleUserProfile?) {
         loginView?.removeFromSuperview()
         clearMeetingsScrollObserver()
+        clearMeetingsPageScrollObserver()
         removeSearchFieldObserver()
         removeSearchShortcutMonitor()
         homeSearchField = nil
@@ -1447,6 +1453,15 @@ class ViewController: NSViewController {
         meetingsScrollView = nil
     }
 
+    private func clearMeetingsPageScrollObserver() {
+        if let meetingsPageScrollObserver {
+            NotificationCenter.default.removeObserver(meetingsPageScrollObserver)
+        }
+        meetingsPageScrollObserver = nil
+        meetingsPageScrollView?.contentView.postsBoundsChangedNotifications = false
+        meetingsPageScrollView = nil
+    }
+
     private func removeSearchFieldObserver() {
         if let searchTextObserver {
             NotificationCenter.default.removeObserver(searchTextObserver)
@@ -1553,6 +1568,42 @@ class ViewController: NSViewController {
         triggerMeetingsRefresh(force: true)
     }
 
+    private func observeMeetingsPageInfiniteScroll(in scrollView: NSScrollView) {
+        clearMeetingsPageScrollObserver()
+        meetingsPageScrollView = scrollView
+        scrollView.contentView.postsBoundsChangedNotifications = true
+        meetingsPageScrollObserver = NotificationCenter.default.addObserver(
+            forName: NSView.boundsDidChangeNotification,
+            object: scrollView.contentView,
+            queue: .main
+        ) { [weak self, weak scrollView] _ in
+            guard let self, let scrollView else { return }
+            self.loadMoreMeetingsIfNeededForScroll(scrollView)
+        }
+    }
+
+    private func loadMoreMeetingsIfNeededForScroll(_ scrollView: NSScrollView) {
+        guard meetingsPageVisibleCount < meetingsPageFilteredMeetings.count else { return }
+        guard let documentView = scrollView.documentView else { return }
+        let visibleRect = scrollView.contentView.bounds
+        let contentHeight = documentView.bounds.height
+        let viewportHeight = visibleRect.height
+        guard contentHeight > 0, viewportHeight > 0 else { return }
+
+        let maxOffset = max(contentHeight - viewportHeight, 0)
+        let y = visibleRect.origin.y
+        let threshold: CGFloat = 72
+        guard y >= (maxOffset - threshold) else { return }
+
+        let now = Date()
+        guard now.timeIntervalSince(lastMeetingsPageAutoLoadAt) >= meetingsPageAutoLoadCooldown else { return }
+        lastMeetingsPageAutoLoadAt = now
+
+        let nextCount = meetingsPageVisibleCount + meetingsPageSize
+        meetingsPageVisibleCount = min(nextCount, meetingsPageFilteredMeetings.count)
+        renderMeetingsPageList()
+    }
+
     @MainActor
     private func resetLoginSigningInState() {
         isSigningIn = false
@@ -1664,20 +1715,52 @@ class ViewController: NSViewController {
     @MainActor
     private func applyMeetingsPageFilters(resetVisible: Bool) {
         let calendar = Calendar.current
+        let now = Date()
+        let activePreset = meetingsPageCategoryFilter?.titleOfSelectedItem ?? meetingsPageSelectedRangePreset
+        meetingsPageSelectedRangePreset = activePreset
+
+        let activeFromDate: Date?
+        let activeToDate: Date?
+        if activePreset == "Custom range",
+           let fromPicker = meetingsPageFromDatePicker,
+           let toPicker = meetingsPageToDatePicker {
+            let fromDay = calendar.startOfDay(for: fromPicker.dateValue)
+            let toDay = calendar.startOfDay(for: toPicker.dateValue)
+            if fromDay <= toDay {
+                activeFromDate = fromDay
+                activeToDate = toDay
+            } else {
+                activeFromDate = toDay
+                activeToDate = fromDay
+            }
+            meetingsPageFromDateFilter = activeFromDate
+            meetingsPageToDateFilter = activeToDate
+        } else {
+            activeFromDate = meetingsPageFromDateFilter
+            activeToDate = meetingsPageToDateFilter
+        }
+
         var filtered = allScheduledMeetings.filter { meeting in
-            switch meetingsPageSelectedRangePreset {
+            guard meeting.start >= now else { return false }
+            switch activePreset {
             case "Today":
-                return calendar.isDate(meeting.start, inSameDayAs: Date())
+                let dayStart = calendar.startOfDay(for: now)
+                let dayEnd = calendar.date(byAdding: .day, value: 1, to: dayStart) ?? now
+                return meeting.start >= now && meeting.start < dayEnd
             case "This week":
-                return calendar.isDate(meeting.start, equalTo: Date(), toGranularity: .weekOfYear)
+                let weekInterval = calendar.dateInterval(of: .weekOfYear, for: now)
+                let weekEnd = weekInterval?.end ?? now
+                return meeting.start >= now && meeting.start < weekEnd
             case "This month":
-                return calendar.isDate(meeting.start, equalTo: Date(), toGranularity: .month)
+                let monthInterval = calendar.dateInterval(of: .month, for: now)
+                let monthEnd = monthInterval?.end ?? now
+                return meeting.start >= now && meeting.start < monthEnd
             case "Custom range":
-                if let fromDate = meetingsPageFromDateFilter {
+                if let fromDate = activeFromDate {
                     let startOfFrom = calendar.startOfDay(for: fromDate)
                     if meeting.start < startOfFrom { return false }
                 }
-                if let toDate = meetingsPageToDateFilter {
+                if let toDate = activeToDate {
                     let startOfTo = calendar.startOfDay(for: toDate)
                     let endOfTo = calendar.date(byAdding: .day, value: 1, to: startOfTo) ?? toDate
                     if meeting.start >= endOfTo { return false }
@@ -1727,9 +1810,7 @@ class ViewController: NSViewController {
                 card.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
             }
         }
-        let hasMore = visibleCount < totalCount
-        meetingsPageLoadMoreButton?.isHidden = hasMore == false
-        meetingsPageLoadMoreButton?.title = hasMore ? "Load more" : "All meetings loaded"
+        meetingsPageLoadMoreButton?.isHidden = true
     }
 
     @objc private func meetingsPageApplyFiltersTapped() {
@@ -1774,6 +1855,9 @@ class ViewController: NSViewController {
     @objc private func meetingsPageRangePresetChanged(_ sender: NSPopUpButton) {
         meetingsPageSelectedRangePreset = sender.titleOfSelectedItem ?? "All"
         updateMeetingsDatePickersEnabledState()
+        Task { @MainActor in
+            self.applyMeetingsPageFilters(resetVisible: true)
+        }
     }
 
     private func updateMeetingsDatePickersEnabledState() {
@@ -1784,14 +1868,6 @@ class ViewController: NSViewController {
         meetingsPageToDatePicker?.alphaValue = isCustomRange ? 1.0 : 0.55
     }
 
-    @objc private func meetingsPageLoadMoreTapped() {
-        Task { @MainActor in
-            let nextCount = meetingsPageVisibleCount + meetingsPageSize
-            meetingsPageVisibleCount = min(nextCount, meetingsPageFilteredMeetings.count)
-            self.renderMeetingsPageList()
-        }
-    }
-
     private func meetingsForDay(_ dayStart: Date) -> [ScheduledMeeting] {
         let calendar = Calendar.current
         let safeDayStart = calendar.startOfDay(for: dayStart)
@@ -4036,7 +4112,7 @@ class ViewController: NSViewController {
         empty.translatesAutoresizingMaskIntoConstraints = false
         empty.isHidden = true
 
-        let loadMoreButton = NSButton(title: "Load more", target: self, action: #selector(meetingsPageLoadMoreTapped))
+        let loadMoreButton = NSButton(title: "", target: nil, action: nil)
         loadMoreButton.translatesAutoresizingMaskIntoConstraints = false
         loadMoreButton.isBordered = false
         loadMoreButton.wantsLayer = true
@@ -4081,7 +4157,7 @@ class ViewController: NSViewController {
             listScroll.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 10),
             listScroll.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
             listScroll.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16),
-            listScroll.bottomAnchor.constraint(equalTo: loadMoreButton.topAnchor, constant: -12),
+            listScroll.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -14),
 
             listDocument.widthAnchor.constraint(equalTo: listScroll.contentView.widthAnchor),
             listStack.topAnchor.constraint(equalTo: listDocument.topAnchor),
@@ -4096,7 +4172,7 @@ class ViewController: NSViewController {
             loadMoreButton.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 16),
             loadMoreButton.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -16),
             loadMoreButton.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -14),
-            loadMoreButton.heightAnchor.constraint(equalToConstant: 40)
+            loadMoreButton.heightAnchor.constraint(equalToConstant: 1)
         ])
 
         meetingsPageCategoryFilter = categoryFilter
@@ -4107,13 +4183,16 @@ class ViewController: NSViewController {
         meetingsPageStatusLabel = statusLabel
         meetingsPageListStack = listStack
         meetingsPageEmptyLabel = empty
+        meetingsPageScrollView = listScroll
         meetingsPageLoadMoreButton = loadMoreButton
+        observeMeetingsPageInfiniteScroll(in: listScroll)
         updateMeetingsDatePickersEnabledState()
         return root
     }
 
     deinit {
         removeSearchFieldObserver()
+        clearMeetingsPageScrollObserver()
         removeSearchShortcutMonitor()
     }