Преглед на файлове

Apply dark theme to the premium subscription paywall.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 преди 2 месеца
родител
ревизия
c57ef41737
променени са 1 файла, в които са добавени 221 реда и са изтрити 21 реда
  1. 221 21
      App for Indeed/Controllers/PremiumPlansWindowController.swift

+ 221 - 21
App for Indeed/Controllers/PremiumPlansWindowController.swift

@@ -3,7 +3,7 @@ import StoreKit
 
 final class PremiumPlansWindowController: NSWindowController {
     /// Matches `PremiumPlansViewController.Theme.pageStart` so the window backing fills sheet corners.
-    static let paywallSheetBackground = NSColor(srgbRed: 249 / 255, green: 252 / 255, blue: 255 / 255, alpha: 1)
+    static var paywallSheetBackground: NSColor { PremiumPlansViewController.Theme.pageStart }
 
     init() {
         let viewController = PremiumPlansViewController()
@@ -29,9 +29,10 @@ final class PremiumPlansWindowController: NSWindowController {
 
 private final class PremiumPlansViewController: NSViewController {
     private final class HoverPricingCardView: NSView {
-        private let baseBorderColor: NSColor
-        private let hoverBorderColor: NSColor
+        private var baseBorderColor: NSColor
+        private var hoverBorderColor: NSColor
         private var trackingAreaRef: NSTrackingArea?
+        private var isHovered = false
 
         init(baseBorderColor: NSColor, hoverBorderColor: NSColor) {
             self.baseBorderColor = baseBorderColor
@@ -60,14 +61,22 @@ private final class PremiumPlansViewController: NSViewController {
 
         override func mouseEntered(with event: NSEvent) {
             super.mouseEntered(with: event)
+            isHovered = true
             applyHoverStyle(isHovered: true, animated: true)
         }
 
         override func mouseExited(with event: NSEvent) {
             super.mouseExited(with: event)
+            isHovered = false
             applyHoverStyle(isHovered: false, animated: true)
         }
 
+        func updateBorderColors(base: NSColor, hover: NSColor) {
+            baseBorderColor = base
+            hoverBorderColor = hover
+            applyHoverStyle(isHovered: isHovered, animated: false)
+        }
+
         private func applyHoverStyle(isHovered: Bool, animated: Bool) {
             guard let layer else { return }
             let updates = {
@@ -232,6 +241,10 @@ private final class PremiumPlansViewController: NSViewController {
             }
         }
 
+        func refreshAppearance() {
+            applyBaseStyle(hovered: false, animated: false)
+        }
+
         private func applyBaseStyle(hovered: Bool, animated: Bool = true) {
             let updates = {
                 if self.isPrimaryStyle {
@@ -332,11 +345,15 @@ private final class PremiumPlansViewController: NSViewController {
             }
         }
 
+        func refreshAppearance(hovered: Bool) {
+            applyStyle(hovered: hovered, animated: false)
+        }
+
         private func applyStyle(hovered: Bool, animated: Bool) {
             let updates = {
                 self.layer?.backgroundColor = (hovered
-                    ? NSColor.white.withAlphaComponent(0.98)
-                    : NSColor.white.withAlphaComponent(0.92)).cgColor
+                    ? Theme.closeButtonBackgroundHover
+                    : Theme.closeButtonBackground).cgColor
                 self.layer?.borderColor = (hovered ? Theme.accent.withAlphaComponent(0.45) : Theme.divider).cgColor
                 self.layer?.borderWidth = hovered ? 1.5 : 1
                 self.contentTintColor = hovered ? Theme.accent : Theme.secondaryText
@@ -374,20 +391,95 @@ private final class PremiumPlansViewController: NSViewController {
         let highlight: Bool
     }
 
-    private enum Theme {
-        static let pageStart = NSColor(srgbRed: 249 / 255, green: 252 / 255, blue: 255 / 255, alpha: 1)
-        static let pageEnd = NSColor(srgbRed: 238 / 255, green: 244 / 255, blue: 255 / 255, alpha: 1)
-        static let cardBackground = NSColor.white
-        static let primaryText = NSColor(srgbRed: 27 / 255, green: 38 / 255, blue: 79 / 255, alpha: 1)
-        static let secondaryText = NSColor(srgbRed: 108 / 255, green: 120 / 255, blue: 157 / 255, alpha: 1)
-        static let cardBorder = NSColor(srgbRed: 198 / 255, green: 216 / 255, blue: 255 / 255, alpha: 1)
-        static let accent = NSColor(srgbRed: 55 / 255, green: 128 / 255, blue: 255 / 255, alpha: 1)
-        static let accentHover = NSColor(srgbRed: 38 / 255, green: 108 / 255, blue: 232 / 255, alpha: 1)
-        static let mutedButtonFill = NSColor(srgbRed: 238 / 255, green: 243 / 255, blue: 252 / 255, alpha: 1)
-        static let bottomStrip = NSColor(srgbRed: 244 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1)
-        static let divider = NSColor(srgbRed: 218 / 255, green: 228 / 255, blue: 247 / 255, alpha: 1)
-        static let successText = NSColor(srgbRed: 21 / 255, green: 154 / 255, blue: 220 / 255, alpha: 1)
-        static let iconTint = NSColor(srgbRed: 47 / 255, green: 136 / 255, blue: 255 / 255, alpha: 1)
+    fileprivate enum Theme {
+        static var pageStart: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.pageBackground
+                : NSColor(srgbRed: 249 / 255, green: 252 / 255, blue: 255 / 255, alpha: 1)
+        }
+
+        static var pageEnd: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.loadingPageBackgroundBottom
+                : NSColor(srgbRed: 238 / 255, green: 244 / 255, blue: 255 / 255, alpha: 1)
+        }
+
+        static var cardBackground: NSColor { AppDashboardTheme.cardBackground }
+
+        static var primaryText: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.primaryText
+                : NSColor(srgbRed: 27 / 255, green: 38 / 255, blue: 79 / 255, alpha: 1)
+        }
+
+        static var secondaryText: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.secondaryText
+                : NSColor(srgbRed: 108 / 255, green: 120 / 255, blue: 157 / 255, alpha: 1)
+        }
+
+        static var cardBorder: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.border
+                : NSColor(srgbRed: 198 / 255, green: 216 / 255, blue: 255 / 255, alpha: 1)
+        }
+
+        static var accent: NSColor { AppDashboardTheme.brandBlue }
+        static var accentHover: NSColor { AppDashboardTheme.brandBlueHover }
+
+        static var mutedButtonFill: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.profileFieldFill
+                : NSColor(srgbRed: 238 / 255, green: 243 / 255, blue: 252 / 255, alpha: 1)
+        }
+
+        static var bottomStrip: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.proCardFill
+                : NSColor(srgbRed: 244 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1)
+        }
+
+        static var divider: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.border
+                : NSColor(srgbRed: 218 / 255, green: 228 / 255, blue: 247 / 255, alpha: 1)
+        }
+
+        static var successText: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.welcomeHeroHeadingBlue
+                : NSColor(srgbRed: 21 / 255, green: 154 / 255, blue: 220 / 255, alpha: 1)
+        }
+
+        static var iconTint: NSColor { AppDashboardTheme.brandBlue }
+
+        static var closeButtonBackground: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.cardBackground
+                : NSColor.white.withAlphaComponent(0.92)
+        }
+
+        static var closeButtonBackgroundHover: NSColor {
+            AppDashboardTheme.isDark
+                ? AppDashboardTheme.neutralHoverFill
+                : NSColor.white.withAlphaComponent(0.98)
+        }
+    }
+
+    private struct PricingCardAppearanceTarget {
+        let card: HoverPricingCardView
+        let iconWell: NSView
+        let planIconView: NSImageView
+        let planId: String
+        let titleLabel: NSTextField
+        let subtitleLabel: NSTextField
+        let priceLabel: NSTextField
+        let periodLabel: NSTextField
+        let billingLabel: NSTextField?
+        let divider: NSBox
+        let featureLabels: [NSTextField]
+        let featureIcons: [NSImageView]
+        let purchaseButton: PlanPurchaseHoverButton
     }
 
     private enum FeatureListMetrics {
@@ -399,8 +491,9 @@ private final class PremiumPlansViewController: NSViewController {
     private var planPriceFields: [String: (price: NSTextField, period: NSTextField)] = [:]
     private var planPurchaseButtons: [String: NSButton] = [:]
     private var subscriptionPrimaryFooterButton: NSButton?
-    private var premiumCloseButton: NSButton?
+    private var premiumCloseButton: PremiumCloseHoverButton?
     private var subscriptionStatusObservation: NSObjectProtocol?
+    private var appearanceObserver: NSObjectProtocol?
 
     private let plans: [Plan] = [
         Plan(
@@ -460,15 +553,30 @@ private final class PremiumPlansViewController: NSViewController {
     ]
 
     private let pageGradient = CAGradientLayer()
+    private var premiumTitleLabel: NSTextField?
+    private var premiumSubtitleLabel: NSTextField?
+    private var pricingCardTargets: [PricingCardAppearanceTarget] = []
+    private weak var trustBadgesRow: NSStackView?
+    private var footerLinkButtons: [FooterLinkButton] = []
 
     deinit {
         if let subscriptionStatusObservation {
             NotificationCenter.default.removeObserver(subscriptionStatusObservation)
         }
+        if let appearanceObserver {
+            NotificationCenter.default.removeObserver(appearanceObserver)
+        }
     }
 
     override func viewDidLoad() {
         super.viewDidLoad()
+        appearanceObserver = NotificationCenter.default.addObserver(
+            forName: AppAppearanceManager.didChangeNotification,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.applyCurrentAppearance()
+        }
         subscriptionStatusObservation = NotificationCenter.default.addObserver(
             forName: .subscriptionStatusDidChange,
             object: nil,
@@ -499,6 +607,7 @@ private final class PremiumPlansViewController: NSViewController {
         pageGradient.endPoint = CGPoint(x: 1, y: 0)
         view.layer?.addSublayer(pageGradient)
         setupLayout()
+        applyCurrentAppearance()
     }
 
     private func setupLayout() {
@@ -514,12 +623,15 @@ private final class PremiumPlansViewController: NSViewController {
         title.font = .systemFont(ofSize: 40, weight: .semibold)
         title.textColor = Theme.primaryText
         title.alignment = .center
+        premiumTitleLabel = title
 
         let subtitle = NSTextField(labelWithString: "Unlock unlimited access to premium tools and boost your productivity.")
         subtitle.font = .systemFont(ofSize: 14, weight: .regular)
         subtitle.textColor = Theme.secondaryText
         subtitle.alignment = .center
+        premiumSubtitleLabel = subtitle
 
+        pricingCardTargets = []
         let cardsRow = NSStackView(views: plans.map(makePricingCard(_:)))
         cardsRow.orientation = .horizontal
         cardsRow.spacing = 14
@@ -626,7 +738,8 @@ private final class PremiumPlansViewController: NSViewController {
         divider.translatesAutoresizingMaskIntoConstraints = false
         divider.borderColor = Theme.divider
 
-        let featuresStack = NSStackView(views: plan.features.map(makeFeatureRow(_:)))
+        let featureRows = plan.features.map(makeFeatureRow(_:))
+        let featuresStack = NSStackView(views: featureRows)
         featuresStack.orientation = .vertical
         featuresStack.spacing = FeatureListMetrics.spacing
         featuresStack.alignment = .leading
@@ -643,6 +756,31 @@ private final class PremiumPlansViewController: NSViewController {
         planPriceFields[plan.id] = (priceLabel, periodLabel)
         selectButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
 
+        let featureLabels = featureRows.compactMap { row -> NSTextField? in
+            (row as? NSStackView)?.arrangedSubviews.compactMap { $0 as? NSTextField }.first
+        }
+        let featureIcons = featureRows.compactMap { row -> NSImageView? in
+            (row as? NSStackView)?.arrangedSubviews.compactMap { $0 as? NSImageView }.first
+        }
+
+        pricingCardTargets.append(
+            PricingCardAppearanceTarget(
+                card: card,
+                iconWell: iconWell,
+                planIconView: icon,
+                planId: plan.id,
+                titleLabel: titleLabel,
+                subtitleLabel: subtitleLabel,
+                priceLabel: priceLabel,
+                periodLabel: periodLabel,
+                billingLabel: plan.billedLine.isEmpty ? nil : billingLabel,
+                divider: divider,
+                featureLabels: featureLabels,
+                featureIcons: featureIcons,
+                purchaseButton: selectButton
+            )
+        )
+
         var contentViews: [NSView] = [iconWell, titleLabel, subtitleLabel, priceRow]
         if !plan.billedLine.isEmpty {
             contentViews.append(billingLabel)
@@ -755,10 +893,12 @@ private final class PremiumPlansViewController: NSViewController {
         badges.layer?.cornerRadius = 10
         badges.setHuggingPriority(.defaultLow, for: .horizontal)
         badges.heightAnchor.constraint(equalToConstant: 72).isActive = true
+        trustBadgesRow = badges
         return badges
     }
 
     private func makeFooterRow() -> NSView {
+        footerLinkButtons = []
         let primary = footerActionCell(
             title: subscriptionPrimaryFooterTitle(),
             action: #selector(didTapPrimaryFooterSubscriptionAction),
@@ -797,6 +937,7 @@ private final class PremiumPlansViewController: NSViewController {
         button.contentTintColor = Theme.secondaryText
         button.focusRingType = .none
         button.translatesAutoresizingMaskIntoConstraints = false
+        footerLinkButtons.append(button)
         container.addSubview(button)
 
         var constraints: [NSLayoutConstraint] = [
@@ -1045,4 +1186,63 @@ private final class PremiumPlansViewController: NSViewController {
         }
         window.close()
     }
+
+    private func applyCurrentAppearance() {
+        view.window?.backgroundColor = PremiumPlansWindowController.paywallSheetBackground
+        pageGradient.colors = [Theme.pageStart.cgColor, Theme.pageEnd.cgColor]
+
+        premiumTitleLabel?.textColor = Theme.primaryText
+        premiumSubtitleLabel?.textColor = Theme.secondaryText
+
+        for target in pricingCardTargets {
+            target.card.updateBorderColors(base: Theme.cardBorder, hover: Theme.accent)
+            target.card.layer?.backgroundColor = Theme.cardBackground.cgColor
+            target.iconWell.layer?.backgroundColor = Theme.bottomStrip.cgColor
+            target.planIconView.contentTintColor = planIconTint(planId: target.planId)
+            target.titleLabel.textColor = Theme.primaryText
+            target.subtitleLabel.textColor = Theme.secondaryText
+            target.priceLabel.textColor = Theme.primaryText
+            target.periodLabel.textColor = Theme.secondaryText
+            target.billingLabel?.textColor = Theme.secondaryText
+            target.divider.borderColor = Theme.divider
+            for label in target.featureLabels {
+                label.textColor = Theme.primaryText
+            }
+            for icon in target.featureIcons {
+                icon.contentTintColor = Theme.iconTint
+            }
+            target.purchaseButton.refreshAppearance()
+        }
+
+        if let trustBadgesRow {
+            trustBadgesRow.layer?.backgroundColor = Theme.bottomStrip.cgColor
+            trustBadgesRow.layer?.borderColor = Theme.divider.cgColor
+            for case let badge as NSStackView in trustBadgesRow.arrangedSubviews {
+                for case let image as NSImageView in badge.arrangedSubviews {
+                    image.contentTintColor = Theme.primaryText
+                }
+                for case let textStack as NSStackView in badge.arrangedSubviews {
+                    let labels = textStack.arrangedSubviews.compactMap { $0 as? NSTextField }
+                    if labels.count >= 2 {
+                        labels[0].textColor = Theme.primaryText
+                        labels[1].textColor = Theme.secondaryText
+                    }
+                }
+            }
+        }
+
+        for button in footerLinkButtons {
+            button.contentTintColor = Theme.secondaryText
+        }
+
+        premiumCloseButton?.refreshAppearance(hovered: false)
+    }
+
+    private func planIconTint(planId: String) -> NSColor {
+        switch planId {
+        case "monthly": Theme.accent
+        case "yearly": Theme.successText
+        default: Theme.iconTint
+        }
+    }
 }