|
|
@@ -20,6 +20,14 @@ class ViewController: NSViewController {
|
|
|
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 secondaryCardBackground = NSColor(calibratedRed: 26 / 255, green: 28 / 255, blue: 33 / 255, alpha: 1)
|
|
|
+ private let appShellBackground = NSColor(calibratedRed: 13 / 255, green: 14 / 255, blue: 17 / 255, alpha: 1)
|
|
|
+ private let contentShellBackground = NSColor(calibratedRed: 14 / 255, green: 15 / 255, blue: 19 / 255, alpha: 1)
|
|
|
+ private let topStripBackground = NSColor(calibratedRed: 28 / 255, green: 30 / 255, blue: 36 / 255, alpha: 1)
|
|
|
+ private let searchPillBackground = NSColor.white.withAlphaComponent(0.06)
|
|
|
+ private let meetingCardBackground = NSColor(calibratedRed: 38 / 255, green: 43 / 255, blue: 58 / 255, alpha: 1)
|
|
|
+ private let titleBarControlBackground = NSColor.white.withAlphaComponent(0.04)
|
|
|
+ private let titleBarLightControlBackground = NSColor.white.withAlphaComponent(0.02)
|
|
|
+ private let appShellCornerRadius: CGFloat = 20
|
|
|
private let accentBlue = NSColor(calibratedRed: 56 / 255, green: 132 / 255, blue: 255 / 255, alpha: 1)
|
|
|
private let accentOrange = NSColor(calibratedRed: 241 / 255, green: 116 / 255, blue: 49 / 255, alpha: 1)
|
|
|
private let primaryText = NSColor(calibratedWhite: 0.98, alpha: 1)
|
|
|
@@ -48,6 +56,11 @@ class ViewController: NSViewController {
|
|
|
private var lastScrollEdgeRefreshAt = Date.distantPast
|
|
|
private let meetingsRefreshInterval: TimeInterval = 8
|
|
|
private let scrollRefreshCooldown: TimeInterval = 3
|
|
|
+
|
|
|
+ private enum SidebarStyle {
|
|
|
+ case login
|
|
|
+ case home
|
|
|
+ }
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
super.viewDidLoad()
|
|
|
@@ -209,6 +222,10 @@ class ViewController: NSViewController {
|
|
|
persistLoggedInState(false)
|
|
|
showLoginView()
|
|
|
}
|
|
|
+
|
|
|
+ @objc private func topBarPlaceholderTapped() {
|
|
|
+ // Reserved for future titlebar control actions.
|
|
|
+ }
|
|
|
|
|
|
private func startMeetingsAutoRefresh() {
|
|
|
meetingsRefreshTimer?.invalidate()
|
|
|
@@ -520,7 +537,7 @@ class ViewController: NSViewController {
|
|
|
|
|
|
private func makeLoginView() -> NSView {
|
|
|
let root = NSView()
|
|
|
- let sidebar = makeSidebar(items: ["Home", "Chat", "Phone", "Docs", "Whiteboards", "Clips", "More"], selected: "Home")
|
|
|
+ let sidebar = makeSidebar(items: ["Home", "Chat", "Phone", "Docs", "Whiteboards", "Clips", "More"], selected: "Home", style: .login)
|
|
|
let content = NSView()
|
|
|
|
|
|
root.addSubview(sidebar)
|
|
|
@@ -625,53 +642,91 @@ class ViewController: NSViewController {
|
|
|
|
|
|
private func makeHomeView(profile: GoogleUserProfile?) -> NSView {
|
|
|
let root = NSView()
|
|
|
- let sidebar = makeSidebar(items: ["Home", "Meetings", "Chat", "Scheduler", "Hub", "More"], selected: "Home")
|
|
|
+ let shell = NSView()
|
|
|
+ shell.wantsLayer = true
|
|
|
+ shell.layer?.backgroundColor = appShellBackground.cgColor
|
|
|
+ shell.layer?.cornerRadius = appShellCornerRadius
|
|
|
+ shell.layer?.borderWidth = 1
|
|
|
+ shell.layer?.borderColor = NSColor.white.withAlphaComponent(0.06).cgColor
|
|
|
+ let sidebar = makeSidebar(items: ["Home", "Meetings", "Chat", "Scheduler", "Hub", "More"], selected: "Home", style: .home)
|
|
|
let content = NSView()
|
|
|
+ content.wantsLayer = true
|
|
|
+ content.layer?.backgroundColor = NSColor.clear.cgColor
|
|
|
|
|
|
- root.addSubview(sidebar)
|
|
|
- root.addSubview(content)
|
|
|
+ root.addSubview(shell)
|
|
|
+ shell.addSubview(sidebar)
|
|
|
+ shell.addSubview(content)
|
|
|
+ shell.translatesAutoresizingMaskIntoConstraints = false
|
|
|
sidebar.translatesAutoresizingMaskIntoConstraints = false
|
|
|
content.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
- sidebar.leadingAnchor.constraint(equalTo: root.leadingAnchor),
|
|
|
- sidebar.topAnchor.constraint(equalTo: root.topAnchor),
|
|
|
- sidebar.bottomAnchor.constraint(equalTo: root.bottomAnchor),
|
|
|
+ shell.leadingAnchor.constraint(equalTo: root.leadingAnchor, constant: 10),
|
|
|
+ shell.trailingAnchor.constraint(equalTo: root.trailingAnchor, constant: -10),
|
|
|
+ shell.topAnchor.constraint(equalTo: root.topAnchor, constant: 10),
|
|
|
+ shell.bottomAnchor.constraint(equalTo: root.bottomAnchor, constant: -10),
|
|
|
+ sidebar.leadingAnchor.constraint(equalTo: shell.leadingAnchor),
|
|
|
+ sidebar.topAnchor.constraint(equalTo: shell.topAnchor),
|
|
|
+ sidebar.bottomAnchor.constraint(equalTo: shell.bottomAnchor),
|
|
|
sidebar.widthAnchor.constraint(equalToConstant: sidebarWidth),
|
|
|
content.leadingAnchor.constraint(equalTo: sidebar.trailingAnchor),
|
|
|
- content.trailingAnchor.constraint(equalTo: root.trailingAnchor),
|
|
|
- content.topAnchor.constraint(equalTo: root.topAnchor),
|
|
|
- content.bottomAnchor.constraint(equalTo: root.bottomAnchor)
|
|
|
+ content.trailingAnchor.constraint(equalTo: shell.trailingAnchor),
|
|
|
+ content.topAnchor.constraint(equalTo: shell.topAnchor),
|
|
|
+ content.bottomAnchor.constraint(equalTo: shell.bottomAnchor)
|
|
|
])
|
|
|
|
|
|
let topBar = NSView()
|
|
|
topBar.wantsLayer = true
|
|
|
- topBar.layer?.backgroundColor = cardBackground.cgColor
|
|
|
- topBar.layer?.cornerRadius = 12
|
|
|
- topBar.layer?.borderWidth = 1
|
|
|
- topBar.layer?.borderColor = NSColor.white.withAlphaComponent(0.06).cgColor
|
|
|
+ topBar.layer?.backgroundColor = topStripBackground.withAlphaComponent(0.88).cgColor
|
|
|
+
|
|
|
+ let topBarDivider = NSView()
|
|
|
+ topBarDivider.wantsLayer = true
|
|
|
+ topBarDivider.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.06).cgColor
|
|
|
|
|
|
let searchPill = NSView()
|
|
|
searchPill.wantsLayer = true
|
|
|
- searchPill.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.05).cgColor
|
|
|
- searchPill.layer?.cornerRadius = 9
|
|
|
- let search = makeLabel("Search meetings, people (\u{2318}E)", size: 13, color: mutedText, weight: .regular, centered: false)
|
|
|
-
|
|
|
- let profileChip = NSView()
|
|
|
+ searchPill.layer?.backgroundColor = searchPillBackground.cgColor
|
|
|
+ searchPill.layer?.cornerRadius = 10
|
|
|
+ let search = makeLabel("Search (\u{2318}E)", size: 13, color: mutedText, weight: .regular, centered: true)
|
|
|
+
|
|
|
+ let brandStack = NSStackView()
|
|
|
+ brandStack.orientation = .vertical
|
|
|
+ brandStack.spacing = 0
|
|
|
+ brandStack.alignment = .leading
|
|
|
+ let brandTop = makeLabel("zoom", size: 14, color: primaryText, weight: .semibold, centered: false)
|
|
|
+ let brandBottom = makeLabel("Workplace", size: 27, color: primaryText, weight: .bold, centered: false)
|
|
|
+ brandBottom.font = .systemFont(ofSize: 14, weight: .bold)
|
|
|
+ [brandTop, brandBottom].forEach { brandStack.addArrangedSubview($0) }
|
|
|
+
|
|
|
+ let leftTopBarCluster = NSStackView()
|
|
|
+ leftTopBarCluster.orientation = .horizontal
|
|
|
+ leftTopBarCluster.spacing = 10
|
|
|
+ leftTopBarCluster.alignment = .centerY
|
|
|
+ let backButton = makeTopBarGlyphButton(symbol: "chevron.left", action: #selector(topBarPlaceholderTapped))
|
|
|
+ let forwardButton = makeTopBarGlyphButton(symbol: "chevron.right", action: #selector(topBarPlaceholderTapped))
|
|
|
+ let historyButton = makeTopBarGlyphButton(symbol: "clock.arrow.circlepath", action: #selector(topBarPlaceholderTapped))
|
|
|
+ [backButton, forwardButton, historyButton].forEach { leftTopBarCluster.addArrangedSubview($0) }
|
|
|
+
|
|
|
+ let rightTopBarCluster = NSStackView()
|
|
|
+ rightTopBarCluster.orientation = .horizontal
|
|
|
+ rightTopBarCluster.spacing = 8
|
|
|
+ rightTopBarCluster.alignment = .centerY
|
|
|
+ let plusButton = makeTopBarGlyphButton(symbol: "plus", action: #selector(topBarPlaceholderTapped))
|
|
|
+ let notificationButton = makeTopBarGlyphButton(symbol: "bell", action: #selector(topBarPlaceholderTapped))
|
|
|
+ let appsButton = makeTopBarIconButton(symbol: "square.grid.2x2", action: #selector(topBarPlaceholderTapped))
|
|
|
+
|
|
|
+ let profileChip = NSButton(title: String((profile?.name ?? "H").prefix(1)).uppercased(), target: self, action: #selector(logoutTapped))
|
|
|
+ profileChip.isBordered = false
|
|
|
profileChip.wantsLayer = true
|
|
|
- profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.22).cgColor
|
|
|
- profileChip.layer?.cornerRadius = 9
|
|
|
- let name = makeLabel(profile?.name ?? "User", size: 13, color: primaryText, weight: .semibold, centered: false)
|
|
|
- let logoutButton = NSButton(title: "Logout", target: self, action: #selector(logoutTapped))
|
|
|
- logoutButton.isBordered = false
|
|
|
- logoutButton.font = .systemFont(ofSize: 13, weight: .semibold)
|
|
|
- logoutButton.contentTintColor = primaryText
|
|
|
- logoutButton.wantsLayer = true
|
|
|
- logoutButton.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.07).cgColor
|
|
|
- logoutButton.layer?.cornerRadius = 8
|
|
|
+ profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.75).cgColor
|
|
|
+ profileChip.layer?.cornerRadius = 10
|
|
|
+ profileChip.contentTintColor = primaryText
|
|
|
+ profileChip.font = .systemFont(ofSize: 14, weight: .bold)
|
|
|
+ profileChip.toolTip = "Profile (click to logout)"
|
|
|
+ [plusButton, notificationButton, appsButton, profileChip].forEach { rightTopBarCluster.addArrangedSubview($0) }
|
|
|
|
|
|
let welcome = makeLabel("Home", size: 15, color: secondaryText, weight: .medium, centered: false)
|
|
|
- let timeTitle = makeLabel("--:--", size: 50, color: primaryText, weight: .bold, centered: true)
|
|
|
+ let timeTitle = makeLabel("--:--", size: 56, color: primaryText, weight: .bold, centered: true)
|
|
|
let dateTitle = makeLabel("-", size: 16, color: secondaryText, weight: .regular, centered: true)
|
|
|
|
|
|
let actions = NSStackView(views: [
|
|
|
@@ -688,14 +743,19 @@ class ViewController: NSViewController {
|
|
|
|
|
|
let panel = NSView()
|
|
|
panel.wantsLayer = true
|
|
|
- panel.layer?.backgroundColor = secondaryCardBackground.cgColor
|
|
|
+ panel.layer?.backgroundColor = secondaryCardBackground.withAlphaComponent(0.94).cgColor
|
|
|
panel.layer?.cornerRadius = 16
|
|
|
panel.layer?.borderWidth = 1
|
|
|
panel.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
|
|
|
+
|
|
|
+ let panelHeaderStrip = NSView()
|
|
|
+ panelHeaderStrip.wantsLayer = true
|
|
|
+ panelHeaderStrip.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.03).cgColor
|
|
|
+ panelHeaderStrip.layer?.cornerRadius = 12
|
|
|
|
|
|
let todaysDateFormatter = DateFormatter()
|
|
|
todaysDateFormatter.dateFormat = "EEEE, MMM d"
|
|
|
- let panelHeader = makeLabel(todaysDateFormatter.string(from: Date()), size: 22, color: primaryText, weight: .semibold, centered: false)
|
|
|
+ let panelHeader = makeLabel(todaysDateFormatter.string(from: Date()), size: 21, color: primaryText, weight: .semibold, centered: false)
|
|
|
let meetingsStatus = makeLabel("Upcoming meetings", size: 12, color: secondaryText, weight: .medium, centered: false)
|
|
|
let noMeeting = makeLabel("No meetings scheduled for today.", size: 18, color: secondaryText, weight: .regular, centered: true)
|
|
|
let meetingsScrollView = NSScrollView()
|
|
|
@@ -714,7 +774,7 @@ class ViewController: NSViewController {
|
|
|
openRecordings.font = .systemFont(ofSize: 14, weight: .semibold)
|
|
|
openRecordings.contentTintColor = primaryText
|
|
|
openRecordings.wantsLayer = true
|
|
|
- openRecordings.layer?.backgroundColor = NSColor(calibratedRed: 31 / 255, green: 33 / 255, blue: 39 / 255, alpha: 1).cgColor
|
|
|
+ openRecordings.layer?.backgroundColor = NSColor(calibratedRed: 36 / 255, green: 39 / 255, blue: 46 / 255, alpha: 1).cgColor
|
|
|
openRecordings.layer?.cornerRadius = 11
|
|
|
openRecordings.layer?.borderWidth = 1
|
|
|
openRecordings.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
|
|
|
@@ -723,7 +783,7 @@ class ViewController: NSViewController {
|
|
|
contentColumn.translatesAutoresizingMaskIntoConstraints = false
|
|
|
content.addSubview(contentColumn)
|
|
|
|
|
|
- [topBar, searchPill, search, profileChip, name, logoutButton, welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
|
|
|
+ [topBar, topBarDivider, brandStack, leftTopBarCluster, rightTopBarCluster, searchPill, search, welcome, timeTitle, dateTitle, actions, panel, panelHeaderStrip, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
|
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
|
contentColumn.addSubview($0)
|
|
|
}
|
|
|
@@ -735,64 +795,72 @@ class ViewController: NSViewController {
|
|
|
NSLayoutConstraint.activate([
|
|
|
contentColumn.topAnchor.constraint(equalTo: content.topAnchor),
|
|
|
contentColumn.bottomAnchor.constraint(equalTo: content.bottomAnchor),
|
|
|
- contentColumn.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 20),
|
|
|
- contentColumn.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20),
|
|
|
-
|
|
|
- topBar.topAnchor.constraint(equalTo: contentColumn.topAnchor, constant: 12),
|
|
|
- topBar.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor, constant: 12),
|
|
|
- topBar.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor, constant: -12),
|
|
|
- topBar.heightAnchor.constraint(equalToConstant: 50),
|
|
|
-
|
|
|
- searchPill.leadingAnchor.constraint(equalTo: topBar.leadingAnchor, constant: 12),
|
|
|
+ contentColumn.leadingAnchor.constraint(equalTo: content.leadingAnchor),
|
|
|
+ contentColumn.trailingAnchor.constraint(equalTo: content.trailingAnchor),
|
|
|
+
|
|
|
+ topBar.topAnchor.constraint(equalTo: contentColumn.topAnchor),
|
|
|
+ topBar.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor),
|
|
|
+ topBar.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor),
|
|
|
+ topBar.heightAnchor.constraint(equalToConstant: 48),
|
|
|
+ topBarDivider.topAnchor.constraint(equalTo: topBar.bottomAnchor),
|
|
|
+ topBarDivider.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor),
|
|
|
+ topBarDivider.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor),
|
|
|
+ topBarDivider.heightAnchor.constraint(equalToConstant: 1),
|
|
|
+
|
|
|
+ brandStack.leadingAnchor.constraint(equalTo: topBar.leadingAnchor, constant: 12),
|
|
|
+ brandStack.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
|
|
|
+
|
|
|
+ leftTopBarCluster.leadingAnchor.constraint(equalTo: brandStack.trailingAnchor, constant: 18),
|
|
|
+ leftTopBarCluster.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
|
|
|
+ rightTopBarCluster.trailingAnchor.constraint(equalTo: topBar.trailingAnchor, constant: -12),
|
|
|
+ rightTopBarCluster.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
|
|
|
+
|
|
|
+ searchPill.centerXAnchor.constraint(equalTo: topBar.centerXAnchor),
|
|
|
searchPill.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
|
|
|
searchPill.heightAnchor.constraint(equalToConstant: 32),
|
|
|
searchPill.widthAnchor.constraint(equalToConstant: 320),
|
|
|
+ searchPill.leadingAnchor.constraint(greaterThanOrEqualTo: leftTopBarCluster.trailingAnchor, constant: 12),
|
|
|
+ searchPill.trailingAnchor.constraint(lessThanOrEqualTo: rightTopBarCluster.leadingAnchor, constant: -12),
|
|
|
search.leadingAnchor.constraint(equalTo: searchPill.leadingAnchor, constant: 12),
|
|
|
search.trailingAnchor.constraint(equalTo: searchPill.trailingAnchor, constant: -12),
|
|
|
search.centerYAnchor.constraint(equalTo: searchPill.centerYAnchor),
|
|
|
+ profileChip.widthAnchor.constraint(equalToConstant: 34),
|
|
|
+ profileChip.heightAnchor.constraint(equalToConstant: 34),
|
|
|
|
|
|
- profileChip.trailingAnchor.constraint(equalTo: topBar.trailingAnchor, constant: -10),
|
|
|
- profileChip.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
|
|
|
- profileChip.heightAnchor.constraint(equalToConstant: 32),
|
|
|
- profileChip.widthAnchor.constraint(greaterThanOrEqualToConstant: 92),
|
|
|
- name.leadingAnchor.constraint(equalTo: profileChip.leadingAnchor, constant: 12),
|
|
|
- name.trailingAnchor.constraint(equalTo: profileChip.trailingAnchor, constant: -12),
|
|
|
- name.centerYAnchor.constraint(equalTo: profileChip.centerYAnchor),
|
|
|
-
|
|
|
- logoutButton.trailingAnchor.constraint(equalTo: profileChip.leadingAnchor, constant: -10),
|
|
|
- logoutButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
|
|
|
- logoutButton.widthAnchor.constraint(equalToConstant: 76),
|
|
|
- logoutButton.heightAnchor.constraint(equalToConstant: 32),
|
|
|
-
|
|
|
- welcome.topAnchor.constraint(equalTo: topBar.bottomAnchor, constant: 18),
|
|
|
+ welcome.topAnchor.constraint(equalTo: topBar.bottomAnchor, constant: 22),
|
|
|
welcome.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
|
|
|
|
- timeTitle.topAnchor.constraint(equalTo: welcome.bottomAnchor, constant: 14),
|
|
|
+ timeTitle.topAnchor.constraint(equalTo: welcome.bottomAnchor, constant: 12),
|
|
|
timeTitle.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
|
dateTitle.topAnchor.constraint(equalTo: timeTitle.bottomAnchor, constant: 6),
|
|
|
dateTitle.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
|
|
|
|
- actions.topAnchor.constraint(equalTo: dateTitle.bottomAnchor, constant: 30),
|
|
|
+ actions.topAnchor.constraint(equalTo: dateTitle.bottomAnchor, constant: 28),
|
|
|
actions.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
|
actions.leadingAnchor.constraint(greaterThanOrEqualTo: contentColumn.leadingAnchor, constant: 18),
|
|
|
actions.trailingAnchor.constraint(lessThanOrEqualTo: contentColumn.trailingAnchor, constant: -18),
|
|
|
- actions.heightAnchor.constraint(equalToConstant: 96),
|
|
|
+ actions.heightAnchor.constraint(equalToConstant: 100),
|
|
|
|
|
|
- panel.topAnchor.constraint(equalTo: actions.bottomAnchor, constant: 24),
|
|
|
- panel.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor, constant: 28),
|
|
|
- panel.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor, constant: -28),
|
|
|
- panel.heightAnchor.constraint(equalToConstant: 300),
|
|
|
+ panel.topAnchor.constraint(equalTo: actions.bottomAnchor, constant: 22),
|
|
|
+ panel.leadingAnchor.constraint(equalTo: contentColumn.leadingAnchor, constant: 26),
|
|
|
+ panel.trailingAnchor.constraint(equalTo: contentColumn.trailingAnchor, constant: -26),
|
|
|
+ panel.heightAnchor.constraint(equalToConstant: 316),
|
|
|
panel.bottomAnchor.constraint(lessThanOrEqualTo: contentColumn.bottomAnchor, constant: -20),
|
|
|
-
|
|
|
- panelHeader.topAnchor.constraint(equalTo: panel.topAnchor, constant: 16),
|
|
|
- panelHeader.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
|
|
|
- meetingsStatus.centerYAnchor.constraint(equalTo: panelHeader.centerYAnchor),
|
|
|
- meetingsStatus.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
|
|
|
+
|
|
|
+ panelHeaderStrip.topAnchor.constraint(equalTo: panel.topAnchor, constant: 10),
|
|
|
+ panelHeaderStrip.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 10),
|
|
|
+ panelHeaderStrip.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -10),
|
|
|
+ panelHeaderStrip.heightAnchor.constraint(equalToConstant: 44),
|
|
|
+
|
|
|
+ panelHeader.centerYAnchor.constraint(equalTo: panelHeaderStrip.centerYAnchor),
|
|
|
+ panelHeader.leadingAnchor.constraint(equalTo: panelHeaderStrip.leadingAnchor, constant: 14),
|
|
|
+ meetingsStatus.centerYAnchor.constraint(equalTo: panelHeaderStrip.centerYAnchor),
|
|
|
+ meetingsStatus.trailingAnchor.constraint(equalTo: panelHeaderStrip.trailingAnchor, constant: -14),
|
|
|
noMeeting.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 18),
|
|
|
noMeeting.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -18),
|
|
|
noMeeting.centerYAnchor.constraint(equalTo: panel.centerYAnchor),
|
|
|
|
|
|
- meetingsScrollView.topAnchor.constraint(equalTo: panel.topAnchor, constant: 58),
|
|
|
+ meetingsScrollView.topAnchor.constraint(equalTo: panelHeaderStrip.bottomAnchor, constant: 10),
|
|
|
meetingsScrollView.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
|
|
|
meetingsScrollView.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
|
|
|
meetingsScrollView.bottomAnchor.constraint(equalTo: openRecordings.topAnchor, constant: -14),
|
|
|
@@ -806,7 +874,7 @@ class ViewController: NSViewController {
|
|
|
openRecordings.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
|
|
|
openRecordings.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
|
|
|
openRecordings.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -12),
|
|
|
- openRecordings.heightAnchor.constraint(equalToConstant: 38)
|
|
|
+ openRecordings.heightAnchor.constraint(equalToConstant: 40)
|
|
|
])
|
|
|
|
|
|
timeLabel = timeTitle
|
|
|
@@ -839,14 +907,27 @@ class ViewController: NSViewController {
|
|
|
|
|
|
// MARK: - Shared UI
|
|
|
|
|
|
- private func makeSidebar(items: [String], selected: String) -> NSView {
|
|
|
+ private func makeSidebar(items: [String], selected: String, style: SidebarStyle = .login) -> NSView {
|
|
|
let sidebar = NSView()
|
|
|
sidebar.wantsLayer = true
|
|
|
- sidebar.layer?.backgroundColor = sidebarBackground.cgColor
|
|
|
+ sidebar.layer?.backgroundColor = (style == .home ? appShellBackground.withAlphaComponent(0.98) : sidebarBackground).cgColor
|
|
|
+ if style == .home {
|
|
|
+ let divider = NSView()
|
|
|
+ divider.wantsLayer = true
|
|
|
+ divider.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.06).cgColor
|
|
|
+ divider.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ sidebar.addSubview(divider)
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ divider.topAnchor.constraint(equalTo: sidebar.topAnchor),
|
|
|
+ divider.bottomAnchor.constraint(equalTo: sidebar.bottomAnchor),
|
|
|
+ divider.trailingAnchor.constraint(equalTo: sidebar.trailingAnchor),
|
|
|
+ divider.widthAnchor.constraint(equalToConstant: 1)
|
|
|
+ ])
|
|
|
+ }
|
|
|
|
|
|
let stack = NSStackView()
|
|
|
stack.orientation = .vertical
|
|
|
- stack.spacing = 16
|
|
|
+ stack.spacing = style == .home ? 12 : 16
|
|
|
stack.alignment = .centerX
|
|
|
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
sidebar.addSubview(stack)
|
|
|
@@ -856,46 +937,106 @@ class ViewController: NSViewController {
|
|
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
row.wantsLayer = true
|
|
|
let selectedRow = item == selected
|
|
|
- row.layer?.backgroundColor = selectedRow ? sidebarActiveBackground.cgColor : NSColor.clear.cgColor
|
|
|
- row.layer?.cornerRadius = 10
|
|
|
- row.widthAnchor.constraint(equalToConstant: 70).isActive = true
|
|
|
+ row.layer?.backgroundColor = selectedRow ? sidebarActiveBackground.withAlphaComponent(0.95).cgColor : NSColor.clear.cgColor
|
|
|
+ row.layer?.cornerRadius = style == .home ? 12 : 10
|
|
|
+ row.widthAnchor.constraint(equalToConstant: style == .home ? 68 : 70).isActive = true
|
|
|
|
|
|
- let icon = makeLabel(selectedRow ? "⌂" : "◻︎", size: 15, color: primaryText, weight: .regular, centered: true)
|
|
|
- let label = makeLabel(item, size: 11, color: selectedRow ? primaryText : secondaryText, weight: .regular, centered: true)
|
|
|
+ let icon = makeLabel(selectedRow ? "⌂" : "◻︎", size: style == .home ? 14 : 15, color: primaryText, weight: .regular, centered: true)
|
|
|
+ let label = makeLabel(item, size: style == .home ? 10 : 11, color: selectedRow ? primaryText : secondaryText, weight: .regular, centered: true)
|
|
|
[icon, label].forEach {
|
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
|
row.addSubview($0)
|
|
|
}
|
|
|
NSLayoutConstraint.activate([
|
|
|
- icon.topAnchor.constraint(equalTo: row.topAnchor, constant: 10),
|
|
|
+ icon.topAnchor.constraint(equalTo: row.topAnchor, constant: style == .home ? 8 : 10),
|
|
|
icon.centerXAnchor.constraint(equalTo: row.centerXAnchor),
|
|
|
- label.topAnchor.constraint(equalTo: icon.bottomAnchor, constant: 5),
|
|
|
+ label.topAnchor.constraint(equalTo: icon.bottomAnchor, constant: style == .home ? 4 : 5),
|
|
|
label.centerXAnchor.constraint(equalTo: row.centerXAnchor),
|
|
|
- label.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: -8)
|
|
|
+ label.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: style == .home ? -7 : -8)
|
|
|
])
|
|
|
stack.addArrangedSubview(row)
|
|
|
}
|
|
|
+
|
|
|
+ if style == .home {
|
|
|
+ let spacer = NSView()
|
|
|
+ spacer.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ spacer.heightAnchor.constraint(greaterThanOrEqualToConstant: 12).isActive = true
|
|
|
+ stack.addArrangedSubview(spacer)
|
|
|
+
|
|
|
+ let settingsBadge = NSView()
|
|
|
+ settingsBadge.wantsLayer = true
|
|
|
+ settingsBadge.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.03).cgColor
|
|
|
+ settingsBadge.layer?.cornerRadius = 12
|
|
|
+ settingsBadge.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ settingsBadge.widthAnchor.constraint(equalToConstant: 40).isActive = true
|
|
|
+ settingsBadge.heightAnchor.constraint(equalToConstant: 40).isActive = true
|
|
|
+ let gear = makeLabel("⚙︎", size: 14, color: secondaryText, weight: .regular, centered: true)
|
|
|
+ gear.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ settingsBadge.addSubview(gear)
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
+ gear.centerXAnchor.constraint(equalTo: settingsBadge.centerXAnchor),
|
|
|
+ gear.centerYAnchor.constraint(equalTo: settingsBadge.centerYAnchor)
|
|
|
+ ])
|
|
|
+ stack.addArrangedSubview(settingsBadge)
|
|
|
+ }
|
|
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
- stack.topAnchor.constraint(equalTo: sidebar.topAnchor, constant: 18),
|
|
|
+ stack.topAnchor.constraint(equalTo: sidebar.topAnchor, constant: style == .home ? 16 : 18),
|
|
|
stack.leadingAnchor.constraint(equalTo: sidebar.leadingAnchor, constant: 4),
|
|
|
stack.trailingAnchor.constraint(equalTo: sidebar.trailingAnchor, constant: -4)
|
|
|
])
|
|
|
+ if style == .home {
|
|
|
+ stack.bottomAnchor.constraint(lessThanOrEqualTo: sidebar.bottomAnchor, constant: -18).isActive = true
|
|
|
+ }
|
|
|
|
|
|
return sidebar
|
|
|
}
|
|
|
+
|
|
|
+ private func makeTopBarIconButton(symbol: String, action: Selector?) -> NSButton {
|
|
|
+ let button = NSButton(title: "", target: action == nil ? nil : self, action: action)
|
|
|
+ button.isBordered = false
|
|
|
+ button.wantsLayer = true
|
|
|
+ button.layer?.backgroundColor = titleBarControlBackground.cgColor
|
|
|
+ button.layer?.cornerRadius = 10
|
|
|
+ button.layer?.borderWidth = 1
|
|
|
+ button.layer?.borderColor = NSColor.white.withAlphaComponent(0.05).cgColor
|
|
|
+ button.contentTintColor = secondaryText
|
|
|
+ button.image = NSImage(systemSymbolName: symbol, accessibilityDescription: symbol)
|
|
|
+ button.imageScaling = .scaleProportionallyUpOrDown
|
|
|
+ button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ button.widthAnchor.constraint(equalToConstant: 30).isActive = true
|
|
|
+ button.heightAnchor.constraint(equalToConstant: 30).isActive = true
|
|
|
+ return button
|
|
|
+ }
|
|
|
+
|
|
|
+ private func makeTopBarGlyphButton(symbol: String, action: Selector?) -> NSButton {
|
|
|
+ let button = NSButton(title: "", target: action == nil ? nil : self, action: action)
|
|
|
+ button.isBordered = false
|
|
|
+ button.wantsLayer = true
|
|
|
+ button.layer?.backgroundColor = titleBarLightControlBackground.cgColor
|
|
|
+ button.layer?.cornerRadius = 9
|
|
|
+ button.layer?.borderWidth = 1
|
|
|
+ button.layer?.borderColor = NSColor.white.withAlphaComponent(0.03).cgColor
|
|
|
+ button.contentTintColor = secondaryText
|
|
|
+ button.image = NSImage(systemSymbolName: symbol, accessibilityDescription: symbol)
|
|
|
+ button.imageScaling = .scaleProportionallyUpOrDown
|
|
|
+ button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
+ button.widthAnchor.constraint(equalToConstant: 26).isActive = true
|
|
|
+ button.heightAnchor.constraint(equalToConstant: 26).isActive = true
|
|
|
+ return button
|
|
|
+ }
|
|
|
|
|
|
private func makeActionTile(title: String, symbol: String, color: NSColor, action: Selector? = nil) -> NSView {
|
|
|
let root = NSView()
|
|
|
root.translatesAutoresizingMaskIntoConstraints = false
|
|
|
- root.widthAnchor.constraint(equalToConstant: 100).isActive = true
|
|
|
- root.heightAnchor.constraint(equalToConstant: 96).isActive = true
|
|
|
+ root.widthAnchor.constraint(equalToConstant: 104).isActive = true
|
|
|
+ root.heightAnchor.constraint(equalToConstant: 100).isActive = true
|
|
|
|
|
|
let iconButton = NSButton(title: "", target: action == nil ? nil : self, action: action)
|
|
|
iconButton.isBordered = false
|
|
|
iconButton.wantsLayer = true
|
|
|
iconButton.layer?.backgroundColor = color.cgColor
|
|
|
- iconButton.layer?.cornerRadius = 18
|
|
|
+ iconButton.layer?.cornerRadius = 20
|
|
|
iconButton.layer?.shadowOpacity = 0.2
|
|
|
iconButton.layer?.shadowRadius = 7
|
|
|
iconButton.layer?.shadowOffset = NSSize(width: 0, height: -1)
|
|
|
@@ -911,9 +1052,9 @@ class ViewController: NSViewController {
|
|
|
NSLayoutConstraint.activate([
|
|
|
iconButton.topAnchor.constraint(equalTo: root.topAnchor),
|
|
|
iconButton.centerXAnchor.constraint(equalTo: root.centerXAnchor),
|
|
|
- iconButton.widthAnchor.constraint(equalToConstant: 62),
|
|
|
- iconButton.heightAnchor.constraint(equalToConstant: 62),
|
|
|
- label.topAnchor.constraint(equalTo: iconButton.bottomAnchor, constant: 9),
|
|
|
+ iconButton.widthAnchor.constraint(equalToConstant: 64),
|
|
|
+ iconButton.heightAnchor.constraint(equalToConstant: 64),
|
|
|
+ label.topAnchor.constraint(equalTo: iconButton.bottomAnchor, constant: 10),
|
|
|
label.centerXAnchor.constraint(equalTo: root.centerXAnchor),
|
|
|
label.bottomAnchor.constraint(equalTo: root.bottomAnchor)
|
|
|
])
|
|
|
@@ -923,10 +1064,12 @@ class ViewController: NSViewController {
|
|
|
private func makeMeetingRowCard(_ meeting: ScheduledMeeting) -> NSView {
|
|
|
let card = NSView()
|
|
|
card.wantsLayer = true
|
|
|
- card.layer?.backgroundColor = NSColor(calibratedRed: 35 / 255, green: 40 / 255, blue: 56 / 255, alpha: 1).cgColor
|
|
|
- card.layer?.cornerRadius = 14
|
|
|
+ card.layer?.backgroundColor = meetingCardBackground.cgColor
|
|
|
+ card.layer?.cornerRadius = 13
|
|
|
+ card.layer?.borderWidth = 1
|
|
|
+ card.layer?.borderColor = NSColor.white.withAlphaComponent(0.06).cgColor
|
|
|
card.translatesAutoresizingMaskIntoConstraints = false
|
|
|
- card.heightAnchor.constraint(equalToConstant: 110).isActive = true
|
|
|
+ card.heightAnchor.constraint(equalToConstant: 116).isActive = true
|
|
|
|
|
|
let dateFormatter = DateFormatter()
|
|
|
dateFormatter.dateFormat = "EEE, MMM d"
|
|
|
@@ -936,7 +1079,7 @@ class ViewController: NSViewController {
|
|
|
let endText = meeting.end.map { timeFormatter.string(from: $0) } ?? ""
|
|
|
let range = endText.isEmpty ? startText : "\(startText) - \(endText)"
|
|
|
|
|
|
- let title = makeLabel(meeting.title, size: 17, color: primaryText, weight: .semibold, centered: false)
|
|
|
+ let title = makeLabel(meeting.title, size: 26, color: primaryText, weight: .regular, centered: false)
|
|
|
let detail = makeLabel("\(dateFormatter.string(from: meeting.start))\n\(range)", size: 14, color: secondaryText, weight: .regular, centered: false)
|
|
|
detail.maximumNumberOfLines = 2
|
|
|
let host = makeLabel("Host: \(meeting.host) • \(meeting.source)", size: 13, color: secondaryText, weight: .regular, centered: false)
|
|
|
@@ -947,15 +1090,15 @@ class ViewController: NSViewController {
|
|
|
}
|
|
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
- title.topAnchor.constraint(equalTo: card.topAnchor, constant: 12),
|
|
|
- title.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 14),
|
|
|
+ title.topAnchor.constraint(equalTo: card.topAnchor, constant: 11),
|
|
|
+ title.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
|
|
|
title.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -14),
|
|
|
|
|
|
- detail.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 6),
|
|
|
+ detail.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 3),
|
|
|
detail.leadingAnchor.constraint(equalTo: title.leadingAnchor),
|
|
|
detail.trailingAnchor.constraint(equalTo: title.trailingAnchor),
|
|
|
|
|
|
- host.topAnchor.constraint(equalTo: detail.bottomAnchor, constant: 6),
|
|
|
+ host.topAnchor.constraint(equalTo: detail.bottomAnchor, constant: 7),
|
|
|
host.leadingAnchor.constraint(equalTo: title.leadingAnchor),
|
|
|
host.trailingAnchor.constraint(equalTo: title.trailingAnchor)
|
|
|
])
|