Sfoglia il codice sorgente

Fix paywall trial display, premium gating, and free-tier billing.

Trust StoreKit for intro-trial badges, load paywall store data reliably, and stop stale premium cache from granting access after expiry. Consolidate free-use charging, route Upgrade to the paywall, and keep a single sidebar free-uses banner.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 3 settimane fa
parent
commit
d0c7281c43

+ 0 - 1
smart_printer/OCRFileView.swift

@@ -323,7 +323,6 @@ final class OCRFileOverlayView: NSView, AppearanceRefreshable {
         Task { @MainActor in
         Task { @MainActor in
             do {
             do {
                 let text = try await OCRFileService.recognizeText(from: imageURL)
                 let text = try await OCRFileService.recognizeText(from: imageURL)
-                PremiumAccess.recordFreeUseIfNeeded()
                 showRecognizedText(text)
                 showRecognizedText(text)
             } catch {
             } catch {
                 showRecognitionError(error)
                 showRecognitionError(error)

+ 42 - 25
smart_printer/PaywallView.swift

@@ -285,6 +285,12 @@ final class StoreManager {
         await initialEntitlementTask?.value
         await initialEntitlementTask?.value
     }
     }
 
 
+    func ensurePaywallStoreDataLoaded(presentLoadingUI: Bool = true) async {
+        await ensureEntitlementsResolved()
+        await loadProducts(presentLoadingUI: presentLoadingUI && !hasAllProductsLoaded)
+        await refreshTrialOffers()
+    }
+
     func product(for plan: PaywallPlan) -> Product? {
     func product(for plan: PaywallPlan) -> Product? {
         productsByID[plan.productID]
         productsByID[plan.productID]
     }
     }
@@ -390,6 +396,7 @@ final class StoreManager {
         }
         }
 
 
         await reconcilePremiumAccess(trustedPlan: nil)
         await reconcilePremiumAccess(trustedPlan: nil)
+        await syncPurchaseHistory(allowReset: true)
         if !isPremium {
         if !isPremium {
             purchaseError = .noActivePurchases
             purchaseError = .noActivePurchases
         }
         }
@@ -454,6 +461,7 @@ final class StoreManager {
             guard !loaded.isEmpty else {
             guard !loaded.isEmpty else {
                 productLoadError = PaywallConfigService.shared.config.messages.noPlansAvailable
                 productLoadError = PaywallConfigService.shared.config.messages.noPlansAvailable
                 productsByID = [:]
                 productsByID = [:]
+                await refreshTrialOffers()
                 return
                 return
             }
             }
             var map: [String: Product] = [:]
             var map: [String: Product] = [:]
@@ -468,9 +476,11 @@ final class StoreManager {
         } catch is ProductLoadError {
         } catch is ProductLoadError {
             productLoadError = PaywallConfigService.shared.config.messages.plansLoadFailed
             productLoadError = PaywallConfigService.shared.config.messages.plansLoadFailed
             purchaseError = .productsLoadTimeout
             purchaseError = .productsLoadTimeout
+            await refreshTrialOffers()
         } catch {
         } catch {
             productLoadError = PaywallConfigService.shared.config.messages.plansLoadFailed
             productLoadError = PaywallConfigService.shared.config.messages.plansLoadFailed
             purchaseError = .generic
             purchaseError = .generic
+            await refreshTrialOffers()
         }
         }
     }
     }
 
 
@@ -520,15 +530,12 @@ final class StoreManager {
             }
             }
         }
         }
 
 
-        var finalPlan = mergeTrustedPlan(resolvedPlan, trustedPlan)
-        if finalPlan == nil {
-            finalPlan = restoredCachedPremiumPlan()
-        }
+        let finalPlan = mergeTrustedPlan(resolvedPlan, trustedPlan)
 
 
         applyPremiumStatus(plan: finalPlan)
         applyPremiumStatus(plan: finalPlan)
         hasResolvedPremiumStatus = true
         hasResolvedPremiumStatus = true
         NotificationCenter.default.post(name: .entitlementsDidResolve, object: nil)
         NotificationCenter.default.post(name: .entitlementsDidResolve, object: nil)
-        await syncPurchaseHistory()
+        await syncPurchaseHistory(allowReset: false)
         await refreshTrialOffers()
         await refreshTrialOffers()
     }
     }
 
 
@@ -607,7 +614,7 @@ final class StoreManager {
         UserDefaults.standard.set(true, forKey: Self.cachedHasEverPurchasedKey)
         UserDefaults.standard.set(true, forKey: Self.cachedHasEverPurchasedKey)
     }
     }
 
 
-    private func syncPurchaseHistory() async {
+    private func syncPurchaseHistory(allowReset: Bool) async {
         var hasRecordedPurchase = false
         var hasRecordedPurchase = false
 
 
         for await result in Transaction.all {
         for await result in Transaction.all {
@@ -622,7 +629,7 @@ final class StoreManager {
                 hasEverPurchasedPremium = true
                 hasEverPurchasedPremium = true
                 UserDefaults.standard.set(true, forKey: Self.cachedHasEverPurchasedKey)
                 UserDefaults.standard.set(true, forKey: Self.cachedHasEverPurchasedKey)
             }
             }
-        } else if hasEverPurchasedPremium {
+        } else if allowReset, hasEverPurchasedPremium {
             resetToNeverPurchasedState()
             resetToNeverPurchasedState()
         }
         }
     }
     }
@@ -634,7 +641,7 @@ final class StoreManager {
     }
     }
 
 
     private func refreshTrialOffers() async {
     private func refreshTrialOffers() async {
-        guard isEligibleForIntroTrial else {
+        guard !isPremium else {
             trialDisplayByPlan = [:]
             trialDisplayByPlan = [:]
             postStoreStateDidChange()
             postStoreStateDidChange()
             return
             return
@@ -656,13 +663,28 @@ final class StoreManager {
 
 
     private func resolveTrialDisplay(for plan: PaywallPlan) async -> PaywallTrialDisplay? {
     private func resolveTrialDisplay(for plan: PaywallPlan) async -> PaywallTrialDisplay? {
         guard plan == PaywallConfigService.shared.config.trialEligiblePlan else { return nil }
         guard plan == PaywallConfigService.shared.config.trialEligiblePlan else { return nil }
+        guard !isPremium else { return nil }
+
+        let config = PaywallConfigService.shared.config
+
         guard let product = product(for: plan),
         guard let product = product(for: plan),
               let subscription = product.subscription else {
               let subscription = product.subscription else {
-            return nil
+            return fallbackTrialDisplay(config: config)
         }
         }
+
         guard await subscription.isEligibleForIntroOffer else { return nil }
         guard await subscription.isEligibleForIntroOffer else { return nil }
-        guard let offer = subscription.introductoryOffer else { return nil }
-        return PaywallTrialDisplay.from(offer: offer, config: PaywallConfigService.shared.config)
+
+        if let offer = subscription.introductoryOffer {
+            return PaywallTrialDisplay.from(offer: offer, config: config)
+        }
+
+        return fallbackTrialDisplay(config: config)
+    }
+
+    private func fallbackTrialDisplay(config: PaywallConfig) -> PaywallTrialDisplay? {
+        guard !isPremium else { return nil }
+        guard let fallback = config.trial.fallbackDuration else { return nil }
+        return PaywallTrialDisplay.from(fallback: fallback, config: config)
     }
     }
 
 
     private func applyPremiumStatus(plan: PaywallPlan?) {
     private func applyPremiumStatus(plan: PaywallPlan?) {
@@ -816,9 +838,6 @@ enum PremiumAccess {
     @MainActor
     @MainActor
     static func canAccess(_ feature: AppFeature) -> Bool {
     static func canAccess(_ feature: AppFeature) -> Bool {
         let store = StoreManager.shared
         let store = StoreManager.shared
-        if store.isResolvingEntitlements {
-            return FreeTierManager.canAccess(feature, isPremium: false)
-        }
         return FreeTierManager.canAccess(feature, isPremium: store.isPremium)
         return FreeTierManager.canAccess(feature, isPremium: store.isPremium)
     }
     }
 
 
@@ -841,12 +860,15 @@ enum PremiumAccess {
     static func requireForAction(
     static func requireForAction(
         feature: AppFeature,
         feature: AppFeature,
         from window: NSWindow?,
         from window: NSWindow?,
-        returnToHomeOnDismiss: Bool = false
+        returnToHomeOnDismiss: Bool = false,
+        recordFreeUse: Bool = true
     ) -> Bool {
     ) -> Bool {
         guard require(feature: feature, from: window, returnToHomeOnDismiss: returnToHomeOnDismiss) else {
         guard require(feature: feature, from: window, returnToHomeOnDismiss: returnToHomeOnDismiss) else {
             return false
             return false
         }
         }
-        recordFreeUseIfNeeded()
+        if recordFreeUse {
+            recordFreeUseIfNeeded()
+        }
         return true
         return true
     }
     }
 
 
@@ -1172,12 +1194,13 @@ private final class PaywallPlanCard: NSControl, AppearanceRefreshable {
                     foreground: AppTheme.paywallPinkText
                     foreground: AppTheme.paywallPinkText
                 )
                 )
                 trialBadgeView = badge
                 trialBadgeView = badge
-                addSubview(badge)
+                addSubview(badge, positioned: .above, relativeTo: nil)
                 NSLayoutConstraint.activate([
                 NSLayoutConstraint.activate([
                     badge.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
                     badge.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
                     badge.topAnchor.constraint(equalTo: topAnchor, constant: 8),
                     badge.topAnchor.constraint(equalTo: topAnchor, constant: 8),
                 ])
                 ])
             }
             }
+            trialBadgeView?.layer?.zPosition = 10
         } else {
         } else {
             trialBadgeView?.isHidden = true
             trialBadgeView?.isHidden = true
         }
         }
@@ -2441,14 +2464,8 @@ final class PaywallOverlayView: NSView, AppearanceRefreshable {
         paywallView.refreshStoreState()
         paywallView.refreshStoreState()
 
 
         Task { @MainActor in
         Task { @MainActor in
-            if StoreManager.shared.isResolvingEntitlements {
-                await StoreManager.shared.ensureEntitlementsResolved()
-                paywallView.refreshStoreState()
-            }
-            if !StoreManager.shared.hasAllProductsLoaded, !StoreManager.shared.isLoadingProducts {
-                await StoreManager.shared.loadProducts(presentLoadingUI: true)
-                paywallView.refreshStoreState()
-            }
+            await StoreManager.shared.ensurePaywallStoreDataLoaded()
+            paywallView.refreshStoreState()
             refreshLoadingOverlay()
             refreshLoadingOverlay()
         }
         }
 
 

+ 16 - 6
smart_printer/PrintService.swift

@@ -5,8 +5,16 @@ import UniformTypeIdentifiers
 
 
 enum PrintService {
 enum PrintService {
     @MainActor
     @MainActor
-    private static func requirePremium(_ feature: AppFeature, from window: NSWindow?) -> Bool {
-        PremiumAccess.requireForAction(feature: feature, from: window ?? NSApp.keyWindow)
+    private static func requirePremium(
+        _ feature: AppFeature,
+        from window: NSWindow?,
+        recordFreeUse: Bool = true
+    ) -> Bool {
+        PremiumAccess.requireForAction(
+            feature: feature,
+            from: window ?? NSApp.keyWindow,
+            recordFreeUse: recordFreeUse
+        )
     }
     }
 
 
     static func printContacts(_ contacts: [PrintableContact], from window: NSWindow? = nil) {
     static func printContacts(_ contacts: [PrintableContact], from window: NSWindow? = nil) {
@@ -131,9 +139,10 @@ enum PrintService {
     static func saveParsedScansAsPDF(
     static func saveParsedScansAsPDF(
         pages: [ParsedScanPage],
         pages: [ParsedScanPage],
         defaultName: String = "Scan",
         defaultName: String = "Scan",
-        from window: NSWindow? = nil
+        from window: NSWindow? = nil,
+        recordFreeUse: Bool = true
     ) {
     ) {
-        guard requirePremium(.scannerScan, from: window) else { return }
+        guard requirePremium(.scannerScan, from: window, recordFreeUse: recordFreeUse) else { return }
         guard !pages.isEmpty else {
         guard !pages.isEmpty else {
             showEmptyTextAlert()
             showEmptyTextAlert()
             return
             return
@@ -161,9 +170,10 @@ enum PrintService {
     static func printParsedScans(
     static func printParsedScans(
         pages: [ParsedScanPage],
         pages: [ParsedScanPage],
         title: String = "Scan",
         title: String = "Scan",
-        from window: NSWindow? = nil
+        from window: NSWindow? = nil,
+        recordFreeUse: Bool = true
     ) {
     ) {
-        guard requirePremium(.scannerScan, from: window) else { return }
+        guard requirePremium(.scannerScan, from: window, recordFreeUse: recordFreeUse) else { return }
         guard !pages.isEmpty else {
         guard !pages.isEmpty else {
             showEmptyTextAlert()
             showEmptyTextAlert()
             return
             return

+ 31 - 2
smart_printer/ScanFileView.swift

@@ -1579,6 +1579,7 @@ final class ScanFileOverlayView: NSView, AppearanceRefreshable {
     private let actionRow = NSStackView()
     private let actionRow = NSStackView()
     private let savePDFButton = ScanFileSecondaryButton(title: "Save as PDF", symbolName: "square.and.arrow.down")
     private let savePDFButton = ScanFileSecondaryButton(title: "Save as PDF", symbolName: "square.and.arrow.down")
     private let printButton = ScanFileSecondaryButton(title: "Print", symbolName: "printer.fill")
     private let printButton = ScanFileSecondaryButton(title: "Print", symbolName: "printer.fill")
+    private var hasRecordedFreeUseForSession = false
 
 
     init(pages: [ScanPage]) {
     init(pages: [ScanPage]) {
         self.pages = pages
         self.pages = pages
@@ -1755,7 +1756,21 @@ final class ScanFileOverlayView: NSView, AppearanceRefreshable {
             showNoPagesSelectedAlert()
             showNoPagesSelectedAlert()
             return
             return
         }
         }
-        PrintService.saveParsedScansAsPDF(pages: pages, defaultName: "Scan", from: window)
+        let recordFreeUse = !hasRecordedFreeUseForSession
+        guard PremiumAccess.requireForAction(
+            feature: .scannerScan,
+            from: window,
+            recordFreeUse: recordFreeUse
+        ) else { return }
+        if recordFreeUse {
+            hasRecordedFreeUseForSession = true
+        }
+        PrintService.saveParsedScansAsPDF(
+            pages: pages,
+            defaultName: "Scan",
+            from: window,
+            recordFreeUse: false
+        )
     }
     }
 
 
     private func printScan() {
     private func printScan() {
@@ -1764,7 +1779,21 @@ final class ScanFileOverlayView: NSView, AppearanceRefreshable {
             showNoPagesSelectedAlert()
             showNoPagesSelectedAlert()
             return
             return
         }
         }
-        PrintService.printParsedScans(pages: pages, title: "Scan", from: window)
+        let recordFreeUse = !hasRecordedFreeUseForSession
+        guard PremiumAccess.requireForAction(
+            feature: .scannerScan,
+            from: window,
+            recordFreeUse: recordFreeUse
+        ) else { return }
+        if recordFreeUse {
+            hasRecordedFreeUseForSession = true
+        }
+        PrintService.printParsedScans(
+            pages: pages,
+            title: "Scan",
+            from: window,
+            recordFreeUse: false
+        )
     }
     }
 
 
     private func pagesForExport() -> [ParsedScanPage] {
     private func pagesForExport() -> [ParsedScanPage] {

+ 1 - 25
smart_printer/SettingsView.swift

@@ -123,39 +123,15 @@ final class SettingsView: NSView, AppearanceRefreshable {
             symbolName: "moon.fill",
             symbolName: "moon.fill",
             title: "Dark Mode",
             title: "Dark Mode",
             isOn: AppSettings.darkModeEnabled,
             isOn: AppSettings.darkModeEnabled,
-            isLast: {
-                #if DEBUG
-                !showsFreeTierReset
-                #else
-                true
-                #endif
-            }()
+            isLast: true
         ) { enabled in
         ) { enabled in
             AppSettings.darkModeEnabled = enabled
             AppSettings.darkModeEnabled = enabled
             AppSettings.applyAppearance()
             AppSettings.applyAppearance()
         })
         })
 
 
-        #if DEBUG
-        if showsFreeTierReset {
-            card.addRow(SettingsActionRow(
-                symbolName: "arrow.counterclockwise",
-                title: "Reset Free Uses",
-                isLast: true
-            ) {
-                FreeTierManager.reset()
-            })
-        }
-        #endif
-
         return card
         return card
     }
     }
 
 
-    #if DEBUG
-    private var showsFreeTierReset: Bool {
-        !StoreManager.shared.isPremium
-    }
-    #endif
-
     private func makeAboutCard() -> SettingsGroupCard {
     private func makeAboutCard() -> SettingsGroupCard {
         let card = SettingsGroupCard()
         let card = SettingsGroupCard()
         card.setRows(aboutCardRows())
         card.setRows(aboutCardRows())

+ 4 - 25
smart_printer/ViewController.swift

@@ -31,7 +31,6 @@ class ViewController: NSViewController {
     private var paywallReturnToHomeOnDismiss = true
     private var paywallReturnToHomeOnDismiss = true
 
 
     private var headerView: NSView!
     private var headerView: NSView!
-    private var headerFreeUsesBanner: FreeUsesBannerView!
     private var contentTopBelowHeader: NSLayoutConstraint!
     private var contentTopBelowHeader: NSLayoutConstraint!
     private var contentTopBelowWindow: NSLayoutConstraint!
     private var contentTopBelowWindow: NSLayoutConstraint!
 
 
@@ -119,7 +118,6 @@ class ViewController: NSViewController {
         sidebar?.refreshAppearance()
         sidebar?.refreshAppearance()
         view.refreshAppearanceRecursively()
         view.refreshAppearanceRecursively()
         paywallOverlay?.refreshAppearance()
         paywallOverlay?.refreshAppearance()
-        headerFreeUsesBanner?.refreshAppearance()
         photoPreviewOverlay?.refreshAppearance()
         photoPreviewOverlay?.refreshAppearance()
         filePreviewOverlay?.refreshAppearance()
         filePreviewOverlay?.refreshAppearance()
         printTextOverlay?.refreshAppearance()
         printTextOverlay?.refreshAppearance()
@@ -388,7 +386,7 @@ class ViewController: NSViewController {
             self?.showDestination(destination)
             self?.showDestination(destination)
         }
         }
         sidebar.onPremiumUpgradeTapped = { [weak self] in
         sidebar.onPremiumUpgradeTapped = { [weak self] in
-            self?.showDestination(.scanAndHome)
+            self?.presentPaywall(returnToHomeOnDismiss: false)
         }
         }
         sidebar.onManageSubscriptionTapped = {
         sidebar.onManageSubscriptionTapped = {
             StoreManager.shared.showManageSubscriptions()
             StoreManager.shared.showManageSubscriptions()
@@ -540,7 +538,7 @@ class ViewController: NSViewController {
                 self?.handlePaywallDismiss()
                 self?.handlePaywallDismiss()
             }
             }
             Task { @MainActor in
             Task { @MainActor in
-                await StoreManager.shared.ensureEntitlementsResolved()
+                await StoreManager.shared.ensurePaywallStoreDataLoaded()
                 paywallOverlay.refreshStoreState()
                 paywallOverlay.refreshStoreState()
             }
             }
             return
             return
@@ -653,11 +651,9 @@ class ViewController: NSViewController {
         documentView.translatesAutoresizingMaskIntoConstraints = false
         documentView.translatesAutoresizingMaskIntoConstraints = false
 
 
         let sectionTitle = makeSectionTitle(title, icon: .grid)
         let sectionTitle = makeSectionTitle(title, icon: .grid)
-        let freeUsesBanner = FreeUsesBannerView()
         let grid = makeFeatureGrid(features: features, columns: AppTheme.featureGridColumns)
         let grid = makeFeatureGrid(features: features, columns: AppTheme.featureGridColumns)
 
 
         documentView.addSubview(sectionTitle)
         documentView.addSubview(sectionTitle)
-        documentView.addSubview(freeUsesBanner)
         documentView.addSubview(grid)
         documentView.addSubview(grid)
         scrollView.documentView = documentView
         scrollView.documentView = documentView
         container.addSubview(scrollView)
         container.addSubview(scrollView)
@@ -679,13 +675,9 @@ class ViewController: NSViewController {
             sectionTitle.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
             sectionTitle.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
             sectionTitle.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 8),
             sectionTitle.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 8),
 
 
-            freeUsesBanner.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
-            freeUsesBanner.trailingAnchor.constraint(lessThanOrEqualTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
-            freeUsesBanner.topAnchor.constraint(equalTo: sectionTitle.bottomAnchor, constant: 12),
-
             grid.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
             grid.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
             grid.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
             grid.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
-            grid.topAnchor.constraint(equalTo: freeUsesBanner.bottomAnchor, constant: 16),
+            grid.topAnchor.constraint(equalTo: sectionTitle.bottomAnchor, constant: 16),
             grid.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -40),
             grid.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -40),
         ])
         ])
 
 
@@ -700,18 +692,11 @@ class ViewController: NSViewController {
         titleLabel.alignment = .center
         titleLabel.alignment = .center
         titleLabel.translatesAutoresizingMaskIntoConstraints = false
         titleLabel.translatesAutoresizingMaskIntoConstraints = false
 
 
-        headerFreeUsesBanner = FreeUsesBannerView()
-        headerFreeUsesBanner.translatesAutoresizingMaskIntoConstraints = false
-
         header.addSubview(titleLabel)
         header.addSubview(titleLabel)
-        header.addSubview(headerFreeUsesBanner)
 
 
         NSLayoutConstraint.activate([
         NSLayoutConstraint.activate([
             titleLabel.centerXAnchor.constraint(equalTo: header.centerXAnchor),
             titleLabel.centerXAnchor.constraint(equalTo: header.centerXAnchor),
             titleLabel.centerYAnchor.constraint(equalTo: header.centerYAnchor),
             titleLabel.centerYAnchor.constraint(equalTo: header.centerYAnchor),
-
-            headerFreeUsesBanner.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -AppTheme.contentPadding),
-            headerFreeUsesBanner.centerYAnchor.constraint(equalTo: header.centerYAnchor),
         ])
         ])
 
 
         return header
         return header
@@ -754,11 +739,9 @@ class ViewController: NSViewController {
         let documentView = FlippedDocumentView()
         let documentView = FlippedDocumentView()
         documentView.translatesAutoresizingMaskIntoConstraints = false
         documentView.translatesAutoresizingMaskIntoConstraints = false
 
 
-        let freeUsesBanner = FreeUsesBannerView()
         let quickStartSection = makeQuickStartSection()
         let quickStartSection = makeQuickStartSection()
         let createPrintSection = makeCreatePrintSection()
         let createPrintSection = makeCreatePrintSection()
 
 
-        documentView.addSubview(freeUsesBanner)
         documentView.addSubview(quickStartSection)
         documentView.addSubview(quickStartSection)
         documentView.addSubview(createPrintSection)
         documentView.addSubview(createPrintSection)
         scrollView.documentView = documentView
         scrollView.documentView = documentView
@@ -771,13 +754,9 @@ class ViewController: NSViewController {
             documentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
             documentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
             documentView.widthAnchor.constraint(equalTo: contentGuide.widthAnchor),
             documentView.widthAnchor.constraint(equalTo: contentGuide.widthAnchor),
 
 
-            freeUsesBanner.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
-            freeUsesBanner.trailingAnchor.constraint(lessThanOrEqualTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
-            freeUsesBanner.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 12),
-
             quickStartSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
             quickStartSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
             quickStartSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
             quickStartSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
-            quickStartSection.topAnchor.constraint(equalTo: freeUsesBanner.bottomAnchor, constant: 16),
+            quickStartSection.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 12),
 
 
             createPrintSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
             createPrintSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
             createPrintSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
             createPrintSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),