|
@@ -54,10 +54,20 @@ class ViewController: NSViewController {
|
|
|
private weak var meetingsNextDayButton: NSButton?
|
|
private weak var meetingsNextDayButton: NSButton?
|
|
|
private weak var meetingsTodayButton: NSButton?
|
|
private weak var meetingsTodayButton: NSButton?
|
|
|
private weak var refreshMeetingsButton: NSButton?
|
|
private weak var refreshMeetingsButton: NSButton?
|
|
|
|
|
+ private weak var homeWelcomeLabel: NSTextField?
|
|
|
|
|
+ private weak var homeTimeLabelView: NSTextField?
|
|
|
|
|
+ private weak var homeDateLabelView: NSTextField?
|
|
|
|
|
+ private weak var homeActionsRow: NSView?
|
|
|
|
|
+ private weak var homeMeetingsPanel: NSView?
|
|
|
|
|
+ private weak var homePlaceholderLabel: NSTextField?
|
|
|
private weak var homeSearchField: NSTextField?
|
|
private weak var homeSearchField: NSTextField?
|
|
|
private weak var homeSearchPill: NSView?
|
|
private weak var homeSearchPill: NSView?
|
|
|
private var allScheduledMeetings: [ScheduledMeeting] = []
|
|
private var allScheduledMeetings: [ScheduledMeeting] = []
|
|
|
private var selectedMeetingsDayStart: Date = Calendar.current.startOfDay(for: Date())
|
|
private var selectedMeetingsDayStart: Date = Calendar.current.startOfDay(for: Date())
|
|
|
|
|
+ private var selectedHomeSidebarItem: String = "Home"
|
|
|
|
|
+ private var homeSidebarRowViews: [String: NSView] = [:]
|
|
|
|
|
+ private var homeSidebarIconViews: [String: NSImageView] = [:]
|
|
|
|
|
+ private var homeSidebarLabels: [String: NSTextField] = [:]
|
|
|
private var searchTextObserver: NSObjectProtocol?
|
|
private var searchTextObserver: NSObjectProtocol?
|
|
|
private var searchShortcutMonitor: Any?
|
|
private var searchShortcutMonitor: Any?
|
|
|
private var searchOutsideClickMonitor: Any?
|
|
private var searchOutsideClickMonitor: Any?
|
|
@@ -160,6 +170,8 @@ class ViewController: NSViewController {
|
|
|
startClock()
|
|
startClock()
|
|
|
startMeetingsAutoRefresh()
|
|
startMeetingsAutoRefresh()
|
|
|
triggerMeetingsRefresh(force: true)
|
|
triggerMeetingsRefresh(force: true)
|
|
|
|
|
+ updateHomeSidebarHighlight()
|
|
|
|
|
+ updateSelectedHomeSectionUI()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func isUserLoggedIn() -> Bool {
|
|
private func isUserLoggedIn() -> Bool {
|
|
@@ -930,7 +942,10 @@ class ViewController: NSViewController {
|
|
|
let chromeHeader = NSView()
|
|
let chromeHeader = NSView()
|
|
|
chromeHeader.wantsLayer = true
|
|
chromeHeader.wantsLayer = true
|
|
|
chromeHeader.layer?.backgroundColor = chromeUnifiedBackground.cgColor
|
|
chromeHeader.layer?.backgroundColor = chromeUnifiedBackground.cgColor
|
|
|
- let sidebar = makeSidebar(items: ["Home", "Meetings", "Scheduler"], selected: "Home", style: .home)
|
|
|
|
|
|
|
+ homeSidebarRowViews = [:]
|
|
|
|
|
+ homeSidebarIconViews = [:]
|
|
|
|
|
+ homeSidebarLabels = [:]
|
|
|
|
|
+ let sidebar = makeSidebar(items: ["Home", "Meetings", "Scheduler"], selected: selectedHomeSidebarItem, style: .home)
|
|
|
let content = NSView()
|
|
let content = NSView()
|
|
|
content.wantsLayer = true
|
|
content.wantsLayer = true
|
|
|
content.layer?.backgroundColor = NSColor.clear.cgColor
|
|
content.layer?.backgroundColor = NSColor.clear.cgColor
|
|
@@ -1140,6 +1155,9 @@ class ViewController: NSViewController {
|
|
|
refreshMeetingsButton.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
|
|
refreshMeetingsButton.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
|
|
|
self.refreshMeetingsButton = refreshMeetingsButton
|
|
self.refreshMeetingsButton = refreshMeetingsButton
|
|
|
|
|
|
|
|
|
|
+ let placeholder = makeLabel("Coming soon", size: 22, color: secondaryText, weight: .semibold, centered: true)
|
|
|
|
|
+ placeholder.isHidden = true
|
|
|
|
|
+
|
|
|
let contentColumn = NSView()
|
|
let contentColumn = NSView()
|
|
|
contentColumn.translatesAutoresizingMaskIntoConstraints = false
|
|
contentColumn.translatesAutoresizingMaskIntoConstraints = false
|
|
|
content.addSubview(topBar)
|
|
content.addSubview(topBar)
|
|
@@ -1158,7 +1176,7 @@ class ViewController: NSViewController {
|
|
|
[searchIcon, searchField, searchHintLabel].forEach {
|
|
[searchIcon, searchField, searchHintLabel].forEach {
|
|
|
searchPill.addSubview($0)
|
|
searchPill.addSubview($0)
|
|
|
}
|
|
}
|
|
|
- [welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, meetingsDayNav, noMeeting, meetingsScrollView, refreshMeetingsButton].forEach {
|
|
|
|
|
|
|
+ [welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, meetingsDayNav, noMeeting, meetingsScrollView, refreshMeetingsButton, placeholder].forEach {
|
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
|
contentColumn.addSubview($0)
|
|
contentColumn.addSubview($0)
|
|
|
}
|
|
}
|
|
@@ -1261,11 +1279,20 @@ class ViewController: NSViewController {
|
|
|
refreshMeetingsButton.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
|
|
refreshMeetingsButton.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
|
|
|
refreshMeetingsButton.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
|
|
refreshMeetingsButton.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
|
|
|
refreshMeetingsButton.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -12),
|
|
refreshMeetingsButton.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -12),
|
|
|
- refreshMeetingsButton.heightAnchor.constraint(equalToConstant: 40)
|
|
|
|
|
|
|
+ refreshMeetingsButton.heightAnchor.constraint(equalToConstant: 40),
|
|
|
|
|
+
|
|
|
|
|
+ placeholder.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
|
|
|
+ placeholder.centerYAnchor.constraint(equalTo: contentColumn.centerYAnchor)
|
|
|
])
|
|
])
|
|
|
|
|
|
|
|
timeLabel = timeTitle
|
|
timeLabel = timeTitle
|
|
|
dateLabel = dateTitle
|
|
dateLabel = dateTitle
|
|
|
|
|
+ homeWelcomeLabel = welcome
|
|
|
|
|
+ homeTimeLabelView = timeTitle
|
|
|
|
|
+ homeDateLabelView = dateTitle
|
|
|
|
|
+ homeActionsRow = actions
|
|
|
|
|
+ homeMeetingsPanel = panel
|
|
|
|
|
+ homePlaceholderLabel = placeholder
|
|
|
meetingsDayHeaderLabel = panelHeader
|
|
meetingsDayHeaderLabel = panelHeader
|
|
|
meetingsListStack = meetingsStack
|
|
meetingsListStack = meetingsStack
|
|
|
meetingsStatusLabel = meetingsStatus
|
|
meetingsStatusLabel = meetingsStatus
|
|
@@ -1356,16 +1383,24 @@ class ViewController: NSViewController {
|
|
|
|
|
|
|
|
let iconView = NSImageView()
|
|
let iconView = NSImageView()
|
|
|
iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
- iconView.contentTintColor = primaryText
|
|
|
|
|
|
|
+ iconView.contentTintColor = selectedRow ? primaryText : secondaryText
|
|
|
iconView.imageScaling = .scaleProportionallyUpOrDown
|
|
iconView.imageScaling = .scaleProportionallyUpOrDown
|
|
|
iconView.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 20, weight: .regular)
|
|
iconView.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 20, weight: .regular)
|
|
|
- iconView.image = NSImage(systemSymbolName: sidebarSymbolName(for: item), accessibilityDescription: item)
|
|
|
|
|
|
|
+ iconView.image = NSImage(systemSymbolName: sidebarSymbolName(for: item, filled: selectedRow), accessibilityDescription: item)
|
|
|
iconContainer.addSubview(iconView)
|
|
iconContainer.addSubview(iconView)
|
|
|
|
|
|
|
|
let label = makeLabel(item, size: 10, color: selectedRow ? primaryText : secondaryText, weight: .regular, centered: true)
|
|
let label = makeLabel(item, size: 10, color: selectedRow ? primaryText : secondaryText, weight: .regular, centered: true)
|
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
row.addSubview(label)
|
|
row.addSubview(label)
|
|
|
|
|
|
|
|
|
|
+ let hit = NSButton(title: "", target: self, action: #selector(homeSidebarItemTapped(_:)))
|
|
|
|
|
+ hit.identifier = NSUserInterfaceItemIdentifier(item)
|
|
|
|
|
+ hit.isBordered = false
|
|
|
|
|
+ hit.bezelStyle = .shadowlessSquare
|
|
|
|
|
+ hit.focusRingType = .none
|
|
|
|
|
+ hit.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ row.addSubview(hit, positioned: .above, relativeTo: nil)
|
|
|
|
|
+
|
|
|
NSLayoutConstraint.activate([
|
|
NSLayoutConstraint.activate([
|
|
|
iconContainer.topAnchor.constraint(equalTo: row.topAnchor, constant: 9),
|
|
iconContainer.topAnchor.constraint(equalTo: row.topAnchor, constant: 9),
|
|
|
iconContainer.centerXAnchor.constraint(equalTo: row.centerXAnchor),
|
|
iconContainer.centerXAnchor.constraint(equalTo: row.centerXAnchor),
|
|
@@ -1379,9 +1414,18 @@ class ViewController: NSViewController {
|
|
|
|
|
|
|
|
label.topAnchor.constraint(equalTo: iconContainer.bottomAnchor, constant: 6),
|
|
label.topAnchor.constraint(equalTo: iconContainer.bottomAnchor, constant: 6),
|
|
|
label.centerXAnchor.constraint(equalTo: row.centerXAnchor),
|
|
label.centerXAnchor.constraint(equalTo: row.centerXAnchor),
|
|
|
- label.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: -8)
|
|
|
|
|
|
|
+ label.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: -8),
|
|
|
|
|
+
|
|
|
|
|
+ hit.leadingAnchor.constraint(equalTo: row.leadingAnchor),
|
|
|
|
|
+ hit.trailingAnchor.constraint(equalTo: row.trailingAnchor),
|
|
|
|
|
+ hit.topAnchor.constraint(equalTo: row.topAnchor),
|
|
|
|
|
+ hit.bottomAnchor.constraint(equalTo: row.bottomAnchor)
|
|
|
])
|
|
])
|
|
|
|
|
|
|
|
|
|
+ homeSidebarRowViews[item] = row
|
|
|
|
|
+ homeSidebarIconViews[item] = iconView
|
|
|
|
|
+ homeSidebarLabels[item] = label
|
|
|
|
|
+
|
|
|
if item == "Hub" {
|
|
if item == "Hub" {
|
|
|
let badge = makeSidebarBadge(text: "1")
|
|
let badge = makeSidebarBadge(text: "1")
|
|
|
badge.translatesAutoresizingMaskIntoConstraints = false
|
|
badge.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -1491,15 +1535,68 @@ class ViewController: NSViewController {
|
|
|
return sidebar
|
|
return sidebar
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private func sidebarSymbolName(for item: String) -> String {
|
|
|
|
|
|
|
+ @objc private func homeSidebarItemTapped(_ sender: NSButton) {
|
|
|
|
|
+ guard let item = sender.identifier?.rawValue else { return }
|
|
|
|
|
+ selectedHomeSidebarItem = item
|
|
|
|
|
+ updateHomeSidebarHighlight()
|
|
|
|
|
+ updateSelectedHomeSectionUI()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @MainActor
|
|
|
|
|
+ private func updateHomeSidebarHighlight() {
|
|
|
|
|
+ for (item, row) in homeSidebarRowViews {
|
|
|
|
|
+ let selected = item == selectedHomeSidebarItem
|
|
|
|
|
+ row.layer?.backgroundColor = selected ? sidebarActiveBackground.withAlphaComponent(0.95).cgColor : NSColor.clear.cgColor
|
|
|
|
|
+ homeSidebarLabels[item]?.textColor = selected ? primaryText : secondaryText
|
|
|
|
|
+ homeSidebarIconViews[item]?.contentTintColor = selected ? primaryText : secondaryText
|
|
|
|
|
+ let symbolName = sidebarSymbolName(for: item, filled: selected)
|
|
|
|
|
+ if let image = NSImage(systemSymbolName: symbolName, accessibilityDescription: item) {
|
|
|
|
|
+ homeSidebarIconViews[item]?.image = image
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @MainActor
|
|
|
|
|
+ private func updateSelectedHomeSectionUI() {
|
|
|
|
|
+ let isHome = selectedHomeSidebarItem == "Home"
|
|
|
|
|
+ let title = selectedHomeSidebarItem
|
|
|
|
|
+
|
|
|
|
|
+ homeWelcomeLabel?.stringValue = title
|
|
|
|
|
+
|
|
|
|
|
+ let dashboardViews: [NSView?] = [
|
|
|
|
|
+ homeTimeLabelView,
|
|
|
|
|
+ homeDateLabelView,
|
|
|
|
|
+ homeActionsRow,
|
|
|
|
|
+ homeMeetingsPanel,
|
|
|
|
|
+ meetingsDayHeaderLabel,
|
|
|
|
|
+ meetingsStatusLabel,
|
|
|
|
|
+ meetingsPrevDayButton,
|
|
|
|
|
+ meetingsTodayButton,
|
|
|
|
|
+ meetingsNextDayButton,
|
|
|
|
|
+ emptyMeetingLabel,
|
|
|
|
|
+ meetingsScrollView,
|
|
|
|
|
+ refreshMeetingsButton
|
|
|
|
|
+ ]
|
|
|
|
|
+ dashboardViews.forEach { $0?.isHidden = isHome == false }
|
|
|
|
|
+
|
|
|
|
|
+ if isHome {
|
|
|
|
|
+ homePlaceholderLabel?.isHidden = true
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Keep non-Home pages empty for now.
|
|
|
|
|
+ homePlaceholderLabel?.isHidden = true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func sidebarSymbolName(for item: String, filled: Bool = false) -> String {
|
|
|
switch item {
|
|
switch item {
|
|
|
case "Home":
|
|
case "Home":
|
|
|
- return "house"
|
|
|
|
|
|
|
+ return filled ? "house.fill" : "house"
|
|
|
case "Meetings":
|
|
case "Meetings":
|
|
|
- return "video"
|
|
|
|
|
|
|
+ return filled ? "video.fill" : "video"
|
|
|
case "Chat":
|
|
case "Chat":
|
|
|
- return "message"
|
|
|
|
|
|
|
+ return filled ? "message.fill" : "message"
|
|
|
case "Scheduler":
|
|
case "Scheduler":
|
|
|
|
|
+ // `calendar.badge.clock.fill` is not available on macOS; keep a stable symbol.
|
|
|
return "calendar.badge.clock"
|
|
return "calendar.badge.clock"
|
|
|
case "Hub":
|
|
case "Hub":
|
|
|
return "square.grid.3x3"
|
|
return "square.grid.3x3"
|