|
|
@@ -54,10 +54,20 @@ class ViewController: NSViewController {
|
|
54
|
54
|
private weak var meetingsNextDayButton: NSButton?
|
|
55
|
55
|
private weak var meetingsTodayButton: NSButton?
|
|
56
|
56
|
private weak var refreshMeetingsButton: NSButton?
|
|
|
57
|
+ private weak var homeWelcomeLabel: NSTextField?
|
|
|
58
|
+ private weak var homeTimeLabelView: NSTextField?
|
|
|
59
|
+ private weak var homeDateLabelView: NSTextField?
|
|
|
60
|
+ private weak var homeActionsRow: NSView?
|
|
|
61
|
+ private weak var homeMeetingsPanel: NSView?
|
|
|
62
|
+ private weak var homePlaceholderLabel: NSTextField?
|
|
57
|
63
|
private weak var homeSearchField: NSTextField?
|
|
58
|
64
|
private weak var homeSearchPill: NSView?
|
|
59
|
65
|
private var allScheduledMeetings: [ScheduledMeeting] = []
|
|
60
|
66
|
private var selectedMeetingsDayStart: Date = Calendar.current.startOfDay(for: Date())
|
|
|
67
|
+ private var selectedHomeSidebarItem: String = "Home"
|
|
|
68
|
+ private var homeSidebarRowViews: [String: NSView] = [:]
|
|
|
69
|
+ private var homeSidebarIconViews: [String: NSImageView] = [:]
|
|
|
70
|
+ private var homeSidebarLabels: [String: NSTextField] = [:]
|
|
61
|
71
|
private var searchTextObserver: NSObjectProtocol?
|
|
62
|
72
|
private var searchShortcutMonitor: Any?
|
|
63
|
73
|
private var searchOutsideClickMonitor: Any?
|
|
|
@@ -160,6 +170,8 @@ class ViewController: NSViewController {
|
|
160
|
170
|
startClock()
|
|
161
|
171
|
startMeetingsAutoRefresh()
|
|
162
|
172
|
triggerMeetingsRefresh(force: true)
|
|
|
173
|
+ updateHomeSidebarHighlight()
|
|
|
174
|
+ updateSelectedHomeSectionUI()
|
|
163
|
175
|
}
|
|
164
|
176
|
|
|
165
|
177
|
private func isUserLoggedIn() -> Bool {
|
|
|
@@ -930,7 +942,10 @@ class ViewController: NSViewController {
|
|
930
|
942
|
let chromeHeader = NSView()
|
|
931
|
943
|
chromeHeader.wantsLayer = true
|
|
932
|
944
|
chromeHeader.layer?.backgroundColor = chromeUnifiedBackground.cgColor
|
|
933
|
|
- let sidebar = makeSidebar(items: ["Home", "Meetings", "Scheduler"], selected: "Home", style: .home)
|
|
|
945
|
+ homeSidebarRowViews = [:]
|
|
|
946
|
+ homeSidebarIconViews = [:]
|
|
|
947
|
+ homeSidebarLabels = [:]
|
|
|
948
|
+ let sidebar = makeSidebar(items: ["Home", "Meetings", "Scheduler"], selected: selectedHomeSidebarItem, style: .home)
|
|
934
|
949
|
let content = NSView()
|
|
935
|
950
|
content.wantsLayer = true
|
|
936
|
951
|
content.layer?.backgroundColor = NSColor.clear.cgColor
|
|
|
@@ -1140,6 +1155,9 @@ class ViewController: NSViewController {
|
|
1140
|
1155
|
refreshMeetingsButton.layer?.borderColor = NSColor.white.withAlphaComponent(0.07).cgColor
|
|
1141
|
1156
|
self.refreshMeetingsButton = refreshMeetingsButton
|
|
1142
|
1157
|
|
|
|
1158
|
+ let placeholder = makeLabel("Coming soon", size: 22, color: secondaryText, weight: .semibold, centered: true)
|
|
|
1159
|
+ placeholder.isHidden = true
|
|
|
1160
|
+
|
|
1143
|
1161
|
let contentColumn = NSView()
|
|
1144
|
1162
|
contentColumn.translatesAutoresizingMaskIntoConstraints = false
|
|
1145
|
1163
|
content.addSubview(topBar)
|
|
|
@@ -1158,7 +1176,7 @@ class ViewController: NSViewController {
|
|
1158
|
1176
|
[searchIcon, searchField, searchHintLabel].forEach {
|
|
1159
|
1177
|
searchPill.addSubview($0)
|
|
1160
|
1178
|
}
|
|
1161
|
|
- [welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, meetingsDayNav, noMeeting, meetingsScrollView, refreshMeetingsButton].forEach {
|
|
|
1179
|
+ [welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, meetingsDayNav, noMeeting, meetingsScrollView, refreshMeetingsButton, placeholder].forEach {
|
|
1162
|
1180
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
1163
|
1181
|
contentColumn.addSubview($0)
|
|
1164
|
1182
|
}
|
|
|
@@ -1261,11 +1279,20 @@ class ViewController: NSViewController {
|
|
1261
|
1279
|
refreshMeetingsButton.leadingAnchor.constraint(equalTo: panel.leadingAnchor, constant: 14),
|
|
1262
|
1280
|
refreshMeetingsButton.trailingAnchor.constraint(equalTo: panel.trailingAnchor, constant: -14),
|
|
1263
|
1281
|
refreshMeetingsButton.bottomAnchor.constraint(equalTo: panel.bottomAnchor, constant: -12),
|
|
1264
|
|
- refreshMeetingsButton.heightAnchor.constraint(equalToConstant: 40)
|
|
|
1282
|
+ refreshMeetingsButton.heightAnchor.constraint(equalToConstant: 40),
|
|
|
1283
|
+
|
|
|
1284
|
+ placeholder.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
|
1285
|
+ placeholder.centerYAnchor.constraint(equalTo: contentColumn.centerYAnchor)
|
|
1265
|
1286
|
])
|
|
1266
|
1287
|
|
|
1267
|
1288
|
timeLabel = timeTitle
|
|
1268
|
1289
|
dateLabel = dateTitle
|
|
|
1290
|
+ homeWelcomeLabel = welcome
|
|
|
1291
|
+ homeTimeLabelView = timeTitle
|
|
|
1292
|
+ homeDateLabelView = dateTitle
|
|
|
1293
|
+ homeActionsRow = actions
|
|
|
1294
|
+ homeMeetingsPanel = panel
|
|
|
1295
|
+ homePlaceholderLabel = placeholder
|
|
1269
|
1296
|
meetingsDayHeaderLabel = panelHeader
|
|
1270
|
1297
|
meetingsListStack = meetingsStack
|
|
1271
|
1298
|
meetingsStatusLabel = meetingsStatus
|
|
|
@@ -1356,16 +1383,24 @@ class ViewController: NSViewController {
|
|
1356
|
1383
|
|
|
1357
|
1384
|
let iconView = NSImageView()
|
|
1358
|
1385
|
iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
1359
|
|
- iconView.contentTintColor = primaryText
|
|
|
1386
|
+ iconView.contentTintColor = selectedRow ? primaryText : secondaryText
|
|
1360
|
1387
|
iconView.imageScaling = .scaleProportionallyUpOrDown
|
|
1361
|
1388
|
iconView.symbolConfiguration = NSImage.SymbolConfiguration(pointSize: 20, weight: .regular)
|
|
1362
|
|
- iconView.image = NSImage(systemSymbolName: sidebarSymbolName(for: item), accessibilityDescription: item)
|
|
|
1389
|
+ iconView.image = NSImage(systemSymbolName: sidebarSymbolName(for: item, filled: selectedRow), accessibilityDescription: item)
|
|
1363
|
1390
|
iconContainer.addSubview(iconView)
|
|
1364
|
1391
|
|
|
1365
|
1392
|
let label = makeLabel(item, size: 10, color: selectedRow ? primaryText : secondaryText, weight: .regular, centered: true)
|
|
1366
|
1393
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
1367
|
1394
|
row.addSubview(label)
|
|
1368
|
1395
|
|
|
|
1396
|
+ let hit = NSButton(title: "", target: self, action: #selector(homeSidebarItemTapped(_:)))
|
|
|
1397
|
+ hit.identifier = NSUserInterfaceItemIdentifier(item)
|
|
|
1398
|
+ hit.isBordered = false
|
|
|
1399
|
+ hit.bezelStyle = .shadowlessSquare
|
|
|
1400
|
+ hit.focusRingType = .none
|
|
|
1401
|
+ hit.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1402
|
+ row.addSubview(hit, positioned: .above, relativeTo: nil)
|
|
|
1403
|
+
|
|
1369
|
1404
|
NSLayoutConstraint.activate([
|
|
1370
|
1405
|
iconContainer.topAnchor.constraint(equalTo: row.topAnchor, constant: 9),
|
|
1371
|
1406
|
iconContainer.centerXAnchor.constraint(equalTo: row.centerXAnchor),
|
|
|
@@ -1379,9 +1414,18 @@ class ViewController: NSViewController {
|
|
1379
|
1414
|
|
|
1380
|
1415
|
label.topAnchor.constraint(equalTo: iconContainer.bottomAnchor, constant: 6),
|
|
1381
|
1416
|
label.centerXAnchor.constraint(equalTo: row.centerXAnchor),
|
|
1382
|
|
- label.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: -8)
|
|
|
1417
|
+ label.bottomAnchor.constraint(equalTo: row.bottomAnchor, constant: -8),
|
|
|
1418
|
+
|
|
|
1419
|
+ hit.leadingAnchor.constraint(equalTo: row.leadingAnchor),
|
|
|
1420
|
+ hit.trailingAnchor.constraint(equalTo: row.trailingAnchor),
|
|
|
1421
|
+ hit.topAnchor.constraint(equalTo: row.topAnchor),
|
|
|
1422
|
+ hit.bottomAnchor.constraint(equalTo: row.bottomAnchor)
|
|
1383
|
1423
|
])
|
|
1384
|
1424
|
|
|
|
1425
|
+ homeSidebarRowViews[item] = row
|
|
|
1426
|
+ homeSidebarIconViews[item] = iconView
|
|
|
1427
|
+ homeSidebarLabels[item] = label
|
|
|
1428
|
+
|
|
1385
|
1429
|
if item == "Hub" {
|
|
1386
|
1430
|
let badge = makeSidebarBadge(text: "1")
|
|
1387
|
1431
|
badge.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -1491,15 +1535,68 @@ class ViewController: NSViewController {
|
|
1491
|
1535
|
return sidebar
|
|
1492
|
1536
|
}
|
|
1493
|
1537
|
|
|
1494
|
|
- private func sidebarSymbolName(for item: String) -> String {
|
|
|
1538
|
+ @objc private func homeSidebarItemTapped(_ sender: NSButton) {
|
|
|
1539
|
+ guard let item = sender.identifier?.rawValue else { return }
|
|
|
1540
|
+ selectedHomeSidebarItem = item
|
|
|
1541
|
+ updateHomeSidebarHighlight()
|
|
|
1542
|
+ updateSelectedHomeSectionUI()
|
|
|
1543
|
+ }
|
|
|
1544
|
+
|
|
|
1545
|
+ @MainActor
|
|
|
1546
|
+ private func updateHomeSidebarHighlight() {
|
|
|
1547
|
+ for (item, row) in homeSidebarRowViews {
|
|
|
1548
|
+ let selected = item == selectedHomeSidebarItem
|
|
|
1549
|
+ row.layer?.backgroundColor = selected ? sidebarActiveBackground.withAlphaComponent(0.95).cgColor : NSColor.clear.cgColor
|
|
|
1550
|
+ homeSidebarLabels[item]?.textColor = selected ? primaryText : secondaryText
|
|
|
1551
|
+ homeSidebarIconViews[item]?.contentTintColor = selected ? primaryText : secondaryText
|
|
|
1552
|
+ let symbolName = sidebarSymbolName(for: item, filled: selected)
|
|
|
1553
|
+ if let image = NSImage(systemSymbolName: symbolName, accessibilityDescription: item) {
|
|
|
1554
|
+ homeSidebarIconViews[item]?.image = image
|
|
|
1555
|
+ }
|
|
|
1556
|
+ }
|
|
|
1557
|
+ }
|
|
|
1558
|
+
|
|
|
1559
|
+ @MainActor
|
|
|
1560
|
+ private func updateSelectedHomeSectionUI() {
|
|
|
1561
|
+ let isHome = selectedHomeSidebarItem == "Home"
|
|
|
1562
|
+ let title = selectedHomeSidebarItem
|
|
|
1563
|
+
|
|
|
1564
|
+ homeWelcomeLabel?.stringValue = title
|
|
|
1565
|
+
|
|
|
1566
|
+ let dashboardViews: [NSView?] = [
|
|
|
1567
|
+ homeTimeLabelView,
|
|
|
1568
|
+ homeDateLabelView,
|
|
|
1569
|
+ homeActionsRow,
|
|
|
1570
|
+ homeMeetingsPanel,
|
|
|
1571
|
+ meetingsDayHeaderLabel,
|
|
|
1572
|
+ meetingsStatusLabel,
|
|
|
1573
|
+ meetingsPrevDayButton,
|
|
|
1574
|
+ meetingsTodayButton,
|
|
|
1575
|
+ meetingsNextDayButton,
|
|
|
1576
|
+ emptyMeetingLabel,
|
|
|
1577
|
+ meetingsScrollView,
|
|
|
1578
|
+ refreshMeetingsButton
|
|
|
1579
|
+ ]
|
|
|
1580
|
+ dashboardViews.forEach { $0?.isHidden = isHome == false }
|
|
|
1581
|
+
|
|
|
1582
|
+ if isHome {
|
|
|
1583
|
+ homePlaceholderLabel?.isHidden = true
|
|
|
1584
|
+ } else {
|
|
|
1585
|
+ // Keep non-Home pages empty for now.
|
|
|
1586
|
+ homePlaceholderLabel?.isHidden = true
|
|
|
1587
|
+ }
|
|
|
1588
|
+ }
|
|
|
1589
|
+
|
|
|
1590
|
+ private func sidebarSymbolName(for item: String, filled: Bool = false) -> String {
|
|
1495
|
1591
|
switch item {
|
|
1496
|
1592
|
case "Home":
|
|
1497
|
|
- return "house"
|
|
|
1593
|
+ return filled ? "house.fill" : "house"
|
|
1498
|
1594
|
case "Meetings":
|
|
1499
|
|
- return "video"
|
|
|
1595
|
+ return filled ? "video.fill" : "video"
|
|
1500
|
1596
|
case "Chat":
|
|
1501
|
|
- return "message"
|
|
|
1597
|
+ return filled ? "message.fill" : "message"
|
|
1502
|
1598
|
case "Scheduler":
|
|
|
1599
|
+ // `calendar.badge.clock.fill` is not available on macOS; keep a stable symbol.
|
|
1503
|
1600
|
return "calendar.badge.clock"
|
|
1504
|
1601
|
case "Hub":
|
|
1505
|
1602
|
return "square.grid.3x3"
|