|
|
@@ -250,6 +250,7 @@ final class ViewController: NSViewController {
|
|
250
|
250
|
private var zoomJoinModeViews: [ZoomJoinMode: NSView] = [:]
|
|
251
|
251
|
private var settingsActionByView = [ObjectIdentifier: SettingsAction]()
|
|
252
|
252
|
private var paywallWindow: NSWindow?
|
|
|
253
|
+ private weak var paywallOverlayView: NSView?
|
|
253
|
254
|
private let paywallContentWidth: CGFloat = 520
|
|
254
|
255
|
private let launchWindowLeftOffset: CGFloat = 80
|
|
255
|
256
|
private var selectedPremiumPlan: PremiumPlan = .monthly
|
|
|
@@ -414,7 +415,9 @@ final class ViewController: NSViewController {
|
|
414
|
415
|
palette = Palette(isDarkMode: darkModeEnabled)
|
|
415
|
416
|
storeKitCoordinator.onEntitlementsChanged = { [weak self] hasPremiumAccess in
|
|
416
|
417
|
guard let self else { return }
|
|
417
|
|
- self.handlePremiumAccessChanged(hasPremiumAccess)
|
|
|
418
|
+ DispatchQueue.main.async {
|
|
|
419
|
+ self.handlePremiumAccessChanged(hasPremiumAccess)
|
|
|
420
|
+ }
|
|
418
|
421
|
}
|
|
419
|
422
|
migrateLegacyRatingStateIfNeeded()
|
|
420
|
423
|
beginUsageTrackingSessionIfNeeded()
|
|
|
@@ -1021,10 +1024,22 @@ private extension ViewController {
|
|
1021
|
1024
|
}
|
|
1022
|
1025
|
|
|
1023
|
1026
|
private func showPaywall(upgradeFlow: Bool = false, preferredPlan: PremiumPlan? = nil) {
|
|
|
1027
|
+ if !Thread.isMainThread {
|
|
|
1028
|
+ DispatchQueue.main.async { [weak self] in
|
|
|
1029
|
+ self?.showPaywall(upgradeFlow: upgradeFlow, preferredPlan: preferredPlan)
|
|
|
1030
|
+ }
|
|
|
1031
|
+ return
|
|
|
1032
|
+ }
|
|
1024
|
1033
|
paywallUpgradeFlowEnabled = upgradeFlow
|
|
1025
|
1034
|
if let preferredPlan {
|
|
1026
|
1035
|
selectedPremiumPlan = preferredPlan
|
|
1027
|
1036
|
}
|
|
|
1037
|
+ if let existingOverlay = paywallOverlayView {
|
|
|
1038
|
+ refreshPaywallStoreUI()
|
|
|
1039
|
+ existingOverlay.alphaValue = 1
|
|
|
1040
|
+ view.addSubview(existingOverlay, positioned: .above, relativeTo: nil)
|
|
|
1041
|
+ return
|
|
|
1042
|
+ }
|
|
1028
|
1043
|
if let existing = paywallWindow {
|
|
1029
|
1044
|
refreshPaywallStoreUI()
|
|
1030
|
1045
|
animatePaywallPresentation(existing)
|
|
|
@@ -1034,33 +1049,30 @@ private extension ViewController {
|
|
1034
|
1049
|
}
|
|
1035
|
1050
|
|
|
1036
|
1051
|
let content = makePaywallContent()
|
|
1037
|
|
- let controller = NSViewController()
|
|
1038
|
|
- controller.view = content
|
|
1039
|
|
-
|
|
1040
|
|
- let panel = NSPanel(
|
|
1041
|
|
- contentRect: NSRect(x: 0, y: 0, width: 640, height: 820),
|
|
1042
|
|
- styleMask: [.titled, .closable, .fullSizeContentView],
|
|
1043
|
|
- backing: .buffered,
|
|
1044
|
|
- defer: false
|
|
1045
|
|
- )
|
|
1046
|
|
- panel.title = "Get Premium"
|
|
1047
|
|
- panel.titleVisibility = .hidden
|
|
1048
|
|
- panel.titlebarAppearsTransparent = true
|
|
1049
|
|
- panel.isFloatingPanel = false
|
|
1050
|
|
- panel.level = .normal
|
|
1051
|
|
- panel.hidesOnDeactivate = true
|
|
1052
|
|
- panel.isReleasedWhenClosed = false
|
|
1053
|
|
- panel.delegate = self
|
|
1054
|
|
- panel.standardWindowButton(.closeButton)?.isHidden = true
|
|
1055
|
|
- panel.standardWindowButton(.miniaturizeButton)?.isHidden = true
|
|
1056
|
|
- panel.standardWindowButton(.zoomButton)?.isHidden = true
|
|
1057
|
|
- panel.center()
|
|
1058
|
|
- panel.contentViewController = controller
|
|
1059
|
|
- panel.alphaValue = 0
|
|
1060
|
|
- panel.makeKeyAndOrderFront(nil)
|
|
1061
|
|
- NSApp.activate(ignoringOtherApps: true)
|
|
1062
|
|
- paywallWindow = panel
|
|
1063
|
|
- animatePaywallPresentation(panel)
|
|
|
1052
|
+ let overlay = NSView()
|
|
|
1053
|
+ overlay.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1054
|
+ overlay.wantsLayer = true
|
|
|
1055
|
+ overlay.layer?.backgroundColor = palette.pageBackground.withAlphaComponent(0.98).cgColor
|
|
|
1056
|
+ overlay.alphaValue = 0
|
|
|
1057
|
+ overlay.addSubview(content)
|
|
|
1058
|
+ view.addSubview(overlay, positioned: .above, relativeTo: nil)
|
|
|
1059
|
+ NSLayoutConstraint.activate([
|
|
|
1060
|
+ overlay.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
|
1061
|
+ overlay.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
|
1062
|
+ overlay.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
1063
|
+ overlay.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
|
1064
|
+
|
|
|
1065
|
+ content.leadingAnchor.constraint(equalTo: overlay.leadingAnchor),
|
|
|
1066
|
+ content.trailingAnchor.constraint(equalTo: overlay.trailingAnchor),
|
|
|
1067
|
+ content.topAnchor.constraint(equalTo: overlay.topAnchor),
|
|
|
1068
|
+ content.bottomAnchor.constraint(equalTo: overlay.bottomAnchor)
|
|
|
1069
|
+ ])
|
|
|
1070
|
+ paywallOverlayView = overlay
|
|
|
1071
|
+ NSAnimationContext.runAnimationGroup { context in
|
|
|
1072
|
+ context.duration = 0.20
|
|
|
1073
|
+ context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
|
1074
|
+ overlay.animator().alphaValue = 1
|
|
|
1075
|
+ }
|
|
1064
|
1076
|
|
|
1065
|
1077
|
Task { [weak self] in
|
|
1066
|
1078
|
guard let self else { return }
|
|
|
@@ -1092,15 +1104,19 @@ private extension ViewController {
|
|
1092
|
1104
|
}
|
|
1093
|
1105
|
|
|
1094
|
1106
|
@objc private func closePaywallClicked(_ sender: Any?) {
|
|
1095
|
|
- if let win = paywallWindow {
|
|
1096
|
|
- win.performClose(nil)
|
|
1097
|
|
- return
|
|
1098
|
|
- }
|
|
1099
|
|
- if let gesture = sender as? NSGestureRecognizer, let win = gesture.view?.window {
|
|
1100
|
|
- win.performClose(nil)
|
|
|
1107
|
+ if let overlay = paywallOverlayView {
|
|
|
1108
|
+ paywallOverlayView = nil
|
|
|
1109
|
+ paywallUpgradeFlowEnabled = false
|
|
|
1110
|
+ NSAnimationContext.runAnimationGroup({ context in
|
|
|
1111
|
+ context.duration = 0.16
|
|
|
1112
|
+ context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
|
|
1113
|
+ overlay.animator().alphaValue = 0
|
|
|
1114
|
+ }, completionHandler: {
|
|
|
1115
|
+ overlay.removeFromSuperview()
|
|
|
1116
|
+ })
|
|
1101
|
1117
|
return
|
|
1102
|
1118
|
}
|
|
1103
|
|
- if let view = sender as? NSView, let win = view.window {
|
|
|
1119
|
+ if let win = paywallWindow {
|
|
1104
|
1120
|
win.performClose(nil)
|
|
1105
|
1121
|
return
|
|
1106
|
1122
|
}
|
|
|
@@ -1459,7 +1475,7 @@ private extension ViewController {
|
|
1459
|
1475
|
await self?.loadSchedule()
|
|
1460
|
1476
|
}
|
|
1461
|
1477
|
self.showSimpleAlert(title: "Purchase Complete", message: "Premium has been unlocked successfully.")
|
|
1462
|
|
- self.paywallWindow?.performClose(nil)
|
|
|
1478
|
+ self.closePaywallClicked(nil)
|
|
1463
|
1479
|
self.scheduleRatingPromptAfterPremiumUpgrade()
|
|
1464
|
1480
|
case .cancelled:
|
|
1465
|
1481
|
break
|
|
|
@@ -2621,9 +2637,10 @@ private extension ViewController {
|
|
2621
|
2637
|
let contentStack = NSStackView()
|
|
2622
|
2638
|
contentStack.translatesAutoresizingMaskIntoConstraints = false
|
|
2623
|
2639
|
contentStack.orientation = .vertical
|
|
2624
|
|
- contentStack.spacing = 12
|
|
2625
|
|
- contentStack.alignment = .leading
|
|
|
2640
|
+ contentStack.spacing = 14
|
|
|
2641
|
+ contentStack.alignment = .centerX
|
|
2626
|
2642
|
panel.addSubview(contentStack)
|
|
|
2643
|
+ let paywallLayoutWidth: CGFloat = 980
|
|
2627
|
2644
|
|
|
2628
|
2645
|
let topRow = NSStackView()
|
|
2629
|
2646
|
topRow.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -2631,7 +2648,7 @@ private extension ViewController {
|
|
2631
|
2648
|
topRow.alignment = .centerY
|
|
2632
|
2649
|
topRow.distribution = .fill
|
|
2633
|
2650
|
topRow.spacing = 10
|
|
2634
|
|
- topRow.addArrangedSubview(textLabel("Get Premium", font: NSFont.systemFont(ofSize: 24, weight: .bold), color: palette.textPrimary))
|
|
|
2651
|
+ topRow.addArrangedSubview(textLabel("Classroom Pro", font: NSFont.systemFont(ofSize: 27, weight: .bold), color: palette.textPrimary))
|
|
2635
|
2652
|
let topSpacer = NSView()
|
|
2636
|
2653
|
topSpacer.translatesAutoresizingMaskIntoConstraints = false
|
|
2637
|
2654
|
topRow.addArrangedSubview(topSpacer)
|
|
|
@@ -2657,47 +2674,83 @@ private extension ViewController {
|
|
2657
|
2674
|
closeButton.contentTintColor = hovering ? (self.darkModeEnabled ? .white : self.palette.textPrimary) : self.palette.textSecondary
|
|
2658
|
2675
|
}
|
|
2659
|
2676
|
topRow.addArrangedSubview(closeButton)
|
|
2660
|
|
- topRow.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth).isActive = true
|
|
2661
|
2677
|
contentStack.addArrangedSubview(topRow)
|
|
|
2678
|
+ topRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
|
2679
|
+
|
|
|
2680
|
+ let hero = roundedContainer(cornerRadius: 16, color: palette.sectionCard)
|
|
|
2681
|
+ hero.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2682
|
+ styleSurface(hero, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
2683
|
+ contentStack.addArrangedSubview(hero)
|
|
|
2684
|
+ hero.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
|
2685
|
+
|
|
|
2686
|
+ let heroBadge = textLabel("PREMIUM EXPERIENCE", font: NSFont.systemFont(ofSize: 10, weight: .bold), color: NSColor(calibratedRed: 0.42, green: 0.93, blue: 0.70, alpha: 1))
|
|
|
2687
|
+ hero.addSubview(heroBadge)
|
|
|
2688
|
+
|
|
|
2689
|
+ let heroTitle = textLabel("Elevate your classroom experience", font: NSFont.systemFont(ofSize: 22, weight: .bold), color: palette.textPrimary)
|
|
|
2690
|
+ let heroSubtitle = textLabel("Unlock intelligent automation, deep analytics, and premium support for modern educators.", font: NSFont.systemFont(ofSize: 13, weight: .medium), color: palette.textSecondary)
|
|
|
2691
|
+ heroTitle.maximumNumberOfLines = 2
|
|
|
2692
|
+ heroSubtitle.maximumNumberOfLines = 3
|
|
|
2693
|
+ hero.addSubview(heroTitle)
|
|
|
2694
|
+ hero.addSubview(heroSubtitle)
|
|
|
2695
|
+ NSLayoutConstraint.activate([
|
|
|
2696
|
+ heroBadge.leadingAnchor.constraint(equalTo: hero.leadingAnchor, constant: 16),
|
|
|
2697
|
+ heroBadge.topAnchor.constraint(equalTo: hero.topAnchor, constant: 14),
|
|
|
2698
|
+ heroTitle.leadingAnchor.constraint(equalTo: hero.leadingAnchor, constant: 16),
|
|
|
2699
|
+ heroTitle.trailingAnchor.constraint(lessThanOrEqualTo: hero.trailingAnchor, constant: -16),
|
|
|
2700
|
+ heroTitle.topAnchor.constraint(equalTo: heroBadge.bottomAnchor, constant: 10),
|
|
|
2701
|
+ heroSubtitle.leadingAnchor.constraint(equalTo: heroTitle.leadingAnchor),
|
|
|
2702
|
+ heroSubtitle.trailingAnchor.constraint(equalTo: hero.trailingAnchor, constant: -16),
|
|
|
2703
|
+ heroSubtitle.topAnchor.constraint(equalTo: heroTitle.bottomAnchor, constant: 8),
|
|
|
2704
|
+ heroSubtitle.bottomAnchor.constraint(equalTo: hero.bottomAnchor, constant: -16)
|
|
|
2705
|
+ ])
|
|
2662
|
2706
|
|
|
2663
|
|
- contentStack.addArrangedSubview(textLabel("Upgrade to unlock premium features.", font: NSFont.systemFont(ofSize: 12, weight: .medium), color: palette.textSecondary))
|
|
2664
|
|
- let benefits = paywallBenefitsSection()
|
|
2665
|
|
- contentStack.addArrangedSubview(benefits)
|
|
2666
|
|
- contentStack.setCustomSpacing(18, after: benefits)
|
|
|
2707
|
+ let benefitsRow = NSStackView()
|
|
|
2708
|
+ benefitsRow.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2709
|
+ benefitsRow.orientation = .horizontal
|
|
|
2710
|
+ benefitsRow.spacing = 8
|
|
|
2711
|
+ benefitsRow.distribution = .fillEqually
|
|
|
2712
|
+ benefitsRow.alignment = .centerY
|
|
|
2713
|
+ benefitsRow.addArrangedSubview(paywallBenefitItem(icon: "📅", text: "Manage classes"))
|
|
|
2714
|
+ benefitsRow.addArrangedSubview(paywallBenefitItem(icon: "🧠", text: "Smart to-dos"))
|
|
|
2715
|
+ benefitsRow.addArrangedSubview(paywallBenefitItem(icon: "⚡", text: "Faster workflow"))
|
|
|
2716
|
+ benefitsRow.addArrangedSubview(paywallBenefitItem(icon: "🛟", text: "Priority support"))
|
|
|
2717
|
+ contentStack.addArrangedSubview(benefitsRow)
|
|
|
2718
|
+ benefitsRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
|
2719
|
+
|
|
|
2720
|
+ let plansRow = NSStackView()
|
|
|
2721
|
+ plansRow.translatesAutoresizingMaskIntoConstraints = false
|
|
|
2722
|
+ plansRow.orientation = .horizontal
|
|
|
2723
|
+ plansRow.spacing = 10
|
|
|
2724
|
+ plansRow.distribution = .fillEqually
|
|
|
2725
|
+ plansRow.alignment = .centerY
|
|
2667
|
2726
|
|
|
2668
|
2727
|
let weeklyCard = paywallPlanCard(
|
|
2669
|
2728
|
title: "Weekly",
|
|
2670
|
2729
|
price: "PKR 1,100.00",
|
|
2671
|
|
- badge: "Basic Deal",
|
|
|
2730
|
+ badge: "Basic",
|
|
2672
|
2731
|
badgeColor: NSColor(calibratedRed: 1.0, green: 0.60, blue: 0.20, alpha: 1),
|
|
2673
|
2732
|
subtitle: nil,
|
|
2674
|
2733
|
plan: .weekly,
|
|
2675
|
2734
|
strikePrice: nil
|
|
2676
|
2735
|
)
|
|
2677
|
|
- contentStack.addArrangedSubview(weeklyCard)
|
|
2678
|
|
-
|
|
2679
|
2736
|
let monthlyCard = paywallPlanCard(
|
|
2680
|
2737
|
title: "Monthly",
|
|
2681
|
2738
|
price: "PKR 2,500.00",
|
|
2682
|
|
- badge: "Free Trial",
|
|
|
2739
|
+ badge: "Popular",
|
|
2683
|
2740
|
badgeColor: NSColor(calibratedRed: 0.19, green: 0.82, blue: 0.39, alpha: 1),
|
|
2684
|
2741
|
subtitle: "625.00/week",
|
|
2685
|
2742
|
plan: .monthly,
|
|
2686
|
2743
|
strikePrice: nil
|
|
2687
|
2744
|
)
|
|
2688
|
|
- contentStack.addArrangedSubview(monthlyCard)
|
|
2689
|
|
-
|
|
2690
|
2745
|
let yearlyCard = paywallPlanCard(
|
|
2691
|
2746
|
title: "Yearly",
|
|
2692
|
2747
|
price: "PKR 9,900.00",
|
|
2693
|
|
- badge: "Best Deal",
|
|
|
2748
|
+ badge: "Best Value",
|
|
2694
|
2749
|
badgeColor: NSColor(calibratedRed: 1.0, green: 0.60, blue: 0.20, alpha: 1),
|
|
2695
|
2750
|
subtitle: "190.38/week",
|
|
2696
|
2751
|
plan: .yearly,
|
|
2697
|
2752
|
strikePrice: nil
|
|
2698
|
2753
|
)
|
|
2699
|
|
- contentStack.addArrangedSubview(yearlyCard)
|
|
2700
|
|
-
|
|
2701
|
2754
|
let lifetimeCard = paywallPlanCard(
|
|
2702
|
2755
|
title: "Lifetime",
|
|
2703
|
2756
|
price: "PKR 14,900.00",
|
|
|
@@ -2707,9 +2760,13 @@ private extension ViewController {
|
|
2707
|
2760
|
plan: .lifetime,
|
|
2708
|
2761
|
strikePrice: "PKR 29,800.00"
|
|
2709
|
2762
|
)
|
|
2710
|
|
- contentStack.addArrangedSubview(lifetimeCard)
|
|
|
2763
|
+ plansRow.addArrangedSubview(weeklyCard)
|
|
|
2764
|
+ plansRow.addArrangedSubview(monthlyCard)
|
|
|
2765
|
+ plansRow.addArrangedSubview(yearlyCard)
|
|
|
2766
|
+ plansRow.addArrangedSubview(lifetimeCard)
|
|
|
2767
|
+ contentStack.addArrangedSubview(plansRow)
|
|
|
2768
|
+ plansRow.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
2711
|
2769
|
updatePaywallPlanSelection()
|
|
2712
|
|
- contentStack.setCustomSpacing(20, after: lifetimeCard)
|
|
2713
|
2770
|
|
|
2714
|
2771
|
let offer = textLabel(paywallOfferText(for: selectedPremiumPlan), font: NSFont.systemFont(ofSize: 13, weight: .semibold), color: palette.textPrimary)
|
|
2715
|
2772
|
offer.alignment = .center
|
|
|
@@ -2717,14 +2774,13 @@ private extension ViewController {
|
|
2717
|
2774
|
let offerWrap = NSView()
|
|
2718
|
2775
|
offerWrap.translatesAutoresizingMaskIntoConstraints = false
|
|
2719
|
2776
|
offerWrap.addSubview(offer)
|
|
|
2777
|
+ contentStack.addArrangedSubview(offerWrap)
|
|
|
2778
|
+ offerWrap.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
2720
|
2779
|
NSLayoutConstraint.activate([
|
|
2721
|
|
- offerWrap.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth),
|
|
2722
|
2780
|
offer.centerXAnchor.constraint(equalTo: offerWrap.centerXAnchor),
|
|
2723
|
|
- offer.topAnchor.constraint(equalTo: offerWrap.topAnchor, constant: 6),
|
|
|
2781
|
+ offer.topAnchor.constraint(equalTo: offerWrap.topAnchor, constant: 4),
|
|
2724
|
2782
|
offer.bottomAnchor.constraint(equalTo: offerWrap.bottomAnchor, constant: -2)
|
|
2725
|
2783
|
])
|
|
2726
|
|
- contentStack.addArrangedSubview(offerWrap)
|
|
2727
|
|
- contentStack.setCustomSpacing(18, after: offerWrap)
|
|
2728
|
2784
|
|
|
2729
|
2785
|
let continueButton = HoverButton(title: "", target: self, action: #selector(paywallContinueClicked(_:)))
|
|
2730
|
2786
|
continueButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -2734,7 +2790,6 @@ private extension ViewController {
|
|
2734
|
2790
|
continueButton.layer?.cornerRadius = 14
|
|
2735
|
2791
|
continueButton.layer?.backgroundColor = palette.primaryBlue.cgColor
|
|
2736
|
2792
|
continueButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
|
|
2737
|
|
- continueButton.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth).isActive = true
|
|
2738
|
2793
|
styleSurface(continueButton, borderColor: palette.primaryBlueBorder, borderWidth: 1, shadow: true)
|
|
2739
|
2794
|
let continueLabel = textLabel("Continue", font: NSFont.systemFont(ofSize: 16, weight: .bold), color: .white)
|
|
2740
|
2795
|
continueButton.addSubview(continueLabel)
|
|
|
@@ -2752,30 +2807,30 @@ private extension ViewController {
|
|
2752
|
2807
|
paywallContinueButton = continueButton
|
|
2753
|
2808
|
paywallContinueLabel = continueLabel
|
|
2754
|
2809
|
contentStack.addArrangedSubview(continueButton)
|
|
2755
|
|
- contentStack.setCustomSpacing(16, after: continueButton)
|
|
|
2810
|
+ continueButton.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
2756
|
2811
|
|
|
2757
|
2812
|
let secure = textLabel("Secured by Apple. Cancel anytime.", font: NSFont.systemFont(ofSize: 12, weight: .semibold), color: palette.textSecondary)
|
|
2758
|
2813
|
secure.alignment = .center
|
|
2759
|
2814
|
let secureWrap = NSView()
|
|
2760
|
2815
|
secureWrap.translatesAutoresizingMaskIntoConstraints = false
|
|
2761
|
2816
|
secureWrap.addSubview(secure)
|
|
|
2817
|
+ contentStack.addArrangedSubview(secureWrap)
|
|
|
2818
|
+ secureWrap.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
2762
|
2819
|
NSLayoutConstraint.activate([
|
|
2763
|
|
- secureWrap.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth),
|
|
2764
|
2820
|
secure.centerXAnchor.constraint(equalTo: secureWrap.centerXAnchor),
|
|
2765
|
|
- secure.topAnchor.constraint(equalTo: secureWrap.topAnchor, constant: 4),
|
|
2766
|
|
- secure.bottomAnchor.constraint(equalTo: secureWrap.bottomAnchor, constant: -8)
|
|
|
2821
|
+ secure.topAnchor.constraint(equalTo: secureWrap.topAnchor, constant: 2),
|
|
|
2822
|
+ secure.bottomAnchor.constraint(equalTo: secureWrap.bottomAnchor, constant: -4)
|
|
2767
|
2823
|
])
|
|
2768
|
|
- contentStack.addArrangedSubview(secureWrap)
|
|
2769
|
|
- contentStack.setCustomSpacing(16, after: secureWrap)
|
|
2770
|
2824
|
|
|
2771
|
2825
|
let footer = paywallFooterLinks()
|
|
2772
|
2826
|
contentStack.addArrangedSubview(footer)
|
|
|
2827
|
+ footer.widthAnchor.constraint(equalTo: contentStack.widthAnchor).isActive = true
|
|
2773
|
2828
|
|
|
2774
|
2829
|
NSLayoutConstraint.activate([
|
|
2775
|
|
- contentStack.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
|
|
2776
|
|
- contentStack.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
|
|
2777
|
|
- contentStack.topAnchor.constraint(equalTo: panel.topAnchor, constant: 16),
|
|
2778
|
|
- contentStack.bottomAnchor.constraint(lessThanOrEqualTo: panel.bottomAnchor, constant: -12)
|
|
|
2830
|
+ contentStack.centerXAnchor.constraint(equalTo: panel.centerXAnchor),
|
|
|
2831
|
+ contentStack.widthAnchor.constraint(equalToConstant: paywallLayoutWidth),
|
|
|
2832
|
+ contentStack.topAnchor.constraint(equalTo: panel.topAnchor, constant: 20),
|
|
|
2833
|
+ contentStack.bottomAnchor.constraint(lessThanOrEqualTo: panel.bottomAnchor, constant: -16)
|
|
2779
|
2834
|
])
|
|
2780
|
2835
|
|
|
2781
|
2836
|
refreshPaywallStoreUI()
|
|
|
@@ -2797,8 +2852,8 @@ private extension ViewController {
|
|
2797
|
2852
|
wrapper.bezelStyle = .regularSquare
|
|
2798
|
2853
|
wrapper.wantsLayer = true
|
|
2799
|
2854
|
wrapper.layer?.backgroundColor = NSColor.clear.cgColor
|
|
2800
|
|
- wrapper.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth).isActive = true
|
|
2801
|
|
- wrapper.heightAnchor.constraint(equalToConstant: 94).isActive = true
|
|
|
2855
|
+ wrapper.widthAnchor.constraint(greaterThanOrEqualToConstant: 160).isActive = true
|
|
|
2856
|
+ wrapper.heightAnchor.constraint(equalToConstant: 162).isActive = true
|
|
2802
|
2857
|
wrapper.tag = plan.rawValue
|
|
2803
|
2858
|
|
|
2804
|
2859
|
let card = HoverTrackingView()
|
|
|
@@ -2806,7 +2861,7 @@ private extension ViewController {
|
|
2806
|
2861
|
card.wantsLayer = true
|
|
2807
|
2862
|
card.layer?.cornerRadius = 16
|
|
2808
|
2863
|
card.layer?.backgroundColor = palette.sectionCard.cgColor
|
|
2809
|
|
- card.heightAnchor.constraint(equalToConstant: 82).isActive = true
|
|
|
2864
|
+ card.heightAnchor.constraint(equalToConstant: 150).isActive = true
|
|
2810
|
2865
|
wrapper.addSubview(card)
|
|
2811
|
2866
|
NSLayoutConstraint.activate([
|
|
2812
|
2867
|
card.leadingAnchor.constraint(equalTo: wrapper.leadingAnchor),
|
|
|
@@ -2842,14 +2897,15 @@ private extension ViewController {
|
|
2842
|
2897
|
paywallPriceLabels[plan] = priceLabel
|
|
2843
|
2898
|
|
|
2844
|
2899
|
NSLayoutConstraint.activate([
|
|
2845
|
|
- badgeWrap.centerXAnchor.constraint(equalTo: card.centerXAnchor),
|
|
|
2900
|
+ badgeWrap.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 12),
|
|
2846
|
2901
|
badgeWrap.centerYAnchor.constraint(equalTo: card.topAnchor),
|
|
2847
|
2902
|
|
|
2848
|
|
- titleLabel.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
|
|
|
2903
|
+ titleLabel.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 12),
|
|
2849
|
2904
|
titleLabel.topAnchor.constraint(equalTo: card.topAnchor, constant: 34),
|
|
2850
|
2905
|
|
|
2851
|
|
- priceLabel.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16),
|
|
2852
|
|
- priceLabel.topAnchor.constraint(equalTo: card.topAnchor, constant: 32)
|
|
|
2906
|
+ priceLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor),
|
|
|
2907
|
+ priceLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
|
|
|
2908
|
+ priceLabel.trailingAnchor.constraint(lessThanOrEqualTo: card.trailingAnchor, constant: -12)
|
|
2853
|
2909
|
])
|
|
2854
|
2910
|
|
|
2855
|
2911
|
if let subtitle {
|
|
|
@@ -2857,8 +2913,9 @@ private extension ViewController {
|
|
2857
|
2913
|
card.addSubview(sub)
|
|
2858
|
2914
|
paywallSubtitleLabels[plan] = sub
|
|
2859
|
2915
|
NSLayoutConstraint.activate([
|
|
2860
|
|
- sub.trailingAnchor.constraint(equalTo: priceLabel.trailingAnchor),
|
|
2861
|
|
- sub.topAnchor.constraint(equalTo: priceLabel.bottomAnchor, constant: 0)
|
|
|
2916
|
+ sub.leadingAnchor.constraint(equalTo: priceLabel.leadingAnchor),
|
|
|
2917
|
+ sub.topAnchor.constraint(equalTo: priceLabel.bottomAnchor, constant: 2),
|
|
|
2918
|
+ sub.trailingAnchor.constraint(lessThanOrEqualTo: card.trailingAnchor, constant: -12)
|
|
2862
|
2919
|
])
|
|
2863
|
2920
|
}
|
|
2864
|
2921
|
|
|
|
@@ -2866,8 +2923,9 @@ private extension ViewController {
|
|
2866
|
2923
|
let strike = textLabel(strikePrice, font: NSFont.systemFont(ofSize: 12, weight: .medium), color: NSColor.systemRed)
|
|
2867
|
2924
|
card.addSubview(strike)
|
|
2868
|
2925
|
NSLayoutConstraint.activate([
|
|
2869
|
|
- strike.trailingAnchor.constraint(equalTo: priceLabel.trailingAnchor),
|
|
2870
|
|
- strike.topAnchor.constraint(equalTo: priceLabel.bottomAnchor, constant: 4)
|
|
|
2926
|
+ strike.leadingAnchor.constraint(equalTo: priceLabel.leadingAnchor),
|
|
|
2927
|
+ strike.topAnchor.constraint(equalTo: priceLabel.bottomAnchor, constant: 4),
|
|
|
2928
|
+ strike.trailingAnchor.constraint(lessThanOrEqualTo: card.trailingAnchor, constant: -12)
|
|
2871
|
2929
|
])
|
|
2872
|
2930
|
}
|
|
2873
|
2931
|
|
|
|
@@ -2885,7 +2943,6 @@ private extension ViewController {
|
|
2885
|
2943
|
let wrap = NSView()
|
|
2886
|
2944
|
wrap.translatesAutoresizingMaskIntoConstraints = false
|
|
2887
|
2945
|
wrap.heightAnchor.constraint(equalToConstant: 34).isActive = true
|
|
2888
|
|
- wrap.widthAnchor.constraint(greaterThanOrEqualToConstant: paywallContentWidth).isActive = true
|
|
2889
|
2946
|
|
|
2890
|
2947
|
let row = NSStackView()
|
|
2891
|
2948
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -2967,7 +3024,7 @@ private extension ViewController {
|
|
2967
|
3024
|
card.wantsLayer = true
|
|
2968
|
3025
|
card.layer?.cornerRadius = 10
|
|
2969
|
3026
|
card.layer?.backgroundColor = palette.inputBackground.cgColor
|
|
2970
|
|
- card.heightAnchor.constraint(equalToConstant: 36).isActive = true
|
|
|
3027
|
+ card.heightAnchor.constraint(equalToConstant: 44).isActive = true
|
|
2971
|
3028
|
styleSurface(card, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
2972
|
3029
|
|
|
2973
|
3030
|
let iconWrap = roundedContainer(cornerRadius: 8, color: palette.inputBackground)
|