Browse Source

Improve meetings refresh with auto-polling and scroll edge triggers.

Refresh scheduled meetings periodically, trigger reloads when users scroll to the top or bottom of the list, and guard against overlapping refresh calls.

Made-with: Cursor
huzaifahayat12 6 days ago
parent
commit
4ed0c95739
1 changed files with 87 additions and 1 deletions
  1. 87 1
      zoom_app/ViewController.swift

+ 87 - 1
zoom_app/ViewController.swift

@@ -37,9 +37,17 @@ class ViewController: NSViewController {
37 37
     private weak var emptyMeetingLabel: NSTextField?
38 38
     private weak var meetingsListStack: NSStackView?
39 39
     private weak var meetingsStatusLabel: NSTextField?
40
+    private weak var meetingsScrollView: NSScrollView?
40 41
     private var clockTimer: Timer?
42
+    private var meetingsRefreshTimer: Timer?
41 43
     private var isSigningIn = false
42 44
     private var isPromptingZoomCredentials = false
45
+    private var isLoadingMeetings = false
46
+    private var meetingsScrollObserver: NSObjectProtocol?
47
+    private var lastMeetingsRefreshAt = Date.distantPast
48
+    private var lastScrollEdgeRefreshAt = Date.distantPast
49
+    private let meetingsRefreshInterval: TimeInterval = 8
50
+    private let scrollRefreshCooldown: TimeInterval = 3
43 51
 
44 52
     override func viewDidLoad() {
45 53
         super.viewDidLoad()
@@ -73,6 +81,9 @@ class ViewController: NSViewController {
73 81
 
74 82
     private func showLoginView() {
75 83
         clockTimer?.invalidate()
84
+        meetingsRefreshTimer?.invalidate()
85
+        meetingsRefreshTimer = nil
86
+        clearMeetingsScrollObserver()
76 87
         homeView?.removeFromSuperview()
77 88
         homeView = nil
78 89
         isSigningIn = false
@@ -89,12 +100,14 @@ class ViewController: NSViewController {
89 100
 
90 101
     private func showHomeView(profile: GoogleUserProfile?) {
91 102
         loginView?.removeFromSuperview()
103
+        clearMeetingsScrollObserver()
92 104
         homeView?.removeFromSuperview()
93 105
         homeView = makeHomeView(profile: profile)
94 106
         if let homeView { attachToRoot(homeView) }
95 107
         persistLoggedInState(true)
96 108
         startClock()
97
-        Task { await loadScheduledMeetings() }
109
+        startMeetingsAutoRefresh()
110
+        triggerMeetingsRefresh(force: true)
98 111
     }
99 112
 
100 113
     private func isUserLoggedIn() -> Bool {
@@ -188,12 +201,81 @@ class ViewController: NSViewController {
188 201
     }
189 202
 
190 203
     @objc private func logoutTapped() {
204
+        meetingsRefreshTimer?.invalidate()
205
+        meetingsRefreshTimer = nil
206
+        clearMeetingsScrollObserver()
191 207
         googleOAuth.clearSavedTokens()
192 208
         zoomOAuth.clearSavedTokens()
193 209
         persistLoggedInState(false)
194 210
         showLoginView()
195 211
     }
196 212
 
213
+    private func startMeetingsAutoRefresh() {
214
+        meetingsRefreshTimer?.invalidate()
215
+        // Poll Zoom meetings periodically so newly scheduled meetings appear automatically.
216
+        meetingsRefreshTimer = Timer.scheduledTimer(withTimeInterval: meetingsRefreshInterval, repeats: true) { [weak self] _ in
217
+            guard let self else { return }
218
+            self.triggerMeetingsRefresh()
219
+        }
220
+    }
221
+
222
+    private func triggerMeetingsRefresh(force: Bool = false) {
223
+        let now = Date()
224
+        if force == false, now.timeIntervalSince(lastMeetingsRefreshAt) < meetingsRefreshInterval {
225
+            return
226
+        }
227
+        lastMeetingsRefreshAt = now
228
+        Task { await self.loadScheduledMeetings() }
229
+    }
230
+
231
+    private func clearMeetingsScrollObserver() {
232
+        if let meetingsScrollObserver {
233
+            NotificationCenter.default.removeObserver(meetingsScrollObserver)
234
+        }
235
+        meetingsScrollObserver = nil
236
+        meetingsScrollView?.contentView.postsBoundsChangedNotifications = false
237
+        meetingsScrollView = nil
238
+    }
239
+
240
+    private func observeMeetingsScrollEdges(in scrollView: NSScrollView) {
241
+        clearMeetingsScrollObserver()
242
+        meetingsScrollView = scrollView
243
+        scrollView.contentView.postsBoundsChangedNotifications = true
244
+        meetingsScrollObserver = NotificationCenter.default.addObserver(
245
+            forName: NSView.boundsDidChangeNotification,
246
+            object: scrollView.contentView,
247
+            queue: .main
248
+        ) { [weak self, weak scrollView] _ in
249
+            guard let self, let scrollView else { return }
250
+            self.refreshMeetingsIfScrolledToEdge(scrollView)
251
+        }
252
+    }
253
+
254
+    private func refreshMeetingsIfScrolledToEdge(_ scrollView: NSScrollView) {
255
+        guard let documentView = scrollView.documentView else { return }
256
+
257
+        let visibleRect = scrollView.contentView.bounds
258
+        let contentHeight = documentView.bounds.height
259
+        let viewportHeight = visibleRect.height
260
+        if contentHeight <= viewportHeight + 2 {
261
+            return
262
+        }
263
+
264
+        let maxOffset = max(contentHeight - viewportHeight, 0)
265
+        let y = visibleRect.origin.y
266
+        let threshold: CGFloat = 12
267
+        let reachedTop = y <= threshold
268
+        let reachedBottom = y >= (maxOffset - threshold)
269
+        guard reachedTop || reachedBottom else { return }
270
+
271
+        let now = Date()
272
+        if now.timeIntervalSince(lastScrollEdgeRefreshAt) < scrollRefreshCooldown {
273
+            return
274
+        }
275
+        lastScrollEdgeRefreshAt = now
276
+        triggerMeetingsRefresh(force: true)
277
+    }
278
+
197 279
     @MainActor
198 280
     private func resetLoginSigningInState() {
199 281
         isSigningIn = false
@@ -251,6 +333,9 @@ class ViewController: NSViewController {
251 333
     }
252 334
 
253 335
     private func loadScheduledMeetings() async {
336
+        if isLoadingMeetings { return }
337
+        isLoadingMeetings = true
338
+        defer { isLoadingMeetings = false }
254 339
         do {
255 340
             let zoomToken = try await zoomOAuth.validAccessToken(presentingWindow: view.window)
256 341
             let zoomMeetings = try await fetchZoomScheduledMeetings(accessToken: zoomToken)
@@ -729,6 +814,7 @@ class ViewController: NSViewController {
729 814
         meetingsListStack = meetingsStack
730 815
         meetingsStatusLabel = meetingsStatus
731 816
         emptyMeetingLabel = noMeeting
817
+        observeMeetingsScrollEdges(in: meetingsScrollView)
732 818
         updateClock()
733 819
         return root
734 820
     }