|
@@ -13,6 +13,7 @@ import WebKit
|
|
13
|
class ViewController: NSViewController {
|
13
|
class ViewController: NSViewController {
|
|
14
|
private let googleOAuth = GoogleOAuthService.shared
|
14
|
private let googleOAuth = GoogleOAuthService.shared
|
|
15
|
private let zoomOAuth = ZoomOAuthService.shared
|
15
|
private let zoomOAuth = ZoomOAuthService.shared
|
|
|
|
16
|
+ private let loginStateKey = "zoom_app.isLoggedIn"
|
|
16
|
private let sidebarWidth: CGFloat = 78
|
17
|
private let sidebarWidth: CGFloat = 78
|
|
17
|
private let appBackground = NSColor(calibratedRed: 10 / 255, green: 11 / 255, blue: 12 / 255, alpha: 1)
|
18
|
private let appBackground = NSColor(calibratedRed: 10 / 255, green: 11 / 255, blue: 12 / 255, alpha: 1)
|
|
18
|
private let sidebarBackground = NSColor(calibratedRed: 16 / 255, green: 17 / 255, blue: 19 / 255, alpha: 1)
|
19
|
private let sidebarBackground = NSColor(calibratedRed: 16 / 255, green: 17 / 255, blue: 19 / 255, alpha: 1)
|
|
@@ -49,7 +50,11 @@ class ViewController: NSViewController {
|
|
49
|
super.viewDidAppear()
|
50
|
super.viewDidAppear()
|
|
50
|
view.window?.setContentSize(NSSize(width: 1020, height: 690))
|
51
|
view.window?.setContentSize(NSSize(width: 1020, height: 690))
|
|
51
|
view.window?.title = "zoom Workplace"
|
52
|
view.window?.title = "zoom Workplace"
|
|
52
|
- showLoginView()
|
|
|
|
|
|
53
|
+ if isUserLoggedIn() {
|
|
|
|
54
|
+ showHomeView(profile: nil)
|
|
|
|
55
|
+ } else {
|
|
|
|
56
|
+ showLoginView()
|
|
|
|
57
|
+ }
|
|
53
|
}
|
58
|
}
|
|
54
|
|
59
|
|
|
55
|
private func setupUI() {
|
60
|
private func setupUI() {
|
|
@@ -87,10 +92,19 @@ class ViewController: NSViewController {
|
|
87
|
homeView?.removeFromSuperview()
|
92
|
homeView?.removeFromSuperview()
|
|
88
|
homeView = makeHomeView(profile: profile)
|
93
|
homeView = makeHomeView(profile: profile)
|
|
89
|
if let homeView { attachToRoot(homeView) }
|
94
|
if let homeView { attachToRoot(homeView) }
|
|
|
|
95
|
+ persistLoggedInState(true)
|
|
90
|
startClock()
|
96
|
startClock()
|
|
91
|
Task { await loadScheduledMeetings() }
|
97
|
Task { await loadScheduledMeetings() }
|
|
92
|
}
|
98
|
}
|
|
93
|
|
99
|
|
|
|
|
100
|
+ private func isUserLoggedIn() -> Bool {
|
|
|
|
101
|
+ UserDefaults.standard.bool(forKey: loginStateKey)
|
|
|
|
102
|
+ }
|
|
|
|
103
|
+
|
|
|
|
104
|
+ private func persistLoggedInState(_ loggedIn: Bool) {
|
|
|
|
105
|
+ UserDefaults.standard.set(loggedIn, forKey: loginStateKey)
|
|
|
|
106
|
+ }
|
|
|
|
107
|
+
|
|
94
|
private func attachToRoot(_ subview: NSView) {
|
108
|
private func attachToRoot(_ subview: NSView) {
|
|
95
|
subview.translatesAutoresizingMaskIntoConstraints = false
|
109
|
subview.translatesAutoresizingMaskIntoConstraints = false
|
|
96
|
if subview.superview != rootContainer {
|
110
|
if subview.superview != rootContainer {
|
|
@@ -173,6 +187,13 @@ class ViewController: NSViewController {
|
|
173
|
}
|
187
|
}
|
|
174
|
}
|
188
|
}
|
|
175
|
|
189
|
|
|
|
|
190
|
+ @objc private func logoutTapped() {
|
|
|
|
191
|
+ googleOAuth.clearSavedTokens()
|
|
|
|
192
|
+ zoomOAuth.clearSavedTokens()
|
|
|
|
193
|
+ persistLoggedInState(false)
|
|
|
|
194
|
+ showLoginView()
|
|
|
|
195
|
+ }
|
|
|
|
196
|
+
|
|
176
|
@MainActor
|
197
|
@MainActor
|
|
177
|
private func resetLoginSigningInState() {
|
198
|
private func resetLoginSigningInState() {
|
|
178
|
isSigningIn = false
|
199
|
isSigningIn = false
|
|
@@ -556,6 +577,13 @@ class ViewController: NSViewController {
|
|
556
|
profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.22).cgColor
|
577
|
profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.22).cgColor
|
|
557
|
profileChip.layer?.cornerRadius = 9
|
578
|
profileChip.layer?.cornerRadius = 9
|
|
558
|
let name = makeLabel(profile?.name ?? "User", size: 13, color: primaryText, weight: .semibold, centered: false)
|
579
|
let name = makeLabel(profile?.name ?? "User", size: 13, color: primaryText, weight: .semibold, centered: false)
|
|
|
|
580
|
+ let logoutButton = NSButton(title: "Logout", target: self, action: #selector(logoutTapped))
|
|
|
|
581
|
+ logoutButton.isBordered = false
|
|
|
|
582
|
+ logoutButton.font = .systemFont(ofSize: 13, weight: .semibold)
|
|
|
|
583
|
+ logoutButton.contentTintColor = primaryText
|
|
|
|
584
|
+ logoutButton.wantsLayer = true
|
|
|
|
585
|
+ logoutButton.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.07).cgColor
|
|
|
|
586
|
+ logoutButton.layer?.cornerRadius = 8
|
|
559
|
|
587
|
|
|
560
|
let welcome = makeLabel("Home", size: 15, color: secondaryText, weight: .medium, centered: false)
|
588
|
let welcome = makeLabel("Home", size: 15, color: secondaryText, weight: .medium, centered: false)
|
|
561
|
let timeTitle = makeLabel("--:--", size: 50, color: primaryText, weight: .bold, centered: true)
|
589
|
let timeTitle = makeLabel("--:--", size: 50, color: primaryText, weight: .bold, centered: true)
|
|
@@ -610,7 +638,7 @@ class ViewController: NSViewController {
|
|
610
|
contentColumn.translatesAutoresizingMaskIntoConstraints = false
|
638
|
contentColumn.translatesAutoresizingMaskIntoConstraints = false
|
|
611
|
content.addSubview(contentColumn)
|
639
|
content.addSubview(contentColumn)
|
|
612
|
|
640
|
|
|
613
|
- [topBar, searchPill, search, profileChip, name, welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
|
|
|
|
|
|
641
|
+ [topBar, searchPill, search, profileChip, name, logoutButton, welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
|
|
614
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
642
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
615
|
contentColumn.addSubview($0)
|
643
|
contentColumn.addSubview($0)
|
|
616
|
}
|
644
|
}
|
|
@@ -646,6 +674,11 @@ class ViewController: NSViewController {
|
|
646
|
name.trailingAnchor.constraint(equalTo: profileChip.trailingAnchor, constant: -12),
|
674
|
name.trailingAnchor.constraint(equalTo: profileChip.trailingAnchor, constant: -12),
|
|
647
|
name.centerYAnchor.constraint(equalTo: profileChip.centerYAnchor),
|
675
|
name.centerYAnchor.constraint(equalTo: profileChip.centerYAnchor),
|
|
648
|
|
676
|
|
|
|
|
677
|
+ logoutButton.trailingAnchor.constraint(equalTo: profileChip.leadingAnchor, constant: -10),
|
|
|
|
678
|
+ logoutButton.centerYAnchor.constraint(equalTo: topBar.centerYAnchor),
|
|
|
|
679
|
+ logoutButton.widthAnchor.constraint(equalToConstant: 76),
|
|
|
|
680
|
+ logoutButton.heightAnchor.constraint(equalToConstant: 32),
|
|
|
|
681
|
+
|
|
649
|
welcome.topAnchor.constraint(equalTo: topBar.bottomAnchor, constant: 18),
|
682
|
welcome.topAnchor.constraint(equalTo: topBar.bottomAnchor, constant: 18),
|
|
650
|
welcome.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
683
|
welcome.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
651
|
|
684
|
|
|
@@ -1154,6 +1187,10 @@ final class GoogleOAuthService: NSObject {
|
|
1154
|
|
1187
|
|
|
1155
|
func loadTokens() -> GoogleOAuthTokens? { try? tokenStore.readTokens() }
|
1188
|
func loadTokens() -> GoogleOAuthTokens? { try? tokenStore.readTokens() }
|
|
1156
|
|
1189
|
|
|
|
|
1190
|
+ func clearSavedTokens() {
|
|
|
|
1191
|
+ tokenStore.clearTokens()
|
|
|
|
1192
|
+ }
|
|
|
|
1193
|
+
|
|
1157
|
func fetchUserProfile(accessToken: String) async throws -> GoogleUserProfile {
|
1194
|
func fetchUserProfile(accessToken: String) async throws -> GoogleUserProfile {
|
|
1158
|
var request = URLRequest(url: URL(string: "https://openidconnect.googleapis.com/v1/userinfo")!)
|
1195
|
var request = URLRequest(url: URL(string: "https://openidconnect.googleapis.com/v1/userinfo")!)
|
|
1159
|
request.httpMethod = "GET"
|
1196
|
request.httpMethod = "GET"
|
|
@@ -1299,6 +1336,10 @@ final class KeychainTokenStore {
|
|
1299
|
let data = try JSONEncoder().encode(tokens)
|
1336
|
let data = try JSONEncoder().encode(tokens)
|
|
1300
|
defaults.set(data, forKey: defaultsKey)
|
1337
|
defaults.set(data, forKey: defaultsKey)
|
|
1301
|
}
|
1338
|
}
|
|
|
|
1339
|
+
|
|
|
|
1340
|
+ func clearTokens() {
|
|
|
|
1341
|
+ defaults.removeObject(forKey: defaultsKey)
|
|
|
|
1342
|
+ }
|
|
1302
|
}
|
1343
|
}
|
|
1303
|
|
1344
|
|
|
1304
|
private final class OAuthLoopbackServer {
|
1345
|
private final class OAuthLoopbackServer {
|