|
@@ -13,6 +13,7 @@ import WebKit
|
|
|
class ViewController: NSViewController {
|
|
class ViewController: NSViewController {
|
|
|
private let googleOAuth = GoogleOAuthService.shared
|
|
private let googleOAuth = GoogleOAuthService.shared
|
|
|
private let zoomOAuth = ZoomOAuthService.shared
|
|
private let zoomOAuth = ZoomOAuthService.shared
|
|
|
|
|
+ private let loginStateKey = "zoom_app.isLoggedIn"
|
|
|
private let sidebarWidth: CGFloat = 78
|
|
private let sidebarWidth: CGFloat = 78
|
|
|
private let appBackground = NSColor(calibratedRed: 10 / 255, green: 11 / 255, blue: 12 / 255, alpha: 1)
|
|
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 sidebarBackground = NSColor(calibratedRed: 16 / 255, green: 17 / 255, blue: 19 / 255, alpha: 1)
|
|
@@ -49,7 +50,11 @@ class ViewController: NSViewController {
|
|
|
super.viewDidAppear()
|
|
super.viewDidAppear()
|
|
|
view.window?.setContentSize(NSSize(width: 1020, height: 690))
|
|
view.window?.setContentSize(NSSize(width: 1020, height: 690))
|
|
|
view.window?.title = "zoom Workplace"
|
|
view.window?.title = "zoom Workplace"
|
|
|
- showLoginView()
|
|
|
|
|
|
|
+ if isUserLoggedIn() {
|
|
|
|
|
+ showHomeView(profile: nil)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showLoginView()
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func setupUI() {
|
|
private func setupUI() {
|
|
@@ -87,10 +92,19 @@ class ViewController: NSViewController {
|
|
|
homeView?.removeFromSuperview()
|
|
homeView?.removeFromSuperview()
|
|
|
homeView = makeHomeView(profile: profile)
|
|
homeView = makeHomeView(profile: profile)
|
|
|
if let homeView { attachToRoot(homeView) }
|
|
if let homeView { attachToRoot(homeView) }
|
|
|
|
|
+ persistLoggedInState(true)
|
|
|
startClock()
|
|
startClock()
|
|
|
Task { await loadScheduledMeetings() }
|
|
Task { await loadScheduledMeetings() }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private func isUserLoggedIn() -> Bool {
|
|
|
|
|
+ UserDefaults.standard.bool(forKey: loginStateKey)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func persistLoggedInState(_ loggedIn: Bool) {
|
|
|
|
|
+ UserDefaults.standard.set(loggedIn, forKey: loginStateKey)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private func attachToRoot(_ subview: NSView) {
|
|
private func attachToRoot(_ subview: NSView) {
|
|
|
subview.translatesAutoresizingMaskIntoConstraints = false
|
|
subview.translatesAutoresizingMaskIntoConstraints = false
|
|
|
if subview.superview != rootContainer {
|
|
if subview.superview != rootContainer {
|
|
@@ -173,6 +187,13 @@ class ViewController: NSViewController {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @objc private func logoutTapped() {
|
|
|
|
|
+ googleOAuth.clearSavedTokens()
|
|
|
|
|
+ zoomOAuth.clearSavedTokens()
|
|
|
|
|
+ persistLoggedInState(false)
|
|
|
|
|
+ showLoginView()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@MainActor
|
|
@MainActor
|
|
|
private func resetLoginSigningInState() {
|
|
private func resetLoginSigningInState() {
|
|
|
isSigningIn = false
|
|
isSigningIn = false
|
|
@@ -556,6 +577,13 @@ class ViewController: NSViewController {
|
|
|
profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.22).cgColor
|
|
profileChip.layer?.backgroundColor = accentBlue.withAlphaComponent(0.22).cgColor
|
|
|
profileChip.layer?.cornerRadius = 9
|
|
profileChip.layer?.cornerRadius = 9
|
|
|
let name = makeLabel(profile?.name ?? "User", size: 13, color: primaryText, weight: .semibold, centered: false)
|
|
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
|
|
|
|
|
|
|
|
let welcome = makeLabel("Home", size: 15, color: secondaryText, weight: .medium, centered: false)
|
|
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: 50, color: primaryText, weight: .bold, centered: true)
|
|
@@ -610,7 +638,7 @@ class ViewController: NSViewController {
|
|
|
contentColumn.translatesAutoresizingMaskIntoConstraints = false
|
|
contentColumn.translatesAutoresizingMaskIntoConstraints = false
|
|
|
content.addSubview(contentColumn)
|
|
content.addSubview(contentColumn)
|
|
|
|
|
|
|
|
- [topBar, searchPill, search, profileChip, name, welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
|
|
|
|
|
|
|
+ [topBar, searchPill, search, profileChip, name, logoutButton, welcome, timeTitle, dateTitle, actions, panel, panelHeader, meetingsStatus, noMeeting, meetingsScrollView, openRecordings].forEach {
|
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
|
contentColumn.addSubview($0)
|
|
contentColumn.addSubview($0)
|
|
|
}
|
|
}
|
|
@@ -646,6 +674,11 @@ class ViewController: NSViewController {
|
|
|
name.trailingAnchor.constraint(equalTo: profileChip.trailingAnchor, constant: -12),
|
|
name.trailingAnchor.constraint(equalTo: profileChip.trailingAnchor, constant: -12),
|
|
|
name.centerYAnchor.constraint(equalTo: profileChip.centerYAnchor),
|
|
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: 18),
|
|
|
welcome.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
welcome.centerXAnchor.constraint(equalTo: contentColumn.centerXAnchor),
|
|
|
|
|
|
|
@@ -1154,6 +1187,10 @@ final class GoogleOAuthService: NSObject {
|
|
|
|
|
|
|
|
func loadTokens() -> GoogleOAuthTokens? { try? tokenStore.readTokens() }
|
|
func loadTokens() -> GoogleOAuthTokens? { try? tokenStore.readTokens() }
|
|
|
|
|
|
|
|
|
|
+ func clearSavedTokens() {
|
|
|
|
|
+ tokenStore.clearTokens()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
func fetchUserProfile(accessToken: String) async throws -> GoogleUserProfile {
|
|
func fetchUserProfile(accessToken: String) async throws -> GoogleUserProfile {
|
|
|
var request = URLRequest(url: URL(string: "https://openidconnect.googleapis.com/v1/userinfo")!)
|
|
var request = URLRequest(url: URL(string: "https://openidconnect.googleapis.com/v1/userinfo")!)
|
|
|
request.httpMethod = "GET"
|
|
request.httpMethod = "GET"
|
|
@@ -1299,6 +1336,10 @@ final class KeychainTokenStore {
|
|
|
let data = try JSONEncoder().encode(tokens)
|
|
let data = try JSONEncoder().encode(tokens)
|
|
|
defaults.set(data, forKey: defaultsKey)
|
|
defaults.set(data, forKey: defaultsKey)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ func clearTokens() {
|
|
|
|
|
+ defaults.removeObject(forKey: defaultsKey)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private final class OAuthLoopbackServer {
|
|
private final class OAuthLoopbackServer {
|