Переглянути джерело

Gate non-premium meeting surfaces and stabilize premium refresh behavior.

Block meeting/scheduler API-driven flows for non-premium users, add blurred paywall overlays for locked sections, hide duplicate background lock text, and refresh/synchronize meeting data when premium access changes.

Made-with: Cursor
huzaifahayat12 3 місяців тому
батько
коміт
c6d220cdd5
1 змінених файлів з 180 додано та 3 видалено
  1. 180 3
      zoom_app/ViewController.swift

+ 180 - 3
zoom_app/ViewController.swift

@@ -231,6 +231,9 @@ class ViewController: NSViewController {
     private weak var meetingsPageEmptyLabel: NSTextField?
     private weak var meetingsPageLoadMoreButton: NSButton?
     private weak var meetingsPageScrollView: NSScrollView?
+    private weak var homePremiumLockOverlay: NSButton?
+    private weak var meetingsPremiumLockOverlay: NSButton?
+    private weak var schedulerPremiumLockOverlay: NSButton?
     private var schedulerDayButtons: [Date: NSButton] = [:]
     private var schedulerDateByButtonTag: [Int: Date] = [:]
     private var nextSchedulerButtonTag: Int = 1
@@ -615,6 +618,7 @@ class ViewController: NSViewController {
             showSimpleAlert(title: "Sign in required", message: "Please sign in with Zoom to schedule a meeting.")
             return
         }
+        guard ensurePremiumAccess() else { return }
         if let existing = scheduleMeetingWindow {
             existing.makeKeyAndOrderFront(nil)
             NSApp.activate(ignoringOtherApps: true)
@@ -1451,6 +1455,10 @@ class ViewController: NSViewController {
             applyGuestLoginMessages()
             return
         }
+        guard ensurePremiumAccess() else {
+            applyPremiumLockMessages()
+            return
+        }
         Task { @MainActor in
             self.animateMeetingsRefresh()
             self.setMeetingsLoadingUI(true)
@@ -1498,6 +1506,7 @@ class ViewController: NSViewController {
 
     private func triggerMeetingsRefresh(force: Bool = false) {
         guard isUserLoggedIn() else { return }
+        guard storeKitCoordinator.hasPremiumAccess else { return }
         let now = Date()
         if let until = meetingsRateLimitedUntil, now < until {
             return
@@ -1879,20 +1888,45 @@ class ViewController: NSViewController {
         schedulerMeetingsEmptyLabel?.stringValue = "Please sign in with Zoom to use Scheduler."
     }
 
+    @MainActor
+    private func applyPremiumLockMessages() {
+        // Keep underlying section labels hidden while locked to avoid duplicate text beneath overlays.
+        meetingsStatusLabel?.stringValue = ""
+        emptyMeetingLabel?.isHidden = true
+        meetingsPageStatusLabel?.stringValue = ""
+        meetingsPageEmptyLabel?.isHidden = true
+        meetingsPageCountLabel?.stringValue = ""
+        schedulerMeetingsEmptyLabel?.isHidden = true
+    }
+
+    private func ensurePremiumAccess() -> Bool {
+        guard storeKitCoordinator.hasPremiumAccess else {
+            showPaywall()
+            return false
+        }
+        return true
+    }
+
     @MainActor
     private func applyAuthenticationUIState(triggerInitialRefresh: Bool) {
         let loggedIn = isUserLoggedIn()
         updateTopBarAuthButton()
         settingsGoogleActionButton?.title = loggedIn ? "Sign Out" : "Sign in with Zoom"
         settingsGoogleActionButton?.isEnabled = true
+        updatePremiumLockedSections()
 
         meetingsRefreshTimer?.invalidate()
         meetingsRefreshTimer = nil
         meetingsRateLimitedUntil = nil
         if loggedIn {
-            startMeetingsAutoRefresh()
-            if triggerInitialRefresh {
-                triggerMeetingsRefresh(force: true)
+            if storeKitCoordinator.hasPremiumAccess {
+                startMeetingsAutoRefresh()
+                if triggerInitialRefresh {
+                    triggerMeetingsRefresh(force: true)
+                }
+            } else {
+                applyMeetings([])
+                applyPremiumLockMessages()
             }
         } else {
             applyGuestLoginMessages()
@@ -1939,6 +1973,21 @@ class ViewController: NSViewController {
     @MainActor
     private func applyMeetings(_ meetings: [ScheduledMeeting]) {
         allScheduledMeetings = meetings
+        if meetings.isEmpty == false {
+            let calendar = Calendar.current
+            let selectedStart = calendar.startOfDay(for: selectedMeetingsDayStart)
+            let selectedEnd = calendar.date(byAdding: .day, value: 1, to: selectedStart) ?? selectedStart.addingTimeInterval(60 * 60 * 24)
+            let selectedDayHasMeetings = meetings.contains { meeting in
+                meeting.start >= selectedStart && meeting.start < selectedEnd
+            }
+            if selectedDayHasMeetings == false,
+               let firstUpcoming = meetings
+                .filter({ $0.start >= Date() })
+                .sorted(by: { $0.start < $1.start })
+                .first {
+                selectedMeetingsDayStart = calendar.startOfDay(for: firstUpcoming.start)
+            }
+        }
         applyFilteredMeetings()
         applyMeetingsPageFilters(resetVisible: true)
     }
@@ -1959,6 +2008,13 @@ class ViewController: NSViewController {
             renderSchedulerCalendarGrid()
             return
         }
+        guard storeKitCoordinator.hasPremiumAccess else {
+            meetingsStatusLabel?.stringValue = ""
+            emptyMeetingLabel?.isHidden = true
+            applySchedulerMeetingsForSelectedDate()
+            renderSchedulerCalendarGrid()
+            return
+        }
 
         let query = (homeSearchField?.stringValue ?? "").trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
         let calendar = Calendar.current
@@ -2015,6 +2071,12 @@ class ViewController: NSViewController {
             renderMeetingsPageList()
             return
         }
+        guard storeKitCoordinator.hasPremiumAccess else {
+            meetingsPageFilteredMeetings = []
+            meetingsPageVisibleCount = 0
+            renderMeetingsPageList()
+            return
+        }
         let calendar = Calendar.current
         let now = Date()
         let activePreset = meetingsPageCategoryFilter?.titleOfSelectedItem ?? meetingsPageSelectedRangePreset
@@ -2099,6 +2161,13 @@ class ViewController: NSViewController {
             meetingsPageLoadMoreButton?.isHidden = true
             return
         }
+        guard storeKitCoordinator.hasPremiumAccess else {
+            meetingsPageCountLabel?.stringValue = ""
+            meetingsPageStatusLabel?.stringValue = ""
+            meetingsPageEmptyLabel?.isHidden = true
+            meetingsPageLoadMoreButton?.isHidden = true
+            return
+        }
 
         let totalCount = meetingsPageFilteredMeetings.count
         let visibleCount = min(meetingsPageVisibleCount, totalCount)
@@ -2273,6 +2342,10 @@ class ViewController: NSViewController {
             schedulerMeetingsEmptyLabel?.stringValue = "Please sign in with Zoom to use Scheduler."
             return
         }
+        guard storeKitCoordinator.hasPremiumAccess else {
+            schedulerMeetingsEmptyLabel?.isHidden = true
+            return
+        }
 
         let meetings = meetingsForDay(selectedMeetingsDayStart)
         if meetings.isEmpty {
@@ -2449,6 +2522,13 @@ class ViewController: NSViewController {
             }
             return
         }
+        guard storeKitCoordinator.hasPremiumAccess else {
+            await MainActor.run {
+                self.applyMeetings([])
+                self.applyPremiumLockMessages()
+            }
+            return
+        }
         if isLoadingMeetings { return }
         isLoadingMeetings = true
         defer {
@@ -3022,6 +3102,10 @@ class ViewController: NSViewController {
                 let wasPremium = self.lastKnownPremiumAccess
                 self.lastKnownPremiumAccess = newIsPremium
                 self.updatePremiumButtons()
+                if (wasPremium == false) && newIsPremium && self.isUserLoggedIn() {
+                    self.startMeetingsAutoRefresh()
+                    self.triggerMeetingsRefresh(force: true)
+                }
                 if wasPremium && !newIsPremium {
                     self.showPaywall()
                 }
@@ -3042,6 +3126,11 @@ class ViewController: NSViewController {
         settingsUpgradeTitleLabel?.stringValue = isPremium ? "Premium Active" : "Upgrade"
         settingsUpgradeButton?.isEnabled = isPremium == false
         settingsUpgradeButton?.alphaValue = isPremium ? 0.6 : 1.0
+        updatePremiumLockedSections()
+        if isPremium == false, isUserLoggedIn() {
+            applyMeetings([])
+            applyPremiumLockMessages()
+        }
 
         updateTopBarPremiumButton()
     }
@@ -3098,6 +3187,7 @@ class ViewController: NSViewController {
         let keepSelected = selectedHomeSidebarItem
         selectedHomeSidebarItem = keepSelected
         showHomeView(profile: nil)
+        updatePremiumLockedSections()
     }
 
     private func applyWindowBackgroundForCurrentTheme(_ window: NSWindow) {
@@ -4332,10 +4422,97 @@ class ViewController: NSViewController {
         topBarAuthButtonWidthConstraint?.isActive = true
         updateTopBarAuthButton()
         applyAuthenticationUIState(triggerInitialRefresh: false)
+        updatePremiumLockedSections()
 
         return root
     }
 
+    @objc private func premiumLockedAreaTapped(_ sender: NSButton) {
+        showPaywall()
+    }
+
+    @MainActor
+    private func updatePremiumLockedSections() {
+        let shouldLock = isUserLoggedIn() && (storeKitCoordinator.hasPremiumAccess == false)
+        homePremiumLockOverlay = updatePremiumLockOverlay(
+            existing: homePremiumLockOverlay,
+            in: homeMeetingsPanel,
+            locked: shouldLock,
+            message: "Upgrade to Premium to access meetings"
+        )
+        meetingsPremiumLockOverlay = updatePremiumLockOverlay(
+            existing: meetingsPremiumLockOverlay,
+            in: meetingsPageRootView,
+            locked: shouldLock,
+            message: "Upgrade to Premium to unlock Meetings"
+        )
+        schedulerPremiumLockOverlay = updatePremiumLockOverlay(
+            existing: schedulerPremiumLockOverlay,
+            in: schedulerRootView,
+            locked: shouldLock,
+            message: "Upgrade to Premium to unlock Scheduler"
+        )
+    }
+
+    @MainActor
+    private func updatePremiumLockOverlay(
+        existing: NSButton?,
+        in target: NSView?,
+        locked: Bool,
+        message: String
+    ) -> NSButton? {
+        guard let target else { return existing }
+        let button: NSButton
+        if let existing, existing.superview === target {
+            button = existing
+        } else {
+            let created = NSButton(title: message, target: self, action: #selector(premiumLockedAreaTapped(_:)))
+            created.translatesAutoresizingMaskIntoConstraints = false
+            created.isBordered = false
+            created.bezelStyle = .shadowlessSquare
+            created.alignment = .center
+            created.wantsLayer = true
+            created.layer?.cornerRadius = 12
+            created.layer?.masksToBounds = true
+            created.layer?.backgroundColor = NSColor.clear.cgColor
+            created.font = .systemFont(ofSize: 15, weight: .semibold)
+            created.contentTintColor = .white
+
+            let blur = NSVisualEffectView()
+            blur.translatesAutoresizingMaskIntoConstraints = false
+            blur.material = .hudWindow
+            blur.blendingMode = .withinWindow
+            blur.state = .active
+            created.addSubview(blur, positioned: .below, relativeTo: nil)
+
+            let dim = NSView()
+            dim.translatesAutoresizingMaskIntoConstraints = false
+            dim.wantsLayer = true
+            dim.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.72).cgColor
+            created.addSubview(dim, positioned: .above, relativeTo: blur)
+
+            target.addSubview(created)
+            NSLayoutConstraint.activate([
+                created.leadingAnchor.constraint(equalTo: target.leadingAnchor),
+                created.trailingAnchor.constraint(equalTo: target.trailingAnchor),
+                created.topAnchor.constraint(equalTo: target.topAnchor),
+                created.bottomAnchor.constraint(equalTo: target.bottomAnchor),
+                blur.leadingAnchor.constraint(equalTo: created.leadingAnchor),
+                blur.trailingAnchor.constraint(equalTo: created.trailingAnchor),
+                blur.topAnchor.constraint(equalTo: created.topAnchor),
+                blur.bottomAnchor.constraint(equalTo: created.bottomAnchor),
+                dim.leadingAnchor.constraint(equalTo: created.leadingAnchor),
+                dim.trailingAnchor.constraint(equalTo: created.trailingAnchor),
+                dim.topAnchor.constraint(equalTo: created.topAnchor),
+                dim.bottomAnchor.constraint(equalTo: created.bottomAnchor)
+            ])
+            button = created
+        }
+        button.title = message
+        button.isHidden = locked == false
+        return button
+    }
+
     private func makeMeetingsPageView() -> NSView {
         let root = NSView()
         root.wantsLayer = true