Эх сурвалжийг харах

Make paywall a fixed overlay on parent window.

Present the paywall consistently as a non-draggable, non-resizable AppKit child window that matches the parent window frame. Replace the launcher/widgets SwiftUI sheet path with the same AppDelegate presenter used on startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
huzaifahayat12 2 сар өмнө
parent
commit
ba1412764b

+ 31 - 21
google_apps/AppDelegate.swift

@@ -59,26 +59,44 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
         guard !hasPresentedStartupPaywall else { return }
         guard !PremiumStore.shared.isPremiumUnlocked else { return }
         guard let parent = launcherWindow else { return }
+        presentPremiumPaywall(over: parent, storeAsStartupPaywall: true)
+
+        hasPresentedStartupPaywall = true
+
+        observePremiumUnlockToAutoDismissPaywall()
+    }
+
+    /// Presents the premium paywall as a non-draggable, non-resizable child window that exactly covers `parent`.
+    @MainActor
+    func presentPremiumPaywall(over parent: NSWindow? = nil, storeAsStartupPaywall: Bool = false) {
+        guard !PremiumStore.shared.isPremiumUnlocked else { return }
+        guard let parentWindow = parent ?? launcherWindow else { return }
+
         if let existing = startupPaywallWindow, existing.isVisible {
+            // Ensure it stays pinned to the parent’s current frame.
+            existing.setFrame(PremiumPaywallWindowSizing.paywallFrame(forParentWindow: parentWindow), display: false)
             existing.makeKeyAndOrderFront(nil)
+            NSApp.activate(ignoringOtherApps: true)
             return
         }
 
-        let paywallSize = PremiumPaywallWindowSizing.contentSize(forParentWindow: parent)
+        let paywallFrame = PremiumPaywallWindowSizing.paywallFrame(forParentWindow: parentWindow)
         let paywallWindow = NSWindow(
-            contentRect: NSRect(origin: .zero, size: paywallSize),
-            styleMask: [.borderless, .resizable],
+            contentRect: paywallFrame,
+            styleMask: [.borderless],
             backing: .buffered,
             defer: false
         )
         paywallWindow.isReleasedWhenClosed = false
         paywallWindow.delegate = self
-        paywallWindow.isMovableByWindowBackground = true
+        paywallWindow.isMovable = false
+        paywallWindow.isMovableByWindowBackground = false
         paywallWindow.hasShadow = true
         paywallWindow.isOpaque = false
         paywallWindow.backgroundColor = .clear
         paywallWindow.level = .floating
-        paywallWindow.minSize = NSSize(width: 560, height: 480)
+        paywallWindow.minSize = paywallFrame.size
+        paywallWindow.maxSize = paywallFrame.size
 
         let hosting = NSHostingController(
             rootView: PremiumFeaturesView(onDismissFromAppKitHost: { [weak self] in
@@ -86,7 +104,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
             })
             .environmentObject(PremiumStore.shared)
         )
-        hosting.view.frame = NSRect(origin: .zero, size: paywallSize)
+        hosting.view.frame = NSRect(origin: .zero, size: paywallFrame.size)
         hosting.view.autoresizingMask = [.width, .height]
         paywallWindow.contentViewController = hosting
 
@@ -99,24 +117,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
             layer.masksToBounds = true
         }
 
-        let parentFrame = parent.frame
-        paywallWindow.setFrame(
-            NSRect(
-                x: parentFrame.midX - paywallSize.width / 2,
-                y: parentFrame.midY - paywallSize.height / 2,
-                width: paywallSize.width,
-                height: paywallSize.height
-            ),
-            display: false
-        )
+        paywallWindow.setFrame(paywallFrame, display: false)
 
-        startupPaywallWindow = paywallWindow
-        hasPresentedStartupPaywall = true
-        parent.addChildWindow(paywallWindow, ordered: .above)
+        if storeAsStartupPaywall {
+            startupPaywallWindow = paywallWindow
+        } else {
+            startupPaywallWindow = paywallWindow
+        }
+        parentWindow.addChildWindow(paywallWindow, ordered: .above)
         paywallWindow.makeKeyAndOrderFront(nil)
         NSApp.activate(ignoringOtherApps: true)
-
-        observePremiumUnlockToAutoDismissPaywall()
     }
 
     @MainActor

+ 9 - 16
google_apps/InAppBrowserWindow.swift

@@ -730,20 +730,22 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
             return
         }
 
-        let paywallSize = PremiumPaywallWindowSizing.contentSize(forParentWindow: parentWindow)
+        let paywallFrame = PremiumPaywallWindowSizing.paywallFrame(forParentWindow: parentWindow)
         let paywallWindow = NSWindow(
-            contentRect: NSRect(origin: .zero, size: paywallSize),
-            styleMask: [.borderless, .resizable],
+            contentRect: paywallFrame,
+            styleMask: [.borderless],
             backing: .buffered,
             defer: false
         )
         paywallWindow.isReleasedWhenClosed = false
         paywallWindow.delegate = self
-        paywallWindow.isMovableByWindowBackground = true
+        paywallWindow.isMovable = false
+        paywallWindow.isMovableByWindowBackground = false
         paywallWindow.hasShadow = true
         paywallWindow.isOpaque = false
         paywallWindow.backgroundColor = .clear
-        paywallWindow.minSize = NSSize(width: 560, height: 480)
+        paywallWindow.minSize = paywallFrame.size
+        paywallWindow.maxSize = paywallFrame.size
 
         let hosting = NSHostingController(
             rootView: PremiumFeaturesView(onDismissFromAppKitHost: { [weak self] in
@@ -751,21 +753,12 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
             })
             .environmentObject(PremiumStore.shared)
         )
-        hosting.view.frame = NSRect(origin: .zero, size: paywallSize)
+        hosting.view.frame = NSRect(origin: .zero, size: paywallFrame.size)
         hosting.view.autoresizingMask = [.width, .height]
         paywallWindow.contentViewController = hosting
         configurePaywallWindowRoundedCorners(paywallWindow)
 
-        let pf = parentWindow.frame
-        paywallWindow.setFrame(
-            NSRect(
-                x: pf.midX - paywallSize.width / 2,
-                y: pf.midY - paywallSize.height / 2,
-                width: paywallSize.width,
-                height: paywallSize.height
-            ),
-            display: false
-        )
+        paywallWindow.setFrame(paywallFrame, display: false)
 
         premiumSheetWindow = paywallWindow
         parentWindow.addChildWindow(paywallWindow, ordered: .above)

+ 8 - 13
google_apps/LauncherRootView.swift

@@ -183,23 +183,18 @@ struct LauncherRootView: View {
             AppDetailView(app: app)
                 .frame(minWidth: 360, minHeight: 220)
         }
-        .sheet(isPresented: $showingPremiumScreen) {
-            PremiumFeaturesView()
-                .environmentObject(premiumStore)
-                .frame(
-                    minWidth: PremiumPaywallMetrics.sheetMinWidth,
-                    idealWidth: PremiumPaywallMetrics.sheetIdealWidth,
-                    minHeight: PremiumPaywallMetrics.sheetMinHeight,
-                    idealHeight: PremiumPaywallMetrics.sheetIdealHeight
-                )
-        }
         .task {
             await premiumStore.refreshEntitlements()
         }
         .onChange(of: showingPremiumScreen) { isShowing in
-            guard !isShowing else { return }
-            Task {
-                await premiumStore.refreshEntitlements()
+            guard isShowing else {
+                Task { await premiumStore.refreshEntitlements() }
+                return
+            }
+            Task { @MainActor in
+                (NSApp.delegate as? AppDelegate)?.presentPremiumPaywall(over: NSApp.keyWindow ?? NSApp.mainWindow)
+                // Reset immediately; the paywall is presented as an AppKit child window now.
+                showingPremiumScreen = false
             }
         }
         .onChange(of: premiumStore.isPremiumUnlocked) { unlocked in

+ 3 - 9
google_apps/PremiumFeaturesView.swift

@@ -516,15 +516,9 @@ enum PremiumPaywallMetrics {
 }
 
 enum PremiumPaywallWindowSizing {
-    /// Sizes the paywall to sit inside the parent window (launcher default content ~980×700, browser ~1200×800).
-    static func contentSize(forParentWindow parent: NSWindow) -> NSSize {
-        let inset: CGFloat = 48
-        let content = parent.contentRect(forFrameRect: parent.frame)
-        let usableW = content.width - inset * 2
-        let usableH = content.height - inset * 2
-        let w = min(1000, max(600, usableW))
-        let h = min(720, max(520, usableH))
-        return NSSize(width: floor(w), height: floor(h))
+    /// Make the paywall exactly cover the parent window (modal overlay).
+    static func paywallFrame(forParentWindow parent: NSWindow) -> NSRect {
+        parent.frame
     }
 }
 

+ 6 - 9
google_apps/Widgets/WidgetsRootView.swift

@@ -60,15 +60,12 @@ struct WidgetsRootView: View {
             }
         }
         .animation(.spring(response: 0.28, dampingFraction: 0.9), value: toastMessage != nil)
-        .sheet(isPresented: $showingPremiumScreen) {
-            PremiumFeaturesView()
-                .environmentObject(premiumStore)
-                .frame(
-                    minWidth: PremiumPaywallMetrics.sheetMinWidth,
-                    idealWidth: PremiumPaywallMetrics.sheetIdealWidth,
-                    minHeight: PremiumPaywallMetrics.sheetMinHeight,
-                    idealHeight: PremiumPaywallMetrics.sheetIdealHeight
-                )
+        .onChange(of: showingPremiumScreen) { isShowing in
+            guard isShowing else { return }
+            Task { @MainActor in
+                (NSApp.delegate as? AppDelegate)?.presentPremiumPaywall(over: NSApp.keyWindow ?? NSApp.mainWindow)
+                showingPremiumScreen = false
+            }
         }
     }