|
|
@@ -188,6 +188,8 @@ private final class PaywallPlanCard: NSControl, AppearanceRefreshable {
|
|
188
|
188
|
private let subtitleLabel = NSTextField(labelWithString: "")
|
|
189
|
189
|
private let priceLabel = NSTextField(labelWithString: "")
|
|
190
|
190
|
private var badgeView: PaywallBadgeView?
|
|
|
191
|
+ private var hoverTracker: HoverTracker?
|
|
|
192
|
+ private var isHovered = false
|
|
191
|
193
|
|
|
192
|
194
|
var isChosen: Bool = false {
|
|
193
|
195
|
didSet { updateAppearance() }
|
|
|
@@ -262,7 +264,12 @@ private final class PaywallPlanCard: NSControl, AppearanceRefreshable {
|
|
262
|
264
|
])
|
|
263
|
265
|
}
|
|
264
|
266
|
|
|
|
267
|
+ applyCardShadow()
|
|
265
|
268
|
updateAppearance()
|
|
|
269
|
+
|
|
|
270
|
+ hoverTracker = HoverTracker(view: self) { [weak self] hovering in
|
|
|
271
|
+ self?.setHovered(hovering)
|
|
|
272
|
+ }
|
|
266
|
273
|
}
|
|
267
|
274
|
|
|
268
|
275
|
@available(*, unavailable)
|
|
|
@@ -271,6 +278,15 @@ private final class PaywallPlanCard: NSControl, AppearanceRefreshable {
|
|
271
|
278
|
func refreshAppearance() {
|
|
272
|
279
|
updateAppearance()
|
|
273
|
280
|
subtitleLabel.refreshThemeLabelColor()
|
|
|
281
|
+ if isHovered {
|
|
|
282
|
+ applyHoverLift(true)
|
|
|
283
|
+ }
|
|
|
284
|
+ }
|
|
|
285
|
+
|
|
|
286
|
+ private func setHovered(_ hovering: Bool) {
|
|
|
287
|
+ isHovered = hovering
|
|
|
288
|
+ applyHoverLift(hovering)
|
|
|
289
|
+ updateAppearance()
|
|
274
|
290
|
}
|
|
275
|
291
|
|
|
276
|
292
|
private func updateAppearance() {
|
|
|
@@ -279,8 +295,19 @@ private final class PaywallPlanCard: NSControl, AppearanceRefreshable {
|
|
279
|
295
|
priceLabel.textColor = titleColor
|
|
280
|
296
|
|
|
281
|
297
|
layer?.backgroundColor = AppTheme.paywallTrustBackground.cgColor
|
|
282
|
|
- layer?.borderWidth = isChosen ? 2 : 1
|
|
283
|
|
- layer?.borderColor = (isChosen ? AppTheme.green : AppTheme.paywallBorder).cgColor
|
|
|
298
|
+
|
|
|
299
|
+ if isChosen {
|
|
|
300
|
+ layer?.borderWidth = 2
|
|
|
301
|
+ layer?.borderColor = AppTheme.green.cgColor
|
|
|
302
|
+ } else if isHovered {
|
|
|
303
|
+ layer?.borderWidth = 1.5
|
|
|
304
|
+ let hoverBorder = AppTheme.paywallBorder.blended(withFraction: 0.35, of: AppTheme.paywallAccent)
|
|
|
305
|
+ ?? AppTheme.paywallBorder
|
|
|
306
|
+ layer?.borderColor = hoverBorder.cgColor
|
|
|
307
|
+ } else {
|
|
|
308
|
+ layer?.borderWidth = 1
|
|
|
309
|
+ layer?.borderColor = AppTheme.paywallBorder.cgColor
|
|
|
310
|
+ }
|
|
284
|
311
|
}
|
|
285
|
312
|
|
|
286
|
313
|
override func mouseUp(with event: NSEvent) {
|
|
|
@@ -296,6 +323,9 @@ private final class PaywallPlanCard: NSControl, AppearanceRefreshable {
|
|
296
|
323
|
// MARK: - Footer Link
|
|
297
|
324
|
|
|
298
|
325
|
private final class PaywallFooterLink: NSButton, AppearanceRefreshable {
|
|
|
326
|
+ private var hoverTracker: HoverTracker?
|
|
|
327
|
+ private var isHovered = false
|
|
|
328
|
+
|
|
299
|
329
|
init(title: String) {
|
|
300
|
330
|
super.init(frame: .zero)
|
|
301
|
331
|
self.title = title
|
|
|
@@ -303,13 +333,18 @@ private final class PaywallFooterLink: NSButton, AppearanceRefreshable {
|
|
303
|
333
|
font = AppTheme.regularFont(size: 11)
|
|
304
|
334
|
translatesAutoresizingMaskIntoConstraints = false
|
|
305
|
335
|
refreshAppearance()
|
|
|
336
|
+
|
|
|
337
|
+ hoverTracker = HoverTracker(view: self) { [weak self] hovering in
|
|
|
338
|
+ self?.isHovered = hovering
|
|
|
339
|
+ self?.refreshAppearance()
|
|
|
340
|
+ }
|
|
306
|
341
|
}
|
|
307
|
342
|
|
|
308
|
343
|
@available(*, unavailable)
|
|
309
|
344
|
required init?(coder: NSCoder) { nil }
|
|
310
|
345
|
|
|
311
|
346
|
func refreshAppearance() {
|
|
312
|
|
- contentTintColor = AppTheme.textSecondary
|
|
|
347
|
+ contentTintColor = isHovered ? AppTheme.textPrimary : AppTheme.textSecondary
|
|
313
|
348
|
}
|
|
314
|
349
|
|
|
315
|
350
|
override func resetCursorRects() {
|
|
|
@@ -392,6 +427,49 @@ private final class PaywallTrustItemView: NSView, AppearanceRefreshable {
|
|
392
|
427
|
}
|
|
393
|
428
|
}
|
|
394
|
429
|
|
|
|
430
|
+// MARK: - CTA Button
|
|
|
431
|
+
|
|
|
432
|
+private final class PaywallCTAButton: NSButton, AppearanceRefreshable {
|
|
|
433
|
+ private var hoverTracker: HoverTracker?
|
|
|
434
|
+
|
|
|
435
|
+ init() {
|
|
|
436
|
+ super.init(frame: .zero)
|
|
|
437
|
+ isBordered = false
|
|
|
438
|
+ wantsLayer = true
|
|
|
439
|
+ layer?.cornerRadius = 12
|
|
|
440
|
+ font = AppTheme.semiboldFont(size: 15)
|
|
|
441
|
+ translatesAutoresizingMaskIntoConstraints = false
|
|
|
442
|
+ refreshAppearance()
|
|
|
443
|
+
|
|
|
444
|
+ hoverTracker = HoverTracker(view: self) { [weak self] hovering in
|
|
|
445
|
+ self?.setHovered(hovering)
|
|
|
446
|
+ }
|
|
|
447
|
+ }
|
|
|
448
|
+
|
|
|
449
|
+ @available(*, unavailable)
|
|
|
450
|
+ required init?(coder: NSCoder) { nil }
|
|
|
451
|
+
|
|
|
452
|
+ func refreshAppearance() {
|
|
|
453
|
+ layer?.backgroundColor = AppTheme.paywallCTABackground.cgColor
|
|
|
454
|
+ contentTintColor = AppTheme.paywallCTAForeground
|
|
|
455
|
+ }
|
|
|
456
|
+
|
|
|
457
|
+ private func setHovered(_ hovering: Bool) {
|
|
|
458
|
+ let base = AppTheme.paywallCTABackground
|
|
|
459
|
+ let color = hovering ? base.blended(withFraction: 0.12, of: .black) ?? base : base
|
|
|
460
|
+ animateHover {
|
|
|
461
|
+ layer?.backgroundColor = color.cgColor
|
|
|
462
|
+ layer?.transform = hovering
|
|
|
463
|
+ ? CATransform3DMakeScale(1.02, 1.02, 1)
|
|
|
464
|
+ : CATransform3DIdentity
|
|
|
465
|
+ }
|
|
|
466
|
+ }
|
|
|
467
|
+
|
|
|
468
|
+ override func resetCursorRects() {
|
|
|
469
|
+ addCursorRect(bounds, cursor: .pointingHand)
|
|
|
470
|
+ }
|
|
|
471
|
+}
|
|
|
472
|
+
|
|
395
|
473
|
// MARK: - Main Paywall Card
|
|
396
|
474
|
|
|
397
|
475
|
final class PaywallView: NSView, AppearanceRefreshable {
|
|
|
@@ -401,7 +479,7 @@ final class PaywallView: NSView, AppearanceRefreshable {
|
|
401
|
479
|
|
|
402
|
480
|
private var selectedPlan: PaywallPlan = .yearly
|
|
403
|
481
|
private var planCards: [PaywallPlan: PaywallPlanCard] = [:]
|
|
404
|
|
- private let ctaButton = NSButton()
|
|
|
482
|
+ private let ctaButton = PaywallCTAButton()
|
|
405
|
483
|
private var leftPanelTitle: NSTextField!
|
|
406
|
484
|
private var rightTitle: NSTextField!
|
|
407
|
485
|
private var rightSubtitle: NSTextField!
|
|
|
@@ -423,8 +501,7 @@ final class PaywallView: NSView, AppearanceRefreshable {
|
|
423
|
501
|
rightSubtitle?.refreshThemeLabelColor()
|
|
424
|
502
|
trustStack?.layer?.backgroundColor = AppTheme.paywallTrustBackground.cgColor
|
|
425
|
503
|
trustStack?.layer?.borderColor = AppTheme.paywallBorder.cgColor
|
|
426
|
|
- ctaButton.layer?.backgroundColor = AppTheme.paywallCTABackground.cgColor
|
|
427
|
|
- ctaButton.contentTintColor = AppTheme.paywallCTAForeground
|
|
|
504
|
+ ctaButton.refreshAppearance()
|
|
428
|
505
|
subviews.forEach { $0.refreshAppearanceRecursively() }
|
|
429
|
506
|
}
|
|
430
|
507
|
|
|
|
@@ -531,10 +608,6 @@ final class PaywallView: NSView, AppearanceRefreshable {
|
|
531
|
608
|
}
|
|
532
|
609
|
|
|
533
|
610
|
ctaButton.title = selectedPlan.ctaTitle
|
|
534
|
|
- ctaButton.isBordered = false
|
|
535
|
|
- ctaButton.wantsLayer = true
|
|
536
|
|
- ctaButton.layer?.cornerRadius = 12
|
|
537
|
|
- ctaButton.font = AppTheme.semiboldFont(size: 15)
|
|
538
|
611
|
ctaButton.target = self
|
|
539
|
612
|
ctaButton.action = #selector(purchaseTapped)
|
|
540
|
613
|
ctaButton.translatesAutoresizingMaskIntoConstraints = false
|