SidebarView.swift 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import Cocoa
  2. enum SidebarDestination: Int, CaseIterable {
  3. case home
  4. case scan
  5. case print
  6. case scanAndHome
  7. case settings
  8. }
  9. final class SidebarView: NSView, AppearanceRefreshable {
  10. private var appNameLabel: NSTextField!
  11. private var premiumCard: SidebarPremiumCardView!
  12. var onDestinationSelected: ((SidebarDestination) -> Void)?
  13. var onPremiumUpgradeTapped: (() -> Void)?
  14. var onManageSubscriptionTapped: (() -> Void)?
  15. private var navItems: [SidebarNavItem] = []
  16. init() {
  17. super.init(frame: .zero)
  18. wantsLayer = true
  19. layer?.backgroundColor = AppTheme.sidebarBackground.cgColor
  20. translatesAutoresizingMaskIntoConstraints = false
  21. setup()
  22. }
  23. @available(*, unavailable)
  24. required init?(coder: NSCoder) { nil }
  25. func select(_ destination: SidebarDestination) {
  26. for (index, item) in navItems.enumerated() {
  27. item.isSelected = navDestination(at: index) == destination
  28. }
  29. }
  30. func refreshPremiumState() {
  31. premiumCard?.update(isPremium: StoreManager.shared.isPremium)
  32. }
  33. func refreshAppearance() {
  34. layer?.backgroundColor = AppTheme.sidebarBackground.cgColor
  35. appNameLabel?.refreshThemeLabelColor()
  36. premiumCard?.refreshAppearance()
  37. }
  38. private func navDestination(at index: Int) -> SidebarDestination {
  39. switch index {
  40. case 0: return .home
  41. case 1: return .scan
  42. case 2: return .print
  43. case 3: return .settings
  44. default: return .home
  45. }
  46. }
  47. private func setup() {
  48. let logoContainer = NSView()
  49. logoContainer.translatesAutoresizingMaskIntoConstraints = false
  50. let logoIcon = NSImageView()
  51. logoIcon.translatesAutoresizingMaskIntoConstraints = false
  52. logoIcon.image = NSImage(named: "AppLogo")
  53. logoIcon.imageScaling = .scaleProportionallyUpOrDown
  54. appNameLabel = NSTextField.themeLabel("Smart Printer", style: .primary, font: AppTheme.semiboldFont(size: 15))
  55. appNameLabel.alignment = .center
  56. appNameLabel.translatesAutoresizingMaskIntoConstraints = false
  57. let navStack = NSStackView()
  58. navStack.orientation = .vertical
  59. navStack.spacing = 4
  60. navStack.alignment = .leading
  61. navStack.translatesAutoresizingMaskIntoConstraints = false
  62. let bottomNavStack = NSStackView()
  63. bottomNavStack.orientation = .vertical
  64. bottomNavStack.spacing = 12
  65. bottomNavStack.alignment = .leading
  66. bottomNavStack.translatesAutoresizingMaskIntoConstraints = false
  67. let homeItem = SidebarNavItem(title: "Home", symbolName: "house.fill", isSelected: true)
  68. let scanItem = SidebarNavItem(title: "Scan", symbolName: "viewfinder")
  69. let printItem = SidebarNavItem(title: "Print", symbolName: "printer")
  70. let settingsItem = SidebarNavItem(title: "Settings", symbolName: "gearshape")
  71. premiumCard = SidebarPremiumCardView()
  72. premiumCard.onUpgradeTapped = { [weak self] in
  73. self?.onPremiumUpgradeTapped?()
  74. }
  75. premiumCard.onManageSubscriptionTapped = { [weak self] in
  76. self?.onManageSubscriptionTapped?()
  77. }
  78. navItems = [homeItem, scanItem, printItem, settingsItem]
  79. for (index, item) in navItems.enumerated() {
  80. item.onClick = { [weak self] in
  81. guard let self else { return }
  82. let destination = self.navDestination(at: index)
  83. self.select(destination)
  84. self.onDestinationSelected?(destination)
  85. }
  86. }
  87. for item in navItems {
  88. navStack.addArrangedSubview(item)
  89. }
  90. bottomNavStack.addArrangedSubview(premiumCard)
  91. addSubview(logoContainer)
  92. logoContainer.addSubview(logoIcon)
  93. addSubview(appNameLabel)
  94. addSubview(navStack)
  95. addSubview(bottomNavStack)
  96. let divider = NSBox()
  97. divider.boxType = .separator
  98. divider.translatesAutoresizingMaskIntoConstraints = false
  99. addSubview(divider)
  100. NSLayoutConstraint.activate([
  101. widthAnchor.constraint(equalToConstant: AppTheme.sidebarWidth),
  102. logoContainer.topAnchor.constraint(equalTo: topAnchor, constant: 52),
  103. logoContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
  104. logoContainer.widthAnchor.constraint(equalToConstant: 56),
  105. logoContainer.heightAnchor.constraint(equalToConstant: 56),
  106. logoIcon.centerXAnchor.constraint(equalTo: logoContainer.centerXAnchor),
  107. logoIcon.centerYAnchor.constraint(equalTo: logoContainer.centerYAnchor),
  108. logoIcon.widthAnchor.constraint(equalToConstant: 48),
  109. logoIcon.heightAnchor.constraint(equalToConstant: 48),
  110. appNameLabel.topAnchor.constraint(equalTo: logoContainer.bottomAnchor, constant: 8),
  111. appNameLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
  112. navStack.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: 32),
  113. navStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
  114. navStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12),
  115. bottomNavStack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
  116. bottomNavStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
  117. bottomNavStack.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
  118. homeItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
  119. scanItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
  120. printItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
  121. settingsItem.widthAnchor.constraint(equalTo: navStack.widthAnchor),
  122. premiumCard.widthAnchor.constraint(equalTo: bottomNavStack.widthAnchor),
  123. divider.trailingAnchor.constraint(equalTo: trailingAnchor),
  124. divider.topAnchor.constraint(equalTo: topAnchor),
  125. divider.bottomAnchor.constraint(equalTo: bottomAnchor),
  126. divider.widthAnchor.constraint(equalToConstant: 1),
  127. ])
  128. }
  129. }