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 9 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
 // MARK: - CTA Button
810
 // MARK: - CTA Button
752
 
811
 
753
 private final class PaywallCTAButton: NSButton, AppearanceRefreshable {
812
 private final class PaywallCTAButton: NSButton, AppearanceRefreshable {
@@ -802,6 +861,7 @@ final class PaywallView: NSView, AppearanceRefreshable {
802
     private let ctaButton = PaywallCTAButton()
861
     private let ctaButton = PaywallCTAButton()
803
     private let continueFreePlanLink = PaywallFooterLink(title: "Continue with free plan")
862
     private let continueFreePlanLink = PaywallFooterLink(title: "Continue with free plan")
804
     private let manageSubscriptionLink = PaywallFooterLink(title: "Manage Subscription")
863
     private let manageSubscriptionLink = PaywallFooterLink(title: "Manage Subscription")
864
+    private let premiumCloseButton = PaywallCloseButton()
805
     private var primaryFooterLinkCell: NSView?
865
     private var primaryFooterLinkCell: NSView?
806
     private var leftPanelTitle: NSTextField!
866
     private var leftPanelTitle: NSTextField!
807
     private var rightTitle: NSTextField!
867
     private var rightTitle: NSTextField!
@@ -860,6 +920,7 @@ final class PaywallView: NSView, AppearanceRefreshable {
860
         let isPro = StoreManager.shared.isPro
920
         let isPro = StoreManager.shared.isPro
861
         continueFreePlanLink.isHidden = isPro
921
         continueFreePlanLink.isHidden = isPro
862
         manageSubscriptionLink.isHidden = !isPro
922
         manageSubscriptionLink.isHidden = !isPro
923
+        premiumCloseButton.isHidden = !isPro
863
         primaryFooterLinkCell?.needsLayout = true
924
         primaryFooterLinkCell?.needsLayout = true
864
         primaryFooterLinkCell?.layoutSubtreeIfNeeded()
925
         primaryFooterLinkCell?.layoutSubtreeIfNeeded()
865
     }
926
     }
@@ -886,8 +947,13 @@ final class PaywallView: NSView, AppearanceRefreshable {
886
         let leftPanel = makeLeftPanel()
947
         let leftPanel = makeLeftPanel()
887
         let rightPanel = makeRightPanel()
948
         let rightPanel = makeRightPanel()
888
 
949
 
950
+        premiumCloseButton.target = self
951
+        premiumCloseButton.action = #selector(premiumCloseTapped)
952
+        premiumCloseButton.isHidden = true
953
+
889
         addSubview(leftPanel)
954
         addSubview(leftPanel)
890
         addSubview(rightPanel)
955
         addSubview(rightPanel)
956
+        addSubview(premiumCloseButton)
891
 
957
 
892
         NSLayoutConstraint.activate([
958
         NSLayoutConstraint.activate([
893
             leftPanel.leadingAnchor.constraint(equalTo: leadingAnchor),
959
             leftPanel.leadingAnchor.constraint(equalTo: leadingAnchor),
@@ -899,6 +965,11 @@ final class PaywallView: NSView, AppearanceRefreshable {
899
             rightPanel.trailingAnchor.constraint(equalTo: trailingAnchor),
965
             rightPanel.trailingAnchor.constraint(equalTo: trailingAnchor),
900
             rightPanel.topAnchor.constraint(equalTo: topAnchor),
966
             rightPanel.topAnchor.constraint(equalTo: topAnchor),
901
             rightPanel.bottomAnchor.constraint(equalTo: bottomAnchor),
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
         onClose?()
1266
         onClose?()
1196
     }
1267
     }
1197
 
1268
 
1269
+    @objc private func premiumCloseTapped() {
1270
+        onClose?()
1271
+    }
1272
+
1198
     @objc private func manageSubscriptionTapped() {
1273
     @objc private func manageSubscriptionTapped() {
1199
         StoreManager.shared.showManageSubscriptions()
1274
         StoreManager.shared.showManageSubscriptions()
1200
     }
1275
     }