|
|
@@ -42,19 +42,20 @@ enum PaywallPlan: CaseIterable {
|
|
|
|
|
|
// MARK: - Left Panel
|
|
|
|
|
|
-private final class PaywallLeftPanelView: NSView {
|
|
|
+private final class PaywallLeftPanelView: NSView, AppearanceRefreshable {
|
|
|
private let gradientLayer = CAGradientLayer()
|
|
|
|
|
|
override init(frame frameRect: NSRect) {
|
|
|
super.init(frame: frameRect)
|
|
|
wantsLayer = true
|
|
|
- gradientLayer.colors = [
|
|
|
- NSColor(red: 0.88, green: 0.94, blue: 1.0, alpha: 1).cgColor,
|
|
|
- NSColor(red: 0.95, green: 0.97, blue: 1.0, alpha: 1).cgColor,
|
|
|
- ]
|
|
|
gradientLayer.startPoint = CGPoint(x: 0.5, y: 1)
|
|
|
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
|
|
|
layer?.insertSublayer(gradientLayer, at: 0)
|
|
|
+ refreshAppearance()
|
|
|
+ }
|
|
|
+
|
|
|
+ func refreshAppearance() {
|
|
|
+ gradientLayer.colors = AppTheme.paywallLeftGradientColors.map(\.cgColor)
|
|
|
}
|
|
|
|
|
|
@available(*, unavailable)
|
|
|
@@ -121,8 +122,11 @@ private final class PaywallBadgeView: NSView {
|
|
|
|
|
|
// MARK: - Feature Row
|
|
|
|
|
|
-private final class PaywallFeatureRow: NSView {
|
|
|
+private final class PaywallFeatureRow: NSView, AppearanceRefreshable {
|
|
|
+ private let label: NSTextField
|
|
|
+
|
|
|
init(text: String) {
|
|
|
+ label = NSTextField.themeLabel(text, style: .primary, font: AppTheme.regularFont(size: 14))
|
|
|
super.init(frame: .zero)
|
|
|
translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
@@ -141,9 +145,6 @@ private final class PaywallFeatureRow: NSView {
|
|
|
}
|
|
|
checkIcon.contentTintColor = .white
|
|
|
|
|
|
- let label = NSTextField(labelWithString: text)
|
|
|
- label.font = AppTheme.regularFont(size: 14)
|
|
|
- label.textColor = AppTheme.navy
|
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
addSubview(checkContainer)
|
|
|
@@ -171,11 +172,15 @@ private final class PaywallFeatureRow: NSView {
|
|
|
|
|
|
@available(*, unavailable)
|
|
|
required init?(coder: NSCoder) { nil }
|
|
|
+
|
|
|
+ func refreshAppearance() {
|
|
|
+ label.refreshThemeLabelColor()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// MARK: - Plan Card
|
|
|
|
|
|
-private final class PaywallPlanCard: NSControl {
|
|
|
+private final class PaywallPlanCard: NSControl, AppearanceRefreshable {
|
|
|
var onSelect: (() -> Void)?
|
|
|
|
|
|
private let plan: PaywallPlan
|
|
|
@@ -194,7 +199,6 @@ private final class PaywallPlanCard: NSControl {
|
|
|
translatesAutoresizingMaskIntoConstraints = false
|
|
|
wantsLayer = true
|
|
|
layer?.cornerRadius = 12
|
|
|
- layer?.backgroundColor = NSColor.white.cgColor
|
|
|
layer?.masksToBounds = false
|
|
|
|
|
|
titleLabel.stringValue = plan.title
|
|
|
@@ -203,6 +207,7 @@ private final class PaywallPlanCard: NSControl {
|
|
|
|
|
|
subtitleLabel.stringValue = plan.subtitle
|
|
|
subtitleLabel.font = AppTheme.regularFont(size: 11)
|
|
|
+ subtitleLabel.themeLabelStyle = .secondary
|
|
|
subtitleLabel.textColor = AppTheme.textSecondary
|
|
|
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
@@ -263,11 +268,17 @@ private final class PaywallPlanCard: NSControl {
|
|
|
@available(*, unavailable)
|
|
|
required init?(coder: NSCoder) { nil }
|
|
|
|
|
|
+ func refreshAppearance() {
|
|
|
+ updateAppearance()
|
|
|
+ subtitleLabel.refreshThemeLabelColor()
|
|
|
+ }
|
|
|
+
|
|
|
private func updateAppearance() {
|
|
|
- let titleColor = isChosen && plan == .yearly ? AppTheme.green : AppTheme.navy
|
|
|
+ let titleColor = isChosen && plan == .yearly ? AppTheme.green : AppTheme.paywallAccent
|
|
|
titleLabel.textColor = titleColor
|
|
|
priceLabel.textColor = titleColor
|
|
|
|
|
|
+ layer?.backgroundColor = AppTheme.paywallTrustBackground.cgColor
|
|
|
layer?.borderWidth = isChosen ? 2 : 1
|
|
|
layer?.borderColor = (isChosen ? AppTheme.green : AppTheme.paywallBorder).cgColor
|
|
|
}
|
|
|
@@ -284,19 +295,23 @@ private final class PaywallPlanCard: NSControl {
|
|
|
|
|
|
// MARK: - Footer Link
|
|
|
|
|
|
-private final class PaywallFooterLink: NSButton {
|
|
|
+private final class PaywallFooterLink: NSButton, AppearanceRefreshable {
|
|
|
init(title: String) {
|
|
|
super.init(frame: .zero)
|
|
|
self.title = title
|
|
|
isBordered = false
|
|
|
font = AppTheme.regularFont(size: 11)
|
|
|
- contentTintColor = AppTheme.textSecondary
|
|
|
translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ refreshAppearance()
|
|
|
}
|
|
|
|
|
|
@available(*, unavailable)
|
|
|
required init?(coder: NSCoder) { nil }
|
|
|
|
|
|
+ func refreshAppearance() {
|
|
|
+ contentTintColor = AppTheme.textSecondary
|
|
|
+ }
|
|
|
+
|
|
|
override func resetCursorRects() {
|
|
|
addCursorRect(bounds, cursor: .pointingHand)
|
|
|
}
|
|
|
@@ -304,36 +319,33 @@ private final class PaywallFooterLink: NSButton {
|
|
|
|
|
|
// MARK: - Footer Trust Item
|
|
|
|
|
|
-private final class PaywallTrustItemView: NSView {
|
|
|
+private final class PaywallTrustItemView: NSView, AppearanceRefreshable {
|
|
|
+ private let iconContainer = NSView()
|
|
|
+ private let icon = NSImageView()
|
|
|
+ private let titleLabel: NSTextField
|
|
|
+ private let subtitleLabel: NSTextField
|
|
|
+
|
|
|
init(iconName: String, title: String, subtitle: String) {
|
|
|
+ titleLabel = NSTextField.themeLabel(title, style: .primary, font: AppTheme.semiboldFont(size: 11))
|
|
|
+ subtitleLabel = NSTextField.themeLabel(subtitle, style: .secondary, font: AppTheme.regularFont(size: 9))
|
|
|
super.init(frame: .zero)
|
|
|
translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
- let iconContainer = NSView()
|
|
|
iconContainer.translatesAutoresizingMaskIntoConstraints = false
|
|
|
iconContainer.wantsLayer = true
|
|
|
- iconContainer.layer?.backgroundColor = AppTheme.blueLight.cgColor
|
|
|
iconContainer.layer?.cornerRadius = 10
|
|
|
iconContainer.layer?.masksToBounds = true
|
|
|
|
|
|
- let icon = NSImageView()
|
|
|
icon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
if let image = NSImage(systemSymbolName: iconName, accessibilityDescription: nil) {
|
|
|
let config = NSImage.SymbolConfiguration(pointSize: 11, weight: .semibold)
|
|
|
icon.image = image.withSymbolConfiguration(config)
|
|
|
}
|
|
|
- icon.contentTintColor = AppTheme.navy
|
|
|
|
|
|
- let titleLabel = NSTextField(labelWithString: title)
|
|
|
- titleLabel.font = AppTheme.semiboldFont(size: 11)
|
|
|
- titleLabel.textColor = AppTheme.navy
|
|
|
titleLabel.lineBreakMode = .byTruncatingTail
|
|
|
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
- let subtitleLabel = NSTextField(labelWithString: subtitle)
|
|
|
- subtitleLabel.font = AppTheme.regularFont(size: 9)
|
|
|
- subtitleLabel.textColor = AppTheme.textSecondary
|
|
|
subtitleLabel.lineBreakMode = .byTruncatingTail
|
|
|
subtitleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -366,15 +378,23 @@ private final class PaywallTrustItemView: NSView {
|
|
|
|
|
|
setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
|
+ refreshAppearance()
|
|
|
}
|
|
|
|
|
|
@available(*, unavailable)
|
|
|
required init?(coder: NSCoder) { nil }
|
|
|
+
|
|
|
+ func refreshAppearance() {
|
|
|
+ iconContainer.layer?.backgroundColor = AppTheme.paywallTrustIconBackground.cgColor
|
|
|
+ icon.contentTintColor = AppTheme.paywallIconAccent
|
|
|
+ titleLabel.refreshThemeLabelColor()
|
|
|
+ subtitleLabel.refreshThemeLabelColor()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// MARK: - Main Paywall Card
|
|
|
|
|
|
-final class PaywallView: NSView {
|
|
|
+final class PaywallView: NSView, AppearanceRefreshable {
|
|
|
var onClose: (() -> Void)?
|
|
|
var onPurchase: ((PaywallPlan) -> Void)?
|
|
|
var onRestore: (() -> Void)?
|
|
|
@@ -382,14 +402,30 @@ final class PaywallView: NSView {
|
|
|
private var selectedPlan: PaywallPlan = .yearly
|
|
|
private var planCards: [PaywallPlan: PaywallPlanCard] = [:]
|
|
|
private let ctaButton = NSButton()
|
|
|
+ private var leftPanelTitle: NSTextField!
|
|
|
+ private var rightTitle: NSTextField!
|
|
|
+ private var rightSubtitle: NSTextField!
|
|
|
+ private var trustStack: NSStackView!
|
|
|
|
|
|
init() {
|
|
|
super.init(frame: .zero)
|
|
|
translatesAutoresizingMaskIntoConstraints = false
|
|
|
wantsLayer = true
|
|
|
- layer?.backgroundColor = NSColor.white.cgColor
|
|
|
layer?.cornerRadius = 0
|
|
|
setup()
|
|
|
+ refreshAppearance()
|
|
|
+ }
|
|
|
+
|
|
|
+ func refreshAppearance() {
|
|
|
+ layer?.backgroundColor = AppTheme.paywallBackground.cgColor
|
|
|
+ leftPanelTitle?.refreshThemeLabelColor()
|
|
|
+ rightTitle?.refreshThemeLabelColor()
|
|
|
+ rightSubtitle?.refreshThemeLabelColor()
|
|
|
+ trustStack?.layer?.backgroundColor = AppTheme.paywallTrustBackground.cgColor
|
|
|
+ trustStack?.layer?.borderColor = AppTheme.paywallBorder.cgColor
|
|
|
+ ctaButton.layer?.backgroundColor = AppTheme.paywallCTABackground.cgColor
|
|
|
+ ctaButton.contentTintColor = AppTheme.paywallCTAForeground
|
|
|
+ subviews.forEach { $0.refreshAppearanceRecursively() }
|
|
|
}
|
|
|
|
|
|
@available(*, unavailable)
|
|
|
@@ -419,11 +455,14 @@ final class PaywallView: NSView {
|
|
|
let panel = PaywallLeftPanelView()
|
|
|
panel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
- let title = NSTextField(labelWithString: "Unlock Your Full\nPrinting Potential")
|
|
|
- title.font = AppTheme.semiboldFont(size: 22)
|
|
|
- title.textColor = AppTheme.navy
|
|
|
+ let title = NSTextField.themeLabel(
|
|
|
+ "Unlock Your Full\nPrinting Potential",
|
|
|
+ style: .primary,
|
|
|
+ font: AppTheme.semiboldFont(size: 22)
|
|
|
+ )
|
|
|
title.maximumNumberOfLines = 2
|
|
|
title.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ leftPanelTitle = title
|
|
|
|
|
|
let featuresStack = NSStackView()
|
|
|
featuresStack.orientation = .vertical
|
|
|
@@ -463,18 +502,20 @@ final class PaywallView: NSView {
|
|
|
let panel = NSView()
|
|
|
panel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
- let title = NSTextField(labelWithString: "Go Premium")
|
|
|
- title.font = AppTheme.semiboldFont(size: 26)
|
|
|
- title.textColor = AppTheme.navy
|
|
|
+ let title = NSTextField.themeLabel("Go Premium", style: .primary, font: AppTheme.semiboldFont(size: 26))
|
|
|
title.alignment = .center
|
|
|
title.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ rightTitle = title
|
|
|
|
|
|
- let subtitle = NSTextField(labelWithString: "Experience professional quality printing and scanning without limits.")
|
|
|
- subtitle.font = AppTheme.regularFont(size: 13)
|
|
|
- subtitle.textColor = AppTheme.textSecondary
|
|
|
+ let subtitle = NSTextField.themeLabel(
|
|
|
+ "Experience professional quality printing and scanning without limits.",
|
|
|
+ style: .secondary,
|
|
|
+ font: AppTheme.regularFont(size: 13)
|
|
|
+ )
|
|
|
subtitle.alignment = .center
|
|
|
subtitle.maximumNumberOfLines = 2
|
|
|
subtitle.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ rightSubtitle = subtitle
|
|
|
|
|
|
let plansStack = NSStackView()
|
|
|
plansStack.orientation = .vertical
|
|
|
@@ -492,10 +533,8 @@ final class PaywallView: NSView {
|
|
|
ctaButton.title = selectedPlan.ctaTitle
|
|
|
ctaButton.isBordered = false
|
|
|
ctaButton.wantsLayer = true
|
|
|
- ctaButton.layer?.backgroundColor = AppTheme.navy.cgColor
|
|
|
ctaButton.layer?.cornerRadius = 12
|
|
|
ctaButton.font = AppTheme.semiboldFont(size: 15)
|
|
|
- ctaButton.contentTintColor = .white
|
|
|
ctaButton.target = self
|
|
|
ctaButton.action = #selector(purchaseTapped)
|
|
|
ctaButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -569,13 +608,12 @@ final class PaywallView: NSView {
|
|
|
trustStack.alignment = .top
|
|
|
trustStack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
trustStack.wantsLayer = true
|
|
|
- trustStack.layer?.backgroundColor = NSColor.white.cgColor
|
|
|
trustStack.layer?.cornerRadius = 12
|
|
|
trustStack.layer?.borderWidth = 1
|
|
|
- trustStack.layer?.borderColor = AppTheme.paywallBorder.cgColor
|
|
|
trustStack.layer?.masksToBounds = true
|
|
|
trustStack.edgeInsets = NSEdgeInsets(top: 12, left: 14, bottom: 12, right: 14)
|
|
|
trustStack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ self.trustStack = trustStack
|
|
|
|
|
|
return trustStack
|
|
|
}
|
|
|
@@ -657,18 +695,27 @@ final class PaywallView: NSView {
|
|
|
|
|
|
// MARK: - Overlay Presenter
|
|
|
|
|
|
-final class PaywallOverlayView: NSView {
|
|
|
+final class PaywallOverlayView: NSView, AppearanceRefreshable {
|
|
|
var onDismiss: (() -> Void)?
|
|
|
|
|
|
private let paywallView: PaywallView
|
|
|
private let blurView = NSVisualEffectView()
|
|
|
private let backdrop = NSView()
|
|
|
+ private let pattern = WavePatternView()
|
|
|
|
|
|
init() {
|
|
|
paywallView = PaywallView()
|
|
|
super.init(frame: .zero)
|
|
|
translatesAutoresizingMaskIntoConstraints = false
|
|
|
setup()
|
|
|
+ refreshAppearance()
|
|
|
+ }
|
|
|
+
|
|
|
+ func refreshAppearance() {
|
|
|
+ backdrop.layer?.backgroundColor = AppTheme.paywallOverlayBackdrop.cgColor
|
|
|
+ blurView.material = AppSettings.darkModeEnabled ? .hudWindow : .underWindowBackground
|
|
|
+ pattern.refreshAppearance()
|
|
|
+ paywallView.refreshAppearance()
|
|
|
}
|
|
|
|
|
|
@available(*, unavailable)
|
|
|
@@ -684,7 +731,6 @@ final class PaywallOverlayView: NSView {
|
|
|
backdrop.wantsLayer = true
|
|
|
backdrop.layer?.backgroundColor = NSColor(calibratedWhite: 0.15, alpha: 0.22).cgColor
|
|
|
|
|
|
- let pattern = WavePatternView()
|
|
|
pattern.translatesAutoresizingMaskIntoConstraints = false
|
|
|
pattern.alphaValue = 0.35
|
|
|
|