|
@@ -1906,7 +1906,7 @@ private extension ViewController {
|
|
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
|
|
|
- let signedIn = googleOAuth.loadTokens() != nil
|
|
|
|
|
|
|
+ let signedIn = hasGoogleSessionAvailable()
|
|
|
let titleText = signedIn ? (scheduleCurrentProfile?.name ?? "Google account connected") : "Google account not connected"
|
|
let titleText = signedIn ? (scheduleCurrentProfile?.name ?? "Google account connected") : "Google account not connected"
|
|
|
let subtitleText = signedIn ? (scheduleCurrentProfile?.email ?? "Signed in") : "Sign in to sync your meetings and calendar."
|
|
let subtitleText = signedIn ? (scheduleCurrentProfile?.email ?? "Signed in") : "Sign in to sync your meetings and calendar."
|
|
|
|
|
|
|
@@ -1978,7 +1978,7 @@ private extension ViewController {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@objc private func settingsGoogleActionButtonClicked(_ sender: NSButton) {
|
|
@objc private func settingsGoogleActionButtonClicked(_ sender: NSButton) {
|
|
|
- if googleOAuth.loadTokens() == nil {
|
|
|
|
|
|
|
+ if hasGoogleSessionAvailable() == false {
|
|
|
scheduleConnectClicked()
|
|
scheduleConnectClicked()
|
|
|
} else {
|
|
} else {
|
|
|
performGoogleSignOut()
|
|
performGoogleSignOut()
|
|
@@ -2297,7 +2297,7 @@ private extension ViewController {
|
|
|
])
|
|
])
|
|
|
mainContentHost = host
|
|
mainContentHost = host
|
|
|
|
|
|
|
|
- if googleOAuth.loadTokens() != nil, let profile = scheduleCurrentProfile {
|
|
|
|
|
|
|
+ if hasGoogleSessionAvailable(), let profile = scheduleCurrentProfile {
|
|
|
applyGoogleProfile(profile)
|
|
applyGoogleProfile(profile)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -3612,7 +3612,7 @@ private extension ViewController {
|
|
|
titleRowSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
titleRowSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
|
|
|
|
|
|
titleRow.addArrangedSubview(titleLabel)
|
|
titleRow.addArrangedSubview(titleLabel)
|
|
|
- if googleOAuth.loadTokens() != nil && storeKitCoordinator.hasPremiumAccess {
|
|
|
|
|
|
|
+ if hasGoogleSessionAvailable() && storeKitCoordinator.hasPremiumAccess {
|
|
|
titleRow.addArrangedSubview(makeSchedulePageAddButton())
|
|
titleRow.addArrangedSubview(makeSchedulePageAddButton())
|
|
|
titleRow.setCustomSpacing(12, after: titleLabel)
|
|
titleRow.setCustomSpacing(12, after: titleLabel)
|
|
|
}
|
|
}
|
|
@@ -5031,8 +5031,16 @@ private final class SettingsMenuViewController: NSViewController {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private extension ViewController {
|
|
private extension ViewController {
|
|
|
|
|
+ private func hasGoogleSessionAvailable() -> Bool {
|
|
|
|
|
+ guard let tokens = googleOAuth.loadTokens() else { return false }
|
|
|
|
|
+ if tokens.expiresAt.timeIntervalSinceNow > 60 {
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ return (tokens.refreshToken?.isEmpty == false)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private func requireGoogleLoginForCalendarScheduling() -> Bool {
|
|
private func requireGoogleLoginForCalendarScheduling() -> Bool {
|
|
|
- guard googleOAuth.loadTokens() != nil else {
|
|
|
|
|
|
|
+ guard hasGoogleSessionAvailable() else {
|
|
|
showSimpleAlert(
|
|
showSimpleAlert(
|
|
|
title: "Connect Google",
|
|
title: "Connect Google",
|
|
|
message: "Sign in with Google first to schedule a meeting from Calendar."
|
|
message: "Sign in with Google first to schedule a meeting from Calendar."
|
|
@@ -5492,7 +5500,7 @@ private extension ViewController {
|
|
|
f.dateFormat = "EEE, d MMM"
|
|
f.dateFormat = "EEE, d MMM"
|
|
|
|
|
|
|
|
if meetings.isEmpty {
|
|
if meetings.isEmpty {
|
|
|
- calendarPageDaySummaryLabel?.stringValue = googleOAuth.loadTokens() == nil
|
|
|
|
|
|
|
+ calendarPageDaySummaryLabel?.stringValue = hasGoogleSessionAvailable() == false
|
|
|
? "Connect Google to see meetings"
|
|
? "Connect Google to see meetings"
|
|
|
: "No meetings on \(f.string(from: selectedDay))"
|
|
: "No meetings on \(f.string(from: selectedDay))"
|
|
|
} else if meetings.count == 1 {
|
|
} else if meetings.count == 1 {
|
|
@@ -6128,11 +6136,11 @@ private extension ViewController {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func scheduleInitialHeadingText() -> String {
|
|
private func scheduleInitialHeadingText() -> String {
|
|
|
- googleOAuth.loadTokens() == nil ? "Connect Google to see meetings" : "Loading…"
|
|
|
|
|
|
|
+ hasGoogleSessionAvailable() ? "Loading…" : "Connect Google to see meetings"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func schedulePageInitialHeadingText() -> String {
|
|
private func schedulePageInitialHeadingText() -> String {
|
|
|
- googleOAuth.loadTokens() == nil ? "Connect Google to see meetings" : "Loading schedule…"
|
|
|
|
|
|
|
+ hasGoogleSessionAvailable() ? "Loading schedule…" : "Connect Google to see meetings"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@objc func scheduleFilterDropdownChanged(_ sender: NSPopUpButton) {
|
|
@objc func scheduleFilterDropdownChanged(_ sender: NSPopUpButton) {
|
|
@@ -6239,7 +6247,7 @@ private extension ViewController {
|
|
|
|
|
|
|
|
private func scheduleHeadingText(for meetings: [ScheduledMeeting]) -> String {
|
|
private func scheduleHeadingText(for meetings: [ScheduledMeeting]) -> String {
|
|
|
guard let first = meetings.first else {
|
|
guard let first = meetings.first else {
|
|
|
- return googleOAuth.loadTokens() == nil ? "Connect Google to see meetings" : "No upcoming meetings"
|
|
|
|
|
|
|
+ return hasGoogleSessionAvailable() ? "No upcoming meetings" : "Connect Google to see meetings"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
let day = Calendar.current.startOfDay(for: first.startDate)
|
|
let day = Calendar.current.startOfDay(for: first.startDate)
|
|
@@ -6276,7 +6284,7 @@ private extension ViewController {
|
|
|
empty.heightAnchor.constraint(equalToConstant: 150).isActive = true
|
|
empty.heightAnchor.constraint(equalToConstant: 150).isActive = true
|
|
|
styleSurface(empty, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
styleSurface(empty, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
|
|
|
- let label = textLabel(googleOAuth.loadTokens() == nil ? "Connect to load schedule" : "No meetings", font: typography.cardSubtitle, color: palette.textSecondary)
|
|
|
|
|
|
|
+ let label = textLabel(hasGoogleSessionAvailable() ? "No meetings" : "Connect to load schedule", font: typography.cardSubtitle, color: palette.textSecondary)
|
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
empty.addSubview(label)
|
|
empty.addSubview(label)
|
|
|
NSLayoutConstraint.activate([
|
|
NSLayoutConstraint.activate([
|
|
@@ -6413,7 +6421,7 @@ private extension ViewController {
|
|
|
empty.translatesAutoresizingMaskIntoConstraints = false
|
|
empty.translatesAutoresizingMaskIntoConstraints = false
|
|
|
empty.heightAnchor.constraint(equalToConstant: 140).isActive = true
|
|
empty.heightAnchor.constraint(equalToConstant: 140).isActive = true
|
|
|
styleSurface(empty, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
styleSurface(empty, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
- let label = textLabel(googleOAuth.loadTokens() == nil ? "Connect to load schedule" : "No meetings for selected filters", font: typography.cardSubtitle, color: palette.textSecondary)
|
|
|
|
|
|
|
+ let label = textLabel(hasGoogleSessionAvailable() ? "No meetings for selected filters" : "Connect to load schedule", font: typography.cardSubtitle, color: palette.textSecondary)
|
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
|
empty.addSubview(label)
|
|
empty.addSubview(label)
|
|
|
NSLayoutConstraint.activate([
|
|
NSLayoutConstraint.activate([
|
|
@@ -6480,7 +6488,7 @@ private extension ViewController {
|
|
|
|
|
|
|
|
private func loadSchedule() async {
|
|
private func loadSchedule() async {
|
|
|
do {
|
|
do {
|
|
|
- if googleOAuth.loadTokens() == nil {
|
|
|
|
|
|
|
+ if hasGoogleSessionAvailable() == false {
|
|
|
await MainActor.run {
|
|
await MainActor.run {
|
|
|
updateGoogleAuthButtonTitle()
|
|
updateGoogleAuthButtonTitle()
|
|
|
applyGoogleProfile(nil)
|
|
applyGoogleProfile(nil)
|
|
@@ -6523,7 +6531,7 @@ private extension ViewController {
|
|
|
} catch {
|
|
} catch {
|
|
|
await MainActor.run {
|
|
await MainActor.run {
|
|
|
updateGoogleAuthButtonTitle()
|
|
updateGoogleAuthButtonTitle()
|
|
|
- if googleOAuth.loadTokens() == nil {
|
|
|
|
|
|
|
+ if hasGoogleSessionAvailable() == false {
|
|
|
applyGoogleProfile(nil)
|
|
applyGoogleProfile(nil)
|
|
|
}
|
|
}
|
|
|
scheduleDateHeadingLabel?.stringValue = "Couldn’t load schedule"
|
|
scheduleDateHeadingLabel?.stringValue = "Couldn’t load schedule"
|
|
@@ -6576,7 +6584,7 @@ private extension ViewController {
|
|
|
Task { [weak self] in
|
|
Task { [weak self] in
|
|
|
guard let self else { return }
|
|
guard let self else { return }
|
|
|
do {
|
|
do {
|
|
|
- if self.googleOAuth.loadTokens() != nil {
|
|
|
|
|
|
|
+ if self.hasGoogleSessionAvailable() {
|
|
|
await MainActor.run { self.showGoogleAccountMenu() }
|
|
await MainActor.run { self.showGoogleAccountMenu() }
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
@@ -6634,25 +6642,30 @@ private extension ViewController {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func performGoogleSignOut() {
|
|
private func performGoogleSignOut() {
|
|
|
- do {
|
|
|
|
|
- try googleOAuth.signOut()
|
|
|
|
|
- applyGoogleProfile(nil)
|
|
|
|
|
- updateGoogleAuthButtonTitle()
|
|
|
|
|
- pageCache[.joinMeetings] = nil
|
|
|
|
|
- pageCache[.photo] = nil
|
|
|
|
|
- pageCache[.video] = nil
|
|
|
|
|
- pageCache[.settings] = nil
|
|
|
|
|
- showSidebarPage(selectedSidebarPage)
|
|
|
|
|
- Task { [weak self] in
|
|
|
|
|
- await self?.loadSchedule()
|
|
|
|
|
|
|
+ Task { [weak self] in
|
|
|
|
|
+ guard let self else { return }
|
|
|
|
|
+ do {
|
|
|
|
|
+ try await self.googleOAuth.signOutAndRevoke()
|
|
|
|
|
+ await MainActor.run {
|
|
|
|
|
+ self.applyGoogleProfile(nil)
|
|
|
|
|
+ self.updateGoogleAuthButtonTitle()
|
|
|
|
|
+ self.pageCache[.joinMeetings] = nil
|
|
|
|
|
+ self.pageCache[.photo] = nil
|
|
|
|
|
+ self.pageCache[.video] = nil
|
|
|
|
|
+ self.pageCache[.settings] = nil
|
|
|
|
|
+ self.showSidebarPage(self.selectedSidebarPage)
|
|
|
|
|
+ }
|
|
|
|
|
+ await self.loadSchedule()
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ await MainActor.run {
|
|
|
|
|
+ self.showSimpleError("Couldn’t logout Google account.", error: error)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- } catch {
|
|
|
|
|
- showSimpleError("Couldn’t logout Google account.", error: error)
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func updateGoogleAuthButtonTitle() {
|
|
private func updateGoogleAuthButtonTitle() {
|
|
|
- let signedIn = (googleOAuth.loadTokens() != nil)
|
|
|
|
|
|
|
+ let signedIn = hasGoogleSessionAvailable()
|
|
|
guard let button = scheduleGoogleAuthButton else { return }
|
|
guard let button = scheduleGoogleAuthButton else { return }
|
|
|
|
|
|
|
|
let profileName = scheduleCurrentProfile?.name ?? "Google account"
|
|
let profileName = scheduleCurrentProfile?.name ?? "Google account"
|
|
@@ -6786,7 +6799,7 @@ private extension ViewController {
|
|
|
|
|
|
|
|
private func applyGoogleAuthButtonSurface() {
|
|
private func applyGoogleAuthButtonSurface() {
|
|
|
guard let button = scheduleGoogleAuthButton else { return }
|
|
guard let button = scheduleGoogleAuthButton else { return }
|
|
|
- let signedIn = (googleOAuth.loadTokens() != nil)
|
|
|
|
|
|
|
+ let signedIn = hasGoogleSessionAvailable()
|
|
|
let isDark = darkModeEnabled
|
|
let isDark = darkModeEnabled
|
|
|
if signedIn {
|
|
if signedIn {
|
|
|
button.layer?.backgroundColor = NSColor.clear.cgColor
|
|
button.layer?.backgroundColor = NSColor.clear.cgColor
|
|
@@ -6812,43 +6825,9 @@ private extension ViewController {
|
|
|
|
|
|
|
|
@MainActor
|
|
@MainActor
|
|
|
func ensureGoogleClientIdConfigured(presentingWindow: NSWindow?) async throws {
|
|
func ensureGoogleClientIdConfigured(presentingWindow: NSWindow?) async throws {
|
|
|
- if googleOAuth.configuredClientId() != nil, googleOAuth.configuredClientSecret() != nil { return }
|
|
|
|
|
-
|
|
|
|
|
- let alert = NSAlert()
|
|
|
|
|
- alert.messageText = "Enter Google OAuth credentials"
|
|
|
|
|
- alert.informativeText = "Paste the OAuth Client ID and Client Secret from your downloaded Desktop OAuth JSON."
|
|
|
|
|
-
|
|
|
|
|
- let accessory = NSStackView()
|
|
|
|
|
- accessory.orientation = .vertical
|
|
|
|
|
- accessory.spacing = 8
|
|
|
|
|
- accessory.alignment = .leading
|
|
|
|
|
-
|
|
|
|
|
- let idField = NSTextField(string: googleOAuth.configuredClientId() ?? "")
|
|
|
|
|
- idField.placeholderString = "Client ID (....apps.googleusercontent.com)"
|
|
|
|
|
- idField.frame = NSRect(x: 0, y: 0, width: 460, height: 24)
|
|
|
|
|
-
|
|
|
|
|
- let secretField = NSSecureTextField(string: googleOAuth.configuredClientSecret() ?? "")
|
|
|
|
|
- secretField.placeholderString = "Client Secret (GOCSPX-...)"
|
|
|
|
|
- secretField.frame = NSRect(x: 0, y: 0, width: 460, height: 24)
|
|
|
|
|
-
|
|
|
|
|
- accessory.addArrangedSubview(idField)
|
|
|
|
|
- accessory.addArrangedSubview(secretField)
|
|
|
|
|
- alert.accessoryView = accessory
|
|
|
|
|
-
|
|
|
|
|
- alert.addButton(withTitle: "Save")
|
|
|
|
|
- alert.addButton(withTitle: "Cancel")
|
|
|
|
|
-
|
|
|
|
|
- // Keep this synchronous to avoid additional sheet state handling.
|
|
|
|
|
- let response = alert.runModal()
|
|
|
|
|
- if response != .alertFirstButtonReturn { throw GoogleOAuthError.missingClientId }
|
|
|
|
|
-
|
|
|
|
|
- let idValue = idField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
- let secretValue = secretField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
|
- if idValue.isEmpty { throw GoogleOAuthError.missingClientId }
|
|
|
|
|
- if secretValue.isEmpty { throw GoogleOAuthError.missingClientSecret }
|
|
|
|
|
-
|
|
|
|
|
- googleOAuth.setClientIdForTesting(idValue)
|
|
|
|
|
- googleOAuth.setClientSecretForTesting(secretValue)
|
|
|
|
|
|
|
+ _ = presentingWindow
|
|
|
|
|
+ guard googleOAuth.configuredClientId() != nil else { throw GoogleOAuthError.missingClientId }
|
|
|
|
|
+ guard googleOAuth.configuredClientSecret() != nil else { throw GoogleOAuthError.missingClientSecret }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func showSimpleError(_ title: String, error: Error) {
|
|
func showSimpleError(_ title: String, error: Error) {
|