Explorar el Código

Polish paywall layout and footer.

Make the paywall content responsive with full-width plan cards, adjust top padding, increase plan card height, and center a single-line footer with free-plan action.

Co-authored-by: Cursor <cursoragent@cursor.com>
huzaifahayat12 hace 2 meses
padre
commit
09e32b708e
Se han modificado 1 ficheros con 94 adiciones y 58 borrados
  1. 94 58
      zoom_app/ViewController.swift

+ 94 - 58
zoom_app/ViewController.swift

@@ -610,6 +610,7 @@ class ViewController: NSViewController {
         super.viewDidLayout()
         alignNativeTrafficLights()
         if paywallOverlayView != nil {
+            updatePaywallPanelSize()
             updatePaywallResponsiveLayout()
         }
     }
@@ -3697,15 +3698,15 @@ class ViewController: NSViewController {
         styleSurface(panel, borderColor: NSColor.clear, borderWidth: 0, shadow: false)
         root.addSubview(panel)
 
-        // Full-screen: fill the entire app content area.
-        let insetX: CGFloat = 0
-        let insetY: CGFloat = 0
+        // Full-screen paywall: fill the entire app content area.
         NSLayoutConstraint.activate([
-            panel.leadingAnchor.constraint(equalTo: root.leadingAnchor, constant: insetX),
-            panel.trailingAnchor.constraint(equalTo: root.trailingAnchor, constant: -insetX),
-            panel.topAnchor.constraint(equalTo: root.topAnchor, constant: insetY),
-            panel.bottomAnchor.constraint(equalTo: root.bottomAnchor, constant: -insetY)
+            panel.leadingAnchor.constraint(equalTo: root.leadingAnchor),
+            panel.trailingAnchor.constraint(equalTo: root.trailingAnchor),
+            panel.topAnchor.constraint(equalTo: root.topAnchor),
+            panel.bottomAnchor.constraint(equalTo: root.bottomAnchor)
         ])
+        paywallPanelWidthConstraint = nil
+        paywallPanelHeightConstraint = nil
 
         // Responsive logic should measure the panel size (not the full-screen root).
         self.paywallPanelView = panel
@@ -3723,7 +3724,7 @@ class ViewController: NSViewController {
         topRow.alignment = .centerY
         topRow.distribution = .fill
         topRow.spacing = 10
-        topRow.addArrangedSubview(textLabel("Get Premium", font: NSFont.systemFont(ofSize: 24, weight: .bold), color: primaryText))
+        topRow.addArrangedSubview(textLabel("Meetings Pro", font: NSFont.systemFont(ofSize: 24, weight: .bold), color: primaryText))
         let topSpacer = NSView()
         topSpacer.translatesAutoresizingMaskIntoConstraints = false
         topRow.addArrangedSubview(topSpacer)
@@ -3743,18 +3744,20 @@ class ViewController: NSViewController {
         closeButton.widthAnchor.constraint(equalToConstant: 28).isActive = true
         closeButton.heightAnchor.constraint(equalToConstant: 28).isActive = true
         topRow.addArrangedSubview(closeButton)
-        topRow.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth).isActive = true
         contentStack.addArrangedSubview(topRow)
+        topRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
 
         let hero = paywallHeroCard(
             eyebrow: "PREMIUM EXPERIENCE",
-            title: "Elevate your classroom experience",
-            subtitle: "Unlock intelligent automation, deep analytics, and premium support for modern educators."
+            title: "Elevate your meetings workflow",
+            subtitle: "Unlock automation, premium meeting tools, and priority support for every session."
         )
         contentStack.addArrangedSubview(hero)
+        hero.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
 
         let features = paywallFeaturesRow()
         contentStack.addArrangedSubview(features)
+        features.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
 
         let bodyStack = NSStackView()
         bodyStack.translatesAutoresizingMaskIntoConstraints = false
@@ -3764,6 +3767,7 @@ class ViewController: NSViewController {
         bodyStack.distribution = .fill
         contentStack.addArrangedSubview(bodyStack)
         paywallBodyStack = bodyStack
+        bodyStack.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
 
         let planColumn = NSStackView()
         planColumn.translatesAutoresizingMaskIntoConstraints = false
@@ -3772,11 +3776,12 @@ class ViewController: NSViewController {
         planColumn.alignment = .leading
         planColumn.distribution = .fill
         bodyStack.addArrangedSubview(planColumn)
+        planColumn.widthAnchor.constraint(equalTo: bodyStack.widthAnchor).isActive = true
 
         let weeklyCard = paywallPlanCard(
             title: "Weekly",
             price: paywallDisplayPrice(for: .weekly),
-            badge: "Basic Deal",
+            badge: "Basic",
             badgeColor: NSColor(calibratedRed: 1.0, green: 0.60, blue: 0.20, alpha: 1),
             subtitle: "Billed weekly",
             plan: .weekly,
@@ -3785,25 +3790,25 @@ class ViewController: NSViewController {
         let monthlyCard = paywallPlanCard(
             title: "Monthly",
             price: paywallDisplayPrice(for: .monthly),
-            badge: "Free Trial",
+            badge: "Popular",
             badgeColor: NSColor(calibratedRed: 0.19, green: 0.82, blue: 0.39, alpha: 1),
-            subtitle: "7-day free trial, then monthly",
+            subtitle: "Billed monthly",
             plan: .monthly,
             strikePrice: nil
         )
         let yearlyCard = paywallPlanCard(
             title: "Yearly",
             price: paywallDisplayPrice(for: .yearly),
-            badge: "Best Deal",
+            badge: "Save 67%",
             badgeColor: NSColor(calibratedRed: 1.0, green: 0.60, blue: 0.20, alpha: 1),
-            subtitle: "Billed yearly",
+            subtitle: "Billed yearly • 3 days free trial",
             plan: .yearly,
             strikePrice: nil
         )
         let lifetimeCard = paywallPlanCard(
             title: "Lifetime",
             price: paywallDisplayPrice(for: .lifetime),
-            badge: "Save 50%",
+            badge: "Best Value",
             badgeColor: NSColor(calibratedRed: 1.0, green: 0.60, blue: 0.20, alpha: 1),
             subtitle: "One-time purchase",
             plan: .lifetime,
@@ -3822,6 +3827,7 @@ class ViewController: NSViewController {
         plansRowWide.addArrangedSubview(lifetimeCard)
         paywallPlansRowWide = plansRowWide
         planColumn.addArrangedSubview(plansRowWide)
+        plansRowWide.widthAnchor.constraint(equalTo: planColumn.widthAnchor).isActive = true
 
         let plansGridNarrow = NSStackView()
         plansGridNarrow.translatesAutoresizingMaskIntoConstraints = false
@@ -3885,6 +3891,9 @@ class ViewController: NSViewController {
         plansGridNarrow.isHidden = true
         paywallPlansGridNarrow = plansGridNarrow
         planColumn.addArrangedSubview(plansGridNarrow)
+        plansGridNarrow.widthAnchor.constraint(equalTo: planColumn.widthAnchor).isActive = true
+        plansRow1.widthAnchor.constraint(equalTo: plansGridNarrow.widthAnchor).isActive = true
+        plansRow2.widthAnchor.constraint(equalTo: plansGridNarrow.widthAnchor).isActive = true
 
         updatePaywallPlanSelection()
         planColumn.setCustomSpacing(16, after: plansGridNarrow)
@@ -3896,16 +3905,17 @@ class ViewController: NSViewController {
         offerWrap.translatesAutoresizingMaskIntoConstraints = false
         offerWrap.addSubview(offer)
         NSLayoutConstraint.activate([
-            offerWrap.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth),
             offer.centerXAnchor.constraint(equalTo: offerWrap.centerXAnchor),
             offer.topAnchor.constraint(equalTo: offerWrap.topAnchor, constant: 6),
             offer.bottomAnchor.constraint(equalTo: offerWrap.bottomAnchor, constant: -2)
         ])
         planColumn.addArrangedSubview(offerWrap)
+        offerWrap.widthAnchor.constraint(equalTo: planColumn.widthAnchor).isActive = true
         planColumn.setCustomSpacing(14, after: offerWrap)
 
         let trustRow = paywallTrustRow()
         planColumn.addArrangedSubview(trustRow)
+        trustRow.widthAnchor.constraint(equalTo: planColumn.widthAnchor).isActive = true
         planColumn.setCustomSpacing(14, after: trustRow)
 
         let continueButton = HoverButton(title: "", target: self, action: #selector(paywallContinueClicked(_:)))
@@ -3914,10 +3924,9 @@ class ViewController: NSViewController {
         continueButton.bezelStyle = .regularSquare
         continueButton.wantsLayer = true
         continueButton.layer?.cornerRadius = 14
-        continueButton.normalColor = NSColor(calibratedRed: 0.0, green: 0.70, blue: 0.42, alpha: 1)
-        continueButton.hoverColor = NSColor(calibratedRed: 0.0, green: 0.70, blue: 0.42, alpha: 0.92)
+        continueButton.normalColor = NSColor(calibratedRed: 0.16, green: 0.49, blue: 0.98, alpha: 1)
+        continueButton.hoverColor = NSColor(calibratedRed: 0.16, green: 0.49, blue: 0.98, alpha: 0.92)
         continueButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
-        continueButton.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth).isActive = true
         styleSurface(continueButton, borderColor: NSColor.black.withAlphaComponent(0.12), borderWidth: 1, shadow: true)
         let continueLabel = textLabel("Continue", font: NSFont.systemFont(ofSize: 16, weight: .bold), color: .white)
         continueButton.addSubview(continueLabel)
@@ -3928,6 +3937,7 @@ class ViewController: NSViewController {
         paywallContinueButton = continueButton
         paywallContinueLabel = continueLabel
         planColumn.addArrangedSubview(continueButton)
+        continueButton.widthAnchor.constraint(equalTo: planColumn.widthAnchor).isActive = true
         planColumn.setCustomSpacing(16, after: continueButton)
 
         let secure = textLabel("Secured by Apple. Cancel anytime.", font: NSFont.systemFont(ofSize: 12, weight: .semibold), color: secondaryText)
@@ -3936,28 +3946,14 @@ class ViewController: NSViewController {
         secureWrap.translatesAutoresizingMaskIntoConstraints = false
         secureWrap.addSubview(secure)
         NSLayoutConstraint.activate([
-            secureWrap.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth),
             secure.centerXAnchor.constraint(equalTo: secureWrap.centerXAnchor),
             secure.topAnchor.constraint(equalTo: secureWrap.topAnchor, constant: 4),
             secure.bottomAnchor.constraint(equalTo: secureWrap.bottomAnchor, constant: -8)
         ])
         planColumn.addArrangedSubview(secureWrap)
+        secureWrap.widthAnchor.constraint(equalTo: planColumn.widthAnchor).isActive = true
         planColumn.setCustomSpacing(8, after: secureWrap)
 
-        let footerButtons = NSStackView()
-        footerButtons.translatesAutoresizingMaskIntoConstraints = false
-        footerButtons.orientation = .horizontal
-        footerButtons.alignment = .centerY
-        footerButtons.distribution = .fill
-        footerButtons.spacing = 8
-        footerButtons.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth).isActive = true
-
-        let leftFooter = NSStackView()
-        leftFooter.translatesAutoresizingMaskIntoConstraints = false
-        leftFooter.orientation = .horizontal
-        leftFooter.alignment = .centerY
-        leftFooter.spacing = 14
-
         let manageButton = paywallFooterButton(
             title: "Manage Subscription",
             action: #selector(paywallManageSubscriptionClicked(_:))
@@ -3966,12 +3962,11 @@ class ViewController: NSViewController {
             title: "Restore Purchase",
             action: #selector(paywallRestorePurchaseClicked(_:))
         )
-        leftFooter.addArrangedSubview(manageButton)
-        leftFooter.addArrangedSubview(restoreButton)
-
-        let footerSpacer = NSView()
-        footerSpacer.translatesAutoresizingMaskIntoConstraints = false
-
+        let freePlanButton = paywallFooterButton(
+            title: "Continue with free plan",
+            action: #selector(closePaywallClicked(_:))
+        )
+        freePlanButton.contentTintColor = secondaryText
         let privacyButton = paywallFooterButton(
             title: "Privacy Policy",
             action: #selector(paywallPrivacyPolicyClicked(_:))
@@ -3984,18 +3979,59 @@ class ViewController: NSViewController {
             title: "Terms of Services",
             action: #selector(paywallTermsOfServicesClicked(_:))
         )
-        footerButtons.addArrangedSubview(leftFooter)
-        footerButtons.addArrangedSubview(footerSpacer)
-        footerButtons.addArrangedSubview(privacyButton)
-        footerButtons.addArrangedSubview(supportButton)
-        footerButtons.addArrangedSubview(termsButton)
-        planColumn.addArrangedSubview(footerButtons)
 
+        // Footer: single centered line.
+        let footerContent = NSStackView()
+        footerContent.translatesAutoresizingMaskIntoConstraints = false
+        footerContent.orientation = .horizontal
+        footerContent.alignment = .centerY
+        footerContent.distribution = .fill
+        footerContent.spacing = 16
+        footerContent.addArrangedSubview(manageButton)
+        footerContent.addArrangedSubview(restoreButton)
+        footerContent.addArrangedSubview(freePlanButton)
+        footerContent.addArrangedSubview(privacyButton)
+        footerContent.addArrangedSubview(supportButton)
+        footerContent.addArrangedSubview(termsButton)
+
+        let footerWrap = NSStackView()
+        footerWrap.translatesAutoresizingMaskIntoConstraints = false
+        footerWrap.orientation = .horizontal
+        footerWrap.alignment = .centerY
+        footerWrap.distribution = .fill
+
+        let footerLeftSpacer = NSView()
+        footerLeftSpacer.translatesAutoresizingMaskIntoConstraints = false
+        let footerRightSpacer = NSView()
+        footerRightSpacer.translatesAutoresizingMaskIntoConstraints = false
+        footerLeftSpacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
+        footerLeftSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+        footerRightSpacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
+        footerRightSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
+        footerContent.setContentHuggingPriority(.required, for: .horizontal)
+        footerContent.setContentCompressionResistancePriority(.required, for: .horizontal)
+
+        footerWrap.addArrangedSubview(footerLeftSpacer)
+        footerWrap.addArrangedSubview(footerContent)
+        footerWrap.addArrangedSubview(footerRightSpacer)
+
+        // Push footer to the bottom of the paywall.
+        let footerPushSpacer = NSView()
+        footerPushSpacer.translatesAutoresizingMaskIntoConstraints = false
+        footerPushSpacer.setContentHuggingPriority(.defaultLow, for: .vertical)
+        footerPushSpacer.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
+        planColumn.addArrangedSubview(footerPushSpacer)
+        planColumn.addArrangedSubview(footerWrap)
+        footerWrap.widthAnchor.constraint(equalTo: planColumn.widthAnchor).isActive = true
+
+        let contentBottomEq = contentStack.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -18)
+        contentBottomEq.priority = .defaultLow
         NSLayoutConstraint.activate([
-            contentStack.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
-            contentStack.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
-            contentStack.topAnchor.constraint(equalTo: panel.topAnchor, constant: 16),
-            contentStack.bottomAnchor.constraint(lessThanOrEqualTo: panel.bottomAnchor, constant: -16)
+            contentStack.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 22),
+            contentStack.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -22),
+            contentStack.topAnchor.constraint(equalTo: panel.topAnchor, constant: 56),
+            contentStack.bottomAnchor.constraint(lessThanOrEqualTo: panel.bottomAnchor, constant: -18),
+            contentBottomEq
         ])
 
         refreshPaywallStoreUI()
@@ -4081,8 +4117,8 @@ class ViewController: NSViewController {
         row.alignment = .centerY
         row.distribution = .fillEqually
 
-        row.addArrangedSubview(paywallBenefitItem(icon: "📅", text: "Manage classes"))
-        row.addArrangedSubview(paywallBenefitItem(icon: "💬", text: "Smart to-dos"))
+        row.addArrangedSubview(paywallBenefitItem(icon: "📅", text: "Manage meetings"))
+        row.addArrangedSubview(paywallBenefitItem(icon: "💬", text: "Smart scheduling"))
         row.addArrangedSubview(paywallBenefitItem(icon: "⚡", text: "Faster workflow"))
         row.addArrangedSubview(paywallBenefitItem(icon: "🔔", text: "Reminder notifications"))
         return row
@@ -4183,7 +4219,7 @@ class ViewController: NSViewController {
         wrapper.bezelStyle = .regularSquare
         wrapper.wantsLayer = true
         wrapper.layer?.backgroundColor = NSColor.clear.cgColor
-        wrapper.heightAnchor.constraint(equalToConstant: 122).isActive = true
+        wrapper.heightAnchor.constraint(equalToConstant: 138).isActive = true
         wrapper.tag = PremiumPlan.allCases.firstIndex(of: plan) ?? 0
 
         let card = NSView()
@@ -4195,7 +4231,7 @@ class ViewController: NSViewController {
         NSLayoutConstraint.activate([
             card.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
             card.trailingAnchor.constraint(equalTo: wrapper.trailingAnchor),
-            card.topAnchor.constraint(equalTo: wrapper.topAnchor, constant: 12),
+            card.topAnchor.constraint(equalTo: wrapper.topAnchor, constant: 14),
             card.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor)
         ])
         styleSurface(card, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
@@ -4288,8 +4324,8 @@ class ViewController: NSViewController {
         let price = paywallDisplayPrice(for: plan)
         switch plan {
         case .weekly: return "\(price)/week"
-        case .monthly: return "7-day free trial, then \(price)/month"
-        case .yearly: return "\(price)/year"
+        case .monthly: return "\(price)/month"
+        case .yearly: return "Free for 3 days, then \(price)/year"
         case .lifetime: return "\(price) one-time purchase"
         }
     }