|
@@ -3,7 +3,7 @@ import StoreKit
|
|
|
|
|
|
|
|
final class PremiumPlansWindowController: NSWindowController {
|
|
final class PremiumPlansWindowController: NSWindowController {
|
|
|
/// Matches `PremiumPlansViewController.Theme.pageStart` so the window backing fills sheet corners.
|
|
/// 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() {
|
|
init() {
|
|
|
let viewController = PremiumPlansViewController()
|
|
let viewController = PremiumPlansViewController()
|
|
@@ -29,9 +29,10 @@ final class PremiumPlansWindowController: NSWindowController {
|
|
|
|
|
|
|
|
private final class PremiumPlansViewController: NSViewController {
|
|
private final class PremiumPlansViewController: NSViewController {
|
|
|
private final class HoverPricingCardView: NSView {
|
|
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 trackingAreaRef: NSTrackingArea?
|
|
|
|
|
+ private var isHovered = false
|
|
|
|
|
|
|
|
init(baseBorderColor: NSColor, hoverBorderColor: NSColor) {
|
|
init(baseBorderColor: NSColor, hoverBorderColor: NSColor) {
|
|
|
self.baseBorderColor = baseBorderColor
|
|
self.baseBorderColor = baseBorderColor
|
|
@@ -60,14 +61,22 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
|
|
|
|
|
override func mouseEntered(with event: NSEvent) {
|
|
override func mouseEntered(with event: NSEvent) {
|
|
|
super.mouseEntered(with: event)
|
|
super.mouseEntered(with: event)
|
|
|
|
|
+ isHovered = true
|
|
|
applyHoverStyle(isHovered: true, animated: true)
|
|
applyHoverStyle(isHovered: true, animated: true)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override func mouseExited(with event: NSEvent) {
|
|
override func mouseExited(with event: NSEvent) {
|
|
|
super.mouseExited(with: event)
|
|
super.mouseExited(with: event)
|
|
|
|
|
+ isHovered = false
|
|
|
applyHoverStyle(isHovered: false, animated: true)
|
|
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) {
|
|
private func applyHoverStyle(isHovered: Bool, animated: Bool) {
|
|
|
guard let layer else { return }
|
|
guard let layer else { return }
|
|
|
let updates = {
|
|
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) {
|
|
private func applyBaseStyle(hovered: Bool, animated: Bool = true) {
|
|
|
let updates = {
|
|
let updates = {
|
|
|
if self.isPrimaryStyle {
|
|
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) {
|
|
private func applyStyle(hovered: Bool, animated: Bool) {
|
|
|
let updates = {
|
|
let updates = {
|
|
|
self.layer?.backgroundColor = (hovered
|
|
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?.borderColor = (hovered ? Theme.accent.withAlphaComponent(0.45) : Theme.divider).cgColor
|
|
|
self.layer?.borderWidth = hovered ? 1.5 : 1
|
|
self.layer?.borderWidth = hovered ? 1.5 : 1
|
|
|
self.contentTintColor = hovered ? Theme.accent : Theme.secondaryText
|
|
self.contentTintColor = hovered ? Theme.accent : Theme.secondaryText
|
|
@@ -374,20 +391,95 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
let highlight: Bool
|
|
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 {
|
|
private enum FeatureListMetrics {
|
|
@@ -399,8 +491,9 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
private var planPriceFields: [String: (price: NSTextField, period: NSTextField)] = [:]
|
|
private var planPriceFields: [String: (price: NSTextField, period: NSTextField)] = [:]
|
|
|
private var planPurchaseButtons: [String: NSButton] = [:]
|
|
private var planPurchaseButtons: [String: NSButton] = [:]
|
|
|
private var subscriptionPrimaryFooterButton: NSButton?
|
|
private var subscriptionPrimaryFooterButton: NSButton?
|
|
|
- private var premiumCloseButton: NSButton?
|
|
|
|
|
|
|
+ private var premiumCloseButton: PremiumCloseHoverButton?
|
|
|
private var subscriptionStatusObservation: NSObjectProtocol?
|
|
private var subscriptionStatusObservation: NSObjectProtocol?
|
|
|
|
|
+ private var appearanceObserver: NSObjectProtocol?
|
|
|
|
|
|
|
|
private let plans: [Plan] = [
|
|
private let plans: [Plan] = [
|
|
|
Plan(
|
|
Plan(
|
|
@@ -460,15 +553,30 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
private let pageGradient = CAGradientLayer()
|
|
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 {
|
|
deinit {
|
|
|
if let subscriptionStatusObservation {
|
|
if let subscriptionStatusObservation {
|
|
|
NotificationCenter.default.removeObserver(subscriptionStatusObservation)
|
|
NotificationCenter.default.removeObserver(subscriptionStatusObservation)
|
|
|
}
|
|
}
|
|
|
|
|
+ if let appearanceObserver {
|
|
|
|
|
+ NotificationCenter.default.removeObserver(appearanceObserver)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
override func viewDidLoad() {
|
|
|
super.viewDidLoad()
|
|
super.viewDidLoad()
|
|
|
|
|
+ appearanceObserver = NotificationCenter.default.addObserver(
|
|
|
|
|
+ forName: AppAppearanceManager.didChangeNotification,
|
|
|
|
|
+ object: nil,
|
|
|
|
|
+ queue: .main
|
|
|
|
|
+ ) { [weak self] _ in
|
|
|
|
|
+ self?.applyCurrentAppearance()
|
|
|
|
|
+ }
|
|
|
subscriptionStatusObservation = NotificationCenter.default.addObserver(
|
|
subscriptionStatusObservation = NotificationCenter.default.addObserver(
|
|
|
forName: .subscriptionStatusDidChange,
|
|
forName: .subscriptionStatusDidChange,
|
|
|
object: nil,
|
|
object: nil,
|
|
@@ -499,6 +607,7 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
pageGradient.endPoint = CGPoint(x: 1, y: 0)
|
|
pageGradient.endPoint = CGPoint(x: 1, y: 0)
|
|
|
view.layer?.addSublayer(pageGradient)
|
|
view.layer?.addSublayer(pageGradient)
|
|
|
setupLayout()
|
|
setupLayout()
|
|
|
|
|
+ applyCurrentAppearance()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func setupLayout() {
|
|
private func setupLayout() {
|
|
@@ -514,12 +623,15 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
title.font = .systemFont(ofSize: 40, weight: .semibold)
|
|
title.font = .systemFont(ofSize: 40, weight: .semibold)
|
|
|
title.textColor = Theme.primaryText
|
|
title.textColor = Theme.primaryText
|
|
|
title.alignment = .center
|
|
title.alignment = .center
|
|
|
|
|
+ premiumTitleLabel = title
|
|
|
|
|
|
|
|
let subtitle = NSTextField(labelWithString: "Unlock unlimited access to premium tools and boost your productivity.")
|
|
let subtitle = NSTextField(labelWithString: "Unlock unlimited access to premium tools and boost your productivity.")
|
|
|
subtitle.font = .systemFont(ofSize: 14, weight: .regular)
|
|
subtitle.font = .systemFont(ofSize: 14, weight: .regular)
|
|
|
subtitle.textColor = Theme.secondaryText
|
|
subtitle.textColor = Theme.secondaryText
|
|
|
subtitle.alignment = .center
|
|
subtitle.alignment = .center
|
|
|
|
|
+ premiumSubtitleLabel = subtitle
|
|
|
|
|
|
|
|
|
|
+ pricingCardTargets = []
|
|
|
let cardsRow = NSStackView(views: plans.map(makePricingCard(_:)))
|
|
let cardsRow = NSStackView(views: plans.map(makePricingCard(_:)))
|
|
|
cardsRow.orientation = .horizontal
|
|
cardsRow.orientation = .horizontal
|
|
|
cardsRow.spacing = 14
|
|
cardsRow.spacing = 14
|
|
@@ -626,7 +738,8 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
divider.translatesAutoresizingMaskIntoConstraints = false
|
|
divider.translatesAutoresizingMaskIntoConstraints = false
|
|
|
divider.borderColor = Theme.divider
|
|
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.orientation = .vertical
|
|
|
featuresStack.spacing = FeatureListMetrics.spacing
|
|
featuresStack.spacing = FeatureListMetrics.spacing
|
|
|
featuresStack.alignment = .leading
|
|
featuresStack.alignment = .leading
|
|
@@ -643,6 +756,31 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
planPriceFields[plan.id] = (priceLabel, periodLabel)
|
|
planPriceFields[plan.id] = (priceLabel, periodLabel)
|
|
|
selectButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
|
|
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]
|
|
var contentViews: [NSView] = [iconWell, titleLabel, subtitleLabel, priceRow]
|
|
|
if !plan.billedLine.isEmpty {
|
|
if !plan.billedLine.isEmpty {
|
|
|
contentViews.append(billingLabel)
|
|
contentViews.append(billingLabel)
|
|
@@ -755,10 +893,12 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
badges.layer?.cornerRadius = 10
|
|
badges.layer?.cornerRadius = 10
|
|
|
badges.setHuggingPriority(.defaultLow, for: .horizontal)
|
|
badges.setHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
badges.heightAnchor.constraint(equalToConstant: 72).isActive = true
|
|
badges.heightAnchor.constraint(equalToConstant: 72).isActive = true
|
|
|
|
|
+ trustBadgesRow = badges
|
|
|
return badges
|
|
return badges
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func makeFooterRow() -> NSView {
|
|
private func makeFooterRow() -> NSView {
|
|
|
|
|
+ footerLinkButtons = []
|
|
|
let primary = footerActionCell(
|
|
let primary = footerActionCell(
|
|
|
title: subscriptionPrimaryFooterTitle(),
|
|
title: subscriptionPrimaryFooterTitle(),
|
|
|
action: #selector(didTapPrimaryFooterSubscriptionAction),
|
|
action: #selector(didTapPrimaryFooterSubscriptionAction),
|
|
@@ -797,6 +937,7 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
button.contentTintColor = Theme.secondaryText
|
|
button.contentTintColor = Theme.secondaryText
|
|
|
button.focusRingType = .none
|
|
button.focusRingType = .none
|
|
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ footerLinkButtons.append(button)
|
|
|
container.addSubview(button)
|
|
container.addSubview(button)
|
|
|
|
|
|
|
|
var constraints: [NSLayoutConstraint] = [
|
|
var constraints: [NSLayoutConstraint] = [
|
|
@@ -1045,4 +1186,63 @@ private final class PremiumPlansViewController: NSViewController {
|
|
|
}
|
|
}
|
|
|
window.close()
|
|
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
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|