| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- //
- // ViewController.swift
- // smart_printer
- //
- import Cocoa
- class ViewController: NSViewController {
- private var sidebar: SidebarView!
- private var homeContentView: NSView!
- private var scanContentView: NSView!
- private var scanAndHomeContentView: NSView!
- private var contentContainer: NSView!
- override func loadView() {
- let container = NSView(frame: NSRect(x: 0, y: 0, width: AppTheme.windowWidth, height: AppTheme.windowHeight))
- container.autoresizingMask = [.width, .height]
- container.wantsLayer = true
- container.layer?.backgroundColor = AppTheme.background.cgColor
- view = container
- }
- override func viewDidLoad() {
- super.viewDidLoad()
- setupLayout()
- }
- private func setupLayout() {
- sidebar = SidebarView()
- let mainContent = NSView()
- mainContent.translatesAutoresizingMaskIntoConstraints = false
- mainContent.wantsLayer = true
- mainContent.layer?.backgroundColor = AppTheme.background.cgColor
- let header = makeHeader()
- contentContainer = NSView()
- contentContainer.translatesAutoresizingMaskIntoConstraints = false
- homeContentView = makeHomeContentView()
- scanContentView = makeScanContentView()
- scanAndHomeContentView = makeScanAndHomeContentView()
- contentContainer.addSubview(homeContentView)
- contentContainer.addSubview(scanContentView)
- contentContainer.addSubview(scanAndHomeContentView)
- let wavePattern = WavePatternView()
- wavePattern.translatesAutoresizingMaskIntoConstraints = false
- view.addSubview(sidebar)
- view.addSubview(mainContent)
- mainContent.addSubview(header)
- mainContent.addSubview(contentContainer)
- mainContent.addSubview(wavePattern)
- sidebar.onDestinationSelected = { [weak self] destination in
- self?.showDestination(destination)
- }
- NSLayoutConstraint.activate([
- sidebar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- sidebar.topAnchor.constraint(equalTo: view.topAnchor),
- sidebar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- mainContent.leadingAnchor.constraint(equalTo: sidebar.trailingAnchor),
- mainContent.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- mainContent.topAnchor.constraint(equalTo: view.topAnchor),
- mainContent.bottomAnchor.constraint(equalTo: view.bottomAnchor),
- header.leadingAnchor.constraint(equalTo: mainContent.leadingAnchor),
- header.trailingAnchor.constraint(equalTo: mainContent.trailingAnchor),
- header.topAnchor.constraint(equalTo: mainContent.topAnchor, constant: 16),
- header.heightAnchor.constraint(equalToConstant: 44),
- contentContainer.leadingAnchor.constraint(equalTo: mainContent.leadingAnchor),
- contentContainer.trailingAnchor.constraint(equalTo: mainContent.trailingAnchor),
- contentContainer.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 8),
- contentContainer.bottomAnchor.constraint(equalTo: mainContent.bottomAnchor),
- wavePattern.trailingAnchor.constraint(equalTo: mainContent.trailingAnchor),
- wavePattern.bottomAnchor.constraint(equalTo: mainContent.bottomAnchor),
- wavePattern.widthAnchor.constraint(equalToConstant: 200),
- wavePattern.heightAnchor.constraint(equalToConstant: 140),
- ])
- pinContentView(homeContentView)
- pinContentView(scanContentView)
- pinContentView(scanAndHomeContentView)
- showDestination(.home)
- }
- private func pinContentView(_ contentView: NSView) {
- NSLayoutConstraint.activate([
- contentView.leadingAnchor.constraint(equalTo: contentContainer.leadingAnchor),
- contentView.trailingAnchor.constraint(equalTo: contentContainer.trailingAnchor),
- contentView.topAnchor.constraint(equalTo: contentContainer.topAnchor),
- contentView.bottomAnchor.constraint(equalTo: contentContainer.bottomAnchor),
- ])
- }
- private func showDestination(_ destination: SidebarDestination) {
- homeContentView.isHidden = destination != .home
- scanContentView.isHidden = destination != .scan
- scanAndHomeContentView.isHidden = destination != .scanAndHome
- }
- private func makeHomeContentView() -> NSView {
- let container = NSView()
- container.translatesAutoresizingMaskIntoConstraints = false
- let scrollView = makeScrollView()
- container.addSubview(scrollView)
- NSLayoutConstraint.activate([
- scrollView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
- scrollView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
- scrollView.topAnchor.constraint(equalTo: container.topAnchor),
- scrollView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
- ])
- return container
- }
- private func makeScanContentView() -> NSView {
- makeDestinationScrollView(
- title: "Scan",
- features: [
- FeatureCardData(title: "Scan File", subtitle: "Scan any document", iconKind: .scanFile),
- FeatureCardData(title: "OCR File", subtitle: "Scan and print text from images", iconKind: .ocrFile),
- ]
- )
- }
- private func makeScanAndHomeContentView() -> NSView {
- makeDestinationScrollView(
- title: "Premium",
- features: [
- FeatureCardData(title: "From Photos", subtitle: "Take a photo from gallery", iconKind: .scanFile),
- FeatureCardData(title: "Scan File", subtitle: "Scan any document", iconKind: .scanFile),
- FeatureCardData(title: "Print Text", subtitle: "Type text and print", iconKind: .printText),
- FeatureCardData(title: "Print Contacts", subtitle: "Print your contacts", iconKind: .printContacts),
- ]
- )
- }
- private func makeDestinationScrollView(title: String, features: [FeatureCardData]) -> NSView {
- let container = NSView()
- container.translatesAutoresizingMaskIntoConstraints = false
- container.isHidden = true
- let scrollView = NSScrollView()
- scrollView.translatesAutoresizingMaskIntoConstraints = false
- scrollView.hasVerticalScroller = true
- scrollView.hasHorizontalScroller = false
- scrollView.autohidesScrollers = true
- scrollView.drawsBackground = false
- scrollView.borderType = .noBorder
- let documentView = FlippedDocumentView()
- documentView.translatesAutoresizingMaskIntoConstraints = false
- let sectionTitle = makeSectionTitle(title, icon: .grid)
- let grid = makeFeatureRow(features: features)
- documentView.addSubview(sectionTitle)
- documentView.addSubview(grid)
- scrollView.documentView = documentView
- container.addSubview(scrollView)
- let contentGuide = scrollView.contentView
- NSLayoutConstraint.activate([
- scrollView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
- scrollView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
- scrollView.topAnchor.constraint(equalTo: container.topAnchor),
- scrollView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
- documentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
- documentView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
- documentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
- documentView.widthAnchor.constraint(equalTo: contentGuide.widthAnchor),
- sectionTitle.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
- sectionTitle.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
- sectionTitle.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 8),
- grid.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
- grid.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
- grid.topAnchor.constraint(equalTo: sectionTitle.bottomAnchor, constant: 16),
- grid.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -40),
- ])
- return container
- }
- private func makeHeader() -> NSView {
- let header = NSView()
- header.translatesAutoresizingMaskIntoConstraints = false
- let titleLabel = NSTextField(labelWithString: "Smart Printer")
- titleLabel.font = AppTheme.semiboldFont(size: 18)
- titleLabel.textColor = AppTheme.textPrimary
- titleLabel.alignment = .center
- titleLabel.translatesAutoresizingMaskIntoConstraints = false
- let settingsButton = NSButton()
- settingsButton.isBordered = false
- settingsButton.translatesAutoresizingMaskIntoConstraints = false
- if let image = NSImage(systemSymbolName: "gearshape", accessibilityDescription: "Settings") {
- let config = NSImage.SymbolConfiguration(pointSize: 18, weight: .regular)
- settingsButton.image = image.withSymbolConfiguration(config)
- }
- settingsButton.contentTintColor = AppTheme.textSecondary
- header.addSubview(titleLabel)
- header.addSubview(settingsButton)
- NSLayoutConstraint.activate([
- titleLabel.centerXAnchor.constraint(equalTo: header.centerXAnchor),
- titleLabel.centerYAnchor.constraint(equalTo: header.centerYAnchor),
- settingsButton.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -28),
- settingsButton.centerYAnchor.constraint(equalTo: header.centerYAnchor),
- settingsButton.widthAnchor.constraint(equalToConstant: 32),
- settingsButton.heightAnchor.constraint(equalToConstant: 32),
- ])
- return header
- }
- private func makeScrollView() -> NSScrollView {
- let scrollView = NSScrollView()
- scrollView.translatesAutoresizingMaskIntoConstraints = false
- scrollView.hasVerticalScroller = true
- scrollView.hasHorizontalScroller = false
- scrollView.autohidesScrollers = true
- scrollView.drawsBackground = false
- scrollView.borderType = .noBorder
- let documentView = FlippedDocumentView()
- documentView.translatesAutoresizingMaskIntoConstraints = false
- let quickStartSection = makeQuickStartSection()
- let createPrintSection = makeCreatePrintSection()
- documentView.addSubview(quickStartSection)
- documentView.addSubview(createPrintSection)
- scrollView.documentView = documentView
- let contentGuide = scrollView.contentView
- NSLayoutConstraint.activate([
- documentView.topAnchor.constraint(equalTo: contentGuide.topAnchor),
- documentView.leadingAnchor.constraint(equalTo: contentGuide.leadingAnchor),
- documentView.trailingAnchor.constraint(equalTo: contentGuide.trailingAnchor),
- documentView.widthAnchor.constraint(equalTo: contentGuide.widthAnchor),
- quickStartSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
- quickStartSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
- quickStartSection.topAnchor.constraint(equalTo: documentView.topAnchor, constant: 8),
- createPrintSection.leadingAnchor.constraint(equalTo: documentView.leadingAnchor, constant: AppTheme.contentPadding),
- createPrintSection.trailingAnchor.constraint(equalTo: documentView.trailingAnchor, constant: -AppTheme.contentPadding),
- createPrintSection.topAnchor.constraint(equalTo: quickStartSection.bottomAnchor, constant: 36),
- createPrintSection.bottomAnchor.constraint(equalTo: documentView.bottomAnchor, constant: -40),
- ])
- return scrollView
- }
- private enum SectionTitleIcon {
- case none
- case sparkle
- case grid
- }
- private func makeSectionTitle(_ text: String, icon: SectionTitleIcon = .none) -> NSView {
- let container = NSView()
- container.translatesAutoresizingMaskIntoConstraints = false
- var leadingAnchor = container.leadingAnchor
- var iconSpacing: CGFloat = 0
- if icon != .none {
- let titleIcon: NSView
- switch icon {
- case .sparkle:
- titleIcon = SparkleIconView()
- case .grid:
- let gridIcon = NSImageView()
- if let image = NSImage(systemSymbolName: "square.grid.2x2", accessibilityDescription: nil) {
- let config = NSImage.SymbolConfiguration(pointSize: 16, weight: .medium)
- gridIcon.image = image.withSymbolConfiguration(config)
- }
- gridIcon.contentTintColor = AppTheme.textPrimary
- titleIcon = gridIcon
- case .none:
- titleIcon = NSView()
- }
- titleIcon.translatesAutoresizingMaskIntoConstraints = false
- container.addSubview(titleIcon)
- NSLayoutConstraint.activate([
- titleIcon.leadingAnchor.constraint(equalTo: container.leadingAnchor),
- titleIcon.centerYAnchor.constraint(equalTo: container.centerYAnchor),
- titleIcon.widthAnchor.constraint(equalToConstant: 24),
- titleIcon.heightAnchor.constraint(equalToConstant: 24),
- ])
- leadingAnchor = titleIcon.trailingAnchor
- iconSpacing = 8
- }
- let label = NSTextField(labelWithString: text)
- label.font = .systemFont(ofSize: 20, weight: .bold)
- label.textColor = AppTheme.textPrimary
- label.translatesAutoresizingMaskIntoConstraints = false
- container.addSubview(label)
- NSLayoutConstraint.activate([
- container.heightAnchor.constraint(equalToConstant: 28),
- label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: iconSpacing),
- label.centerYAnchor.constraint(equalTo: container.centerYAnchor),
- label.trailingAnchor.constraint(equalTo: container.trailingAnchor),
- ])
- return container
- }
- private func makeQuickStartSection() -> NSView {
- let section = NSView()
- section.translatesAutoresizingMaskIntoConstraints = false
- let title = makeSectionTitle("Quick Start", icon: .sparkle)
- section.addSubview(title)
- let cardsStack = NSStackView()
- cardsStack.orientation = .horizontal
- cardsStack.spacing = AppTheme.quickStartSpacing
- cardsStack.distribution = .fillEqually
- cardsStack.translatesAutoresizingMaskIntoConstraints = false
- let cards: [QuickStartCardData] = [
- QuickStartCardData(
- title: "From Photos",
- subtitle: "Take a photo from gallery",
- buttonTitle: "Open Gallery",
- accentColor: AppTheme.blue,
- gradientColors: [AppTheme.blueLight, NSColor(red: 0.82, green: 0.90, blue: 1.0, alpha: 1)],
- iconKind: .photos
- ),
- QuickStartCardData(
- title: "From Files",
- subtitle: "Take a photo from file manager",
- buttonTitle: "Browse Files",
- accentColor: AppTheme.green,
- gradientColors: [AppTheme.greenLight, NSColor(red: 0.78, green: 0.94, blue: 0.84, alpha: 1)],
- iconKind: .files
- ),
- QuickStartCardData(
- title: "Import File",
- subtitle: "Import a file from storage",
- buttonTitle: "Import Now",
- accentColor: AppTheme.orange,
- gradientColors: [AppTheme.orangeLight, NSColor(red: 1.0, green: 0.88, blue: 0.76, alpha: 1)],
- iconKind: .importFile
- ),
- ]
- for cardData in cards {
- cardsStack.addArrangedSubview(QuickStartCardView(data: cardData))
- }
- section.addSubview(cardsStack)
- NSLayoutConstraint.activate([
- title.leadingAnchor.constraint(equalTo: section.leadingAnchor),
- title.topAnchor.constraint(equalTo: section.topAnchor),
- cardsStack.leadingAnchor.constraint(equalTo: section.leadingAnchor),
- cardsStack.trailingAnchor.constraint(equalTo: section.trailingAnchor),
- cardsStack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 16),
- cardsStack.bottomAnchor.constraint(equalTo: section.bottomAnchor),
- ])
- return section
- }
- private func makeCreatePrintSection() -> NSView {
- let section = NSView()
- section.translatesAutoresizingMaskIntoConstraints = false
- let title = makeSectionTitle("Create & Print", icon: .grid)
- section.addSubview(title)
- let features: [FeatureCardData] = [
- FeatureCardData(title: "Scan File", subtitle: "Scan any document", iconKind: .scanFile),
- FeatureCardData(title: "Print Text", subtitle: "Type text and print", iconKind: .printText),
- FeatureCardData(title: "Print Contacts", subtitle: "Print your contacts", iconKind: .printContacts),
- FeatureCardData(title: "Print Website", subtitle: "Print any website", iconKind: .printWebsite),
- FeatureCardData(title: "Draw & Print", subtitle: "Add drawings, text and more", iconKind: .drawPrint),
- FeatureCardData(title: "OCR File", subtitle: "Scan and print text from images", iconKind: .ocrFile),
- ]
- let topRow = makeFeatureRow(features: Array(features.prefix(4)))
- let bottomRow = makeFeatureRow(features: Array(features.suffix(2)))
- let grid = NSStackView(views: [topRow, bottomRow])
- grid.orientation = .vertical
- grid.spacing = AppTheme.featureGridSpacing
- grid.distribution = .fillEqually
- grid.translatesAutoresizingMaskIntoConstraints = false
- section.addSubview(grid)
- NSLayoutConstraint.activate([
- title.leadingAnchor.constraint(equalTo: section.leadingAnchor),
- title.topAnchor.constraint(equalTo: section.topAnchor),
- grid.leadingAnchor.constraint(equalTo: section.leadingAnchor),
- grid.trailingAnchor.constraint(equalTo: section.trailingAnchor),
- grid.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 16),
- grid.bottomAnchor.constraint(equalTo: section.bottomAnchor),
- ])
- return section
- }
- private func makeFeatureRow(features: [FeatureCardData]) -> NSStackView {
- let row = NSStackView()
- row.orientation = .horizontal
- row.spacing = AppTheme.featureGridSpacing
- row.distribution = .fillEqually
- row.translatesAutoresizingMaskIntoConstraints = false
- for data in features {
- row.addArrangedSubview(FeatureCardView(data: data))
- }
- return row
- }
- }
- private final class FlippedDocumentView: NSView {
- override var isFlipped: Bool { true }
- }
|