Browse Source

Add a close button to the premium paywall.

Give pro users a round bordered dismiss control with hover feedback so they can close the subscription page without using Continue with free plan.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 3 hours ago
parent
commit
0f234ce589
1 changed files with 75 additions and 0 deletions
  1. 75 0
      smart_printer/PaywallView.swift

+ 75 - 0
smart_printer/PaywallView.swift

@@ -748,6 +748,65 @@ private final class PaywallTrustItemView: NSView, AppearanceRefreshable {
748 748
     }
749 749
 }
750 750
 
751
+// MARK: - Close Button
752
+
753
+private final class PaywallCloseButton: NSButton, AppearanceRefreshable {
754
+    private var hoverTracker: HoverTracker?
755
+    private var isHovered = false
756
+
757
+    init() {
758
+        super.init(frame: .zero)
759
+        isBordered = false
760
+        bezelStyle = .inline
761
+        translatesAutoresizingMaskIntoConstraints = false
762
+        wantsLayer = true
763
+        layer?.cornerRadius = 15
764
+        layer?.borderWidth = 1.5
765
+        if let image = NSImage(systemSymbolName: "xmark", accessibilityDescription: "Close") {
766
+            let config = NSImage.SymbolConfiguration(pointSize: 12, weight: .semibold)
767
+            self.image = image.withSymbolConfiguration(config)
768
+        }
769
+        refreshAppearance()
770
+
771
+        hoverTracker = HoverTracker(view: self) { [weak self] hovering in
772
+            self?.setHovered(hovering)
773
+        }
774
+    }
775
+
776
+    @available(*, unavailable)
777
+    required init?(coder: NSCoder) { nil }
778
+
779
+    func refreshAppearance() {
780
+        updateAppearance()
781
+    }
782
+
783
+    private func setHovered(_ hovering: Bool) {
784
+        isHovered = hovering
785
+        animateHover {
786
+            updateAppearance()
787
+            layer?.transform = hovering
788
+                ? CATransform3DMakeScale(1.06, 1.06, 1)
789
+                : CATransform3DIdentity
790
+        }
791
+    }
792
+
793
+    private func updateAppearance() {
794
+        if isHovered {
795
+            layer?.backgroundColor = AppTheme.elevatedBackground.cgColor
796
+            layer?.borderColor = AppTheme.paywallAccent.withAlphaComponent(0.45).cgColor
797
+            contentTintColor = AppTheme.textPrimary
798
+        } else {
799
+            layer?.backgroundColor = AppTheme.paywallTrustBackground.cgColor
800
+            layer?.borderColor = AppTheme.paywallBorder.cgColor
801
+            contentTintColor = AppTheme.textSecondary
802
+        }
803
+    }
804
+
805
+    override func resetCursorRects() {
806
+        addCursorRect(bounds, cursor: .pointingHand)
807
+    }
808
+}
809
+
751 810
 // MARK: - CTA Button
752 811
 
753 812
 private final class PaywallCTAButton: NSButton, AppearanceRefreshable {
@@ -802,6 +861,7 @@ final class PaywallView: NSView, AppearanceRefreshable {
802 861
     private let ctaButton = PaywallCTAButton()
803 862
     private let continueFreePlanLink = PaywallFooterLink(title: "Continue with free plan")
804 863
     private let manageSubscriptionLink = PaywallFooterLink(title: "Manage Subscription")
864
+    private let premiumCloseButton = PaywallCloseButton()
805 865
     private var primaryFooterLinkCell: NSView?
806 866
     private var leftPanelTitle: NSTextField!
807 867
     private var rightTitle: NSTextField!
@@ -860,6 +920,7 @@ final class PaywallView: NSView, AppearanceRefreshable {
860 920
         let isPro = StoreManager.shared.isPro
861 921
         continueFreePlanLink.isHidden = isPro
862 922
         manageSubscriptionLink.isHidden = !isPro
923
+        premiumCloseButton.isHidden = !isPro
863 924
         primaryFooterLinkCell?.needsLayout = true
864 925
         primaryFooterLinkCell?.layoutSubtreeIfNeeded()
865 926
     }
@@ -886,8 +947,13 @@ final class PaywallView: NSView, AppearanceRefreshable {
886 947
         let leftPanel = makeLeftPanel()
887 948
         let rightPanel = makeRightPanel()
888 949
 
950
+        premiumCloseButton.target = self
951
+        premiumCloseButton.action = #selector(premiumCloseTapped)
952
+        premiumCloseButton.isHidden = true
953
+
889 954
         addSubview(leftPanel)
890 955
         addSubview(rightPanel)
956
+        addSubview(premiumCloseButton)
891 957
 
892 958
         NSLayoutConstraint.activate([
893 959
             leftPanel.leadingAnchor.constraint(equalTo: leadingAnchor),
@@ -899,6 +965,11 @@ final class PaywallView: NSView, AppearanceRefreshable {
899 965
             rightPanel.trailingAnchor.constraint(equalTo: trailingAnchor),
900 966
             rightPanel.topAnchor.constraint(equalTo: topAnchor),
901 967
             rightPanel.bottomAnchor.constraint(equalTo: bottomAnchor),
968
+
969
+            premiumCloseButton.topAnchor.constraint(equalTo: topAnchor, constant: 16),
970
+            premiumCloseButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
971
+            premiumCloseButton.widthAnchor.constraint(equalToConstant: 30),
972
+            premiumCloseButton.heightAnchor.constraint(equalToConstant: 30),
902 973
         ])
903 974
     }
904 975
 
@@ -1195,6 +1266,10 @@ final class PaywallView: NSView, AppearanceRefreshable {
1195 1266
         onClose?()
1196 1267
     }
1197 1268
 
1269
+    @objc private func premiumCloseTapped() {
1270
+        onClose?()
1271
+    }
1272
+
1198 1273
     @objc private func manageSubscriptionTapped() {
1199 1274
         StoreManager.shared.showManageSubscriptions()
1200 1275
     }