|
|
@@ -8,19 +8,311 @@
|
|
|
import Cocoa
|
|
|
|
|
|
class ViewController: NSViewController {
|
|
|
+ private let sidebarWidth: CGFloat = 78
|
|
|
+ // Dark-mode values copied from classroom_app Palette(isDarkMode: true).
|
|
|
+ private let appBackground = NSColor(calibratedRed: 10 / 255, green: 11 / 255, blue: 12 / 255, alpha: 1)
|
|
|
+ private let sidebarBackground = NSColor(calibratedRed: 16 / 255, green: 17 / 255, blue: 19 / 255, alpha: 1)
|
|
|
+ private let sidebarActiveBackground = NSColor(calibratedRed: 22 / 255, green: 23 / 255, blue: 26 / 255, alpha: 1)
|
|
|
+ private let cardBackground = NSColor(calibratedRed: 20 / 255, green: 21 / 255, blue: 24 / 255, alpha: 1)
|
|
|
+ private let separatorColor = NSColor(calibratedRed: 26 / 255, green: 27 / 255, blue: 30 / 255, alpha: 1)
|
|
|
+ private let accentBlue = NSColor(calibratedRed: 56 / 255, green: 132 / 255, blue: 255 / 255, alpha: 1)
|
|
|
+ private let primaryText = NSColor(calibratedWhite: 0.98, alpha: 1)
|
|
|
+ private let secondaryText = NSColor(calibratedWhite: 0.78, alpha: 1)
|
|
|
+ private let mutedText = NSColor(calibratedWhite: 0.66, alpha: 1)
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
super.viewDidLoad()
|
|
|
+ setupUI()
|
|
|
+ }
|
|
|
+
|
|
|
+ override func viewDidAppear() {
|
|
|
+ super.viewDidAppear()
|
|
|
+
|
|
|
+ // Keep a practical default size for this desktop mock.
|
|
|
+ view.window?.setContentSize(NSSize(width: 960, height: 700))
|
|
|
+ view.window?.title = "zoom Workplace"
|
|
|
+ }
|
|
|
+
|
|
|
+ private func setupUI() {
|
|
|
+ view.wantsLayer = true
|
|
|
+ view.layer?.backgroundColor = appBackground.cgColor
|
|
|
+
|
|
|
+ let sidebar = makeSidebar()
|
|
|
+ let content = makeContent()
|
|
|
+
|
|
|
+ view.addSubview(sidebar)
|
|
|
+ view.addSubview(content)
|
|
|
+
|
|
|
+ sidebar.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ content.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ sidebar.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
|
+ sidebar.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
+ sidebar.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
|
+ sidebar.widthAnchor.constraint(equalToConstant: sidebarWidth),
|
|
|
+
|
|
|
+ content.leadingAnchor.constraint(equalTo: sidebar.trailingAnchor),
|
|
|
+ content.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
|
+ content.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
+ content.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
|
+ ])
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeSidebar() -> NSView {
|
|
|
+ let sidebar = NSView()
|
|
|
+ sidebar.wantsLayer = true
|
|
|
+ sidebar.layer?.backgroundColor = sidebarBackground.cgColor
|
|
|
+
|
|
|
+ let title = NSTextField(labelWithString: "Home")
|
|
|
+ title.font = .systemFont(ofSize: 11, weight: .medium)
|
|
|
+ title.textColor = primaryText
|
|
|
+ title.alignment = .center
|
|
|
+
|
|
|
+ let homeBox = NSBox()
|
|
|
+ homeBox.boxType = .custom
|
|
|
+ homeBox.borderType = .noBorder
|
|
|
+ homeBox.cornerRadius = 12
|
|
|
+ homeBox.fillColor = sidebarActiveBackground
|
|
|
+
|
|
|
+ let homeIcon = NSTextField(labelWithString: "⌂")
|
|
|
+ homeIcon.font = .systemFont(ofSize: 20, weight: .medium)
|
|
|
+ homeIcon.textColor = .white
|
|
|
+ homeIcon.alignment = .center
|
|
|
|
|
|
- // Do any additional setup after loading the view.
|
|
|
+ homeBox.contentView?.addSubview(homeIcon)
|
|
|
+ homeIcon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ homeIcon.centerXAnchor.constraint(equalTo: homeBox.contentView!.centerXAnchor),
|
|
|
+ homeIcon.centerYAnchor.constraint(equalTo: homeBox.contentView!.centerYAnchor)
|
|
|
+ ])
|
|
|
+
|
|
|
+ let sidebarItems = ["Chat", "Phone", "Docs", "Whiteboards", "Clips", "More"]
|
|
|
+ var itemViews: [NSView] = []
|
|
|
+
|
|
|
+ for item in sidebarItems {
|
|
|
+ let container = NSView()
|
|
|
+ let icon = NSTextField(labelWithString: "◻︎")
|
|
|
+ icon.font = .systemFont(ofSize: 15, weight: .regular)
|
|
|
+ icon.textColor = secondaryText
|
|
|
+ icon.alignment = .center
|
|
|
+
|
|
|
+ let label = NSTextField(labelWithString: item)
|
|
|
+ label.font = .systemFont(ofSize: 12, weight: .regular)
|
|
|
+ label.textColor = secondaryText
|
|
|
+ label.alignment = .center
|
|
|
+
|
|
|
+ container.addSubview(icon)
|
|
|
+ container.addSubview(label)
|
|
|
+ icon.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ icon.topAnchor.constraint(equalTo: container.topAnchor),
|
|
|
+ icon.centerXAnchor.constraint(equalTo: container.centerXAnchor),
|
|
|
+ label.topAnchor.constraint(equalTo: icon.bottomAnchor, constant: 5),
|
|
|
+ label.centerXAnchor.constraint(equalTo: container.centerXAnchor),
|
|
|
+ label.bottomAnchor.constraint(equalTo: container.bottomAnchor)
|
|
|
+ ])
|
|
|
+ itemViews.append(container)
|
|
|
+ }
|
|
|
+
|
|
|
+ let stack = NSStackView(views: itemViews)
|
|
|
+ stack.orientation = .vertical
|
|
|
+ stack.spacing = 16
|
|
|
+ stack.alignment = .centerX
|
|
|
+ stack.distribution = .gravityAreas
|
|
|
+
|
|
|
+ sidebar.addSubview(homeBox)
|
|
|
+ sidebar.addSubview(title)
|
|
|
+ sidebar.addSubview(stack)
|
|
|
+
|
|
|
+ homeBox.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ title.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ homeBox.topAnchor.constraint(equalTo: sidebar.topAnchor, constant: 42),
|
|
|
+ homeBox.centerXAnchor.constraint(equalTo: sidebar.centerXAnchor),
|
|
|
+ homeBox.widthAnchor.constraint(equalToConstant: 56),
|
|
|
+ homeBox.heightAnchor.constraint(equalToConstant: 56),
|
|
|
+
|
|
|
+ title.topAnchor.constraint(equalTo: homeBox.bottomAnchor, constant: 8),
|
|
|
+ title.centerXAnchor.constraint(equalTo: sidebar.centerXAnchor),
|
|
|
+
|
|
|
+ stack.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 30),
|
|
|
+ stack.leadingAnchor.constraint(equalTo: sidebar.leadingAnchor),
|
|
|
+ stack.trailingAnchor.constraint(equalTo: sidebar.trailingAnchor)
|
|
|
+ ])
|
|
|
+
|
|
|
+ return sidebar
|
|
|
}
|
|
|
|
|
|
- override var representedObject: Any? {
|
|
|
- didSet {
|
|
|
- // Update the view, if already loaded.
|
|
|
+ private func makeContent() -> NSView {
|
|
|
+ let content = NSView()
|
|
|
+
|
|
|
+ let back = NSTextField(labelWithString: "‹ Back")
|
|
|
+ back.font = .systemFont(ofSize: 34, weight: .regular)
|
|
|
+ back.textColor = accentBlue
|
|
|
+
|
|
|
+ let logo = NSTextField(labelWithString: "zoom\nWorkplace")
|
|
|
+ logo.font = .systemFont(ofSize: 24, weight: .bold)
|
|
|
+ logo.textColor = primaryText
|
|
|
+ logo.alignment = .center
|
|
|
+ logo.maximumNumberOfLines = 2
|
|
|
+
|
|
|
+ let domain = NSTextField(labelWithString: "us05web.zoom.us")
|
|
|
+ domain.font = .systemFont(ofSize: 16, weight: .semibold)
|
|
|
+ domain.textColor = primaryText
|
|
|
+ domain.alignment = .center
|
|
|
+
|
|
|
+ let emailField = NSTextField()
|
|
|
+ emailField.placeholderString = "Email or phone number"
|
|
|
+ emailField.font = .systemFont(ofSize: 20, weight: .regular)
|
|
|
+ emailField.textColor = .white
|
|
|
+ emailField.focusRingType = .none
|
|
|
+ emailField.wantsLayer = true
|
|
|
+ emailField.layer?.cornerRadius = 10
|
|
|
+ emailField.layer?.borderWidth = 1.5
|
|
|
+ emailField.layer?.borderColor = accentBlue.cgColor
|
|
|
+ emailField.layer?.backgroundColor = cardBackground.cgColor
|
|
|
+
|
|
|
+ let nextButton = NSButton(title: "Next", target: nil, action: nil)
|
|
|
+ nextButton.font = .systemFont(ofSize: 20, weight: .semibold)
|
|
|
+ nextButton.bezelStyle = .regularSquare
|
|
|
+ nextButton.isBordered = false
|
|
|
+ nextButton.wantsLayer = true
|
|
|
+ nextButton.layer?.cornerRadius = 10
|
|
|
+ nextButton.layer?.backgroundColor = cardBackground.cgColor
|
|
|
+ nextButton.contentTintColor = mutedText
|
|
|
+
|
|
|
+ let divider = NSBox()
|
|
|
+ divider.boxType = .separator
|
|
|
+ divider.borderColor = separatorColor
|
|
|
+
|
|
|
+ let signInText = NSTextField(labelWithString: "or sign in with")
|
|
|
+ signInText.font = .systemFont(ofSize: 14, weight: .regular)
|
|
|
+ signInText.textColor = secondaryText
|
|
|
+ signInText.alignment = .center
|
|
|
+
|
|
|
+ let ssoButton = makeSocialButton(icon: "🔑", text: "SSO")
|
|
|
+ let googleButton = makeSocialButton(icon: "G", text: "Google")
|
|
|
+ let appleButton = makeSocialButton(icon: "", text: "Apple")
|
|
|
+ let facebookButton = makeSocialButton(icon: "f", text: "Facebook")
|
|
|
+ let microsoftButton = makeSocialButton(icon: "■", text: "Microsoft")
|
|
|
+
|
|
|
+ let socialStack = NSStackView(views: [ssoButton, googleButton, appleButton, facebookButton, microsoftButton])
|
|
|
+ socialStack.orientation = .horizontal
|
|
|
+ socialStack.spacing = 14
|
|
|
+ socialStack.distribution = .fillEqually
|
|
|
+
|
|
|
+ let signup = NSTextField(labelWithString: "Don't have an account? Sign up")
|
|
|
+ signup.font = .systemFont(ofSize: 15, weight: .regular)
|
|
|
+ signup.textColor = primaryText
|
|
|
+ signup.alignment = .center
|
|
|
+
|
|
|
+ let footer = NSTextField(labelWithString: "Help Terms Privacy")
|
|
|
+ footer.font = .systemFont(ofSize: 14, weight: .regular)
|
|
|
+ footer.textColor = NSColor(calibratedRed: 72 / 255, green: 129 / 255, blue: 218 / 255, alpha: 1)
|
|
|
+ footer.alignment = .center
|
|
|
+
|
|
|
+ content.addSubview(back)
|
|
|
+ content.addSubview(logo)
|
|
|
+ content.addSubview(domain)
|
|
|
+ content.addSubview(emailField)
|
|
|
+ content.addSubview(nextButton)
|
|
|
+ content.addSubview(divider)
|
|
|
+ content.addSubview(signInText)
|
|
|
+ content.addSubview(socialStack)
|
|
|
+ content.addSubview(signup)
|
|
|
+ content.addSubview(footer)
|
|
|
+
|
|
|
+ [back, logo, domain, emailField, nextButton, divider, signInText, socialStack, signup, footer].forEach {
|
|
|
+ $0.translatesAutoresizingMaskIntoConstraints = false
|
|
|
}
|
|
|
+
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ back.topAnchor.constraint(equalTo: content.topAnchor, constant: 26),
|
|
|
+ back.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 34),
|
|
|
+
|
|
|
+ logo.topAnchor.constraint(equalTo: content.topAnchor, constant: 118),
|
|
|
+ logo.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
+
|
|
|
+ domain.topAnchor.constraint(equalTo: logo.bottomAnchor, constant: 12),
|
|
|
+ domain.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
+
|
|
|
+ emailField.topAnchor.constraint(equalTo: domain.bottomAnchor, constant: 30),
|
|
|
+ emailField.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
+ emailField.widthAnchor.constraint(equalToConstant: 520),
|
|
|
+ emailField.heightAnchor.constraint(equalToConstant: 52),
|
|
|
+
|
|
|
+ nextButton.topAnchor.constraint(equalTo: emailField.bottomAnchor, constant: 20),
|
|
|
+ nextButton.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
+ nextButton.widthAnchor.constraint(equalTo: emailField.widthAnchor),
|
|
|
+ nextButton.heightAnchor.constraint(equalToConstant: 52),
|
|
|
+
|
|
|
+ divider.topAnchor.constraint(equalTo: nextButton.bottomAnchor, constant: 28),
|
|
|
+ divider.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
+ divider.widthAnchor.constraint(equalTo: emailField.widthAnchor),
|
|
|
+
|
|
|
+ signInText.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
+ signInText.centerYAnchor.constraint(equalTo: divider.centerYAnchor),
|
|
|
+
|
|
|
+ socialStack.topAnchor.constraint(equalTo: divider.bottomAnchor, constant: 18),
|
|
|
+ socialStack.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
+ socialStack.widthAnchor.constraint(equalTo: emailField.widthAnchor),
|
|
|
+
|
|
|
+ signup.topAnchor.constraint(equalTo: socialStack.bottomAnchor, constant: 14),
|
|
|
+ signup.centerXAnchor.constraint(equalTo: content.centerXAnchor),
|
|
|
+
|
|
|
+ footer.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -16),
|
|
|
+ footer.centerXAnchor.constraint(equalTo: content.centerXAnchor)
|
|
|
+ ])
|
|
|
+
|
|
|
+ return content
|
|
|
}
|
|
|
|
|
|
+ private func makeSocialButton(icon: String, text: String) -> NSView {
|
|
|
+ let wrapper = NSView()
|
|
|
+ let iconLabel = NSTextField(labelWithString: icon)
|
|
|
+ iconLabel.font = .systemFont(ofSize: 22, weight: .medium)
|
|
|
+ iconLabel.alignment = .center
|
|
|
+ iconLabel.textColor = primaryText
|
|
|
+
|
|
|
+ let iconBox = NSBox()
|
|
|
+ iconBox.boxType = .custom
|
|
|
+ iconBox.cornerRadius = 12
|
|
|
+ iconBox.borderType = .noBorder
|
|
|
+ iconBox.fillColor = cardBackground
|
|
|
|
|
|
+ let textLabel = NSTextField(labelWithString: text)
|
|
|
+ textLabel.font = .systemFont(ofSize: 12, weight: .regular)
|
|
|
+ textLabel.textColor = secondaryText
|
|
|
+ textLabel.alignment = .center
|
|
|
+
|
|
|
+ wrapper.addSubview(iconBox)
|
|
|
+ wrapper.addSubview(textLabel)
|
|
|
+ iconBox.contentView?.addSubview(iconLabel)
|
|
|
+
|
|
|
+ iconBox.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ iconLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ iconBox.topAnchor.constraint(equalTo: wrapper.topAnchor),
|
|
|
+ iconBox.centerXAnchor.constraint(equalTo: wrapper.centerXAnchor),
|
|
|
+ iconBox.widthAnchor.constraint(equalToConstant: 52),
|
|
|
+ iconBox.heightAnchor.constraint(equalToConstant: 52),
|
|
|
+
|
|
|
+ iconLabel.centerXAnchor.constraint(equalTo: iconBox.contentView!.centerXAnchor),
|
|
|
+ iconLabel.centerYAnchor.constraint(equalTo: iconBox.contentView!.centerYAnchor),
|
|
|
+
|
|
|
+ textLabel.topAnchor.constraint(equalTo: iconBox.bottomAnchor, constant: 6),
|
|
|
+ textLabel.centerXAnchor.constraint(equalTo: wrapper.centerXAnchor),
|
|
|
+ textLabel.bottomAnchor.constraint(equalTo: wrapper.bottomAnchor)
|
|
|
+ ])
|
|
|
+
|
|
|
+ return wrapper
|
|
|
+ }
|
|
|
}
|
|
|
|