|
|
@@ -1906,7 +1906,7 @@ private extension ViewController {
|
|
1906
|
1906
|
row.translatesAutoresizingMaskIntoConstraints = false
|
|
1907
|
1907
|
styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
1908
|
1908
|
|
|
1909
|
|
- let signedIn = googleOAuth.loadTokens() != nil
|
|
|
1909
|
+ let signedIn = hasGoogleSessionAvailable()
|
|
1910
|
1910
|
let titleText = signedIn ? (scheduleCurrentProfile?.name ?? "Google account connected") : "Google account not connected"
|
|
1911
|
1911
|
let subtitleText = signedIn ? (scheduleCurrentProfile?.email ?? "Signed in") : "Sign in to sync your meetings and calendar."
|
|
1912
|
1912
|
|
|
|
@@ -1978,7 +1978,7 @@ private extension ViewController {
|
|
1978
|
1978
|
}
|
|
1979
|
1979
|
|
|
1980
|
1980
|
@objc private func settingsGoogleActionButtonClicked(_ sender: NSButton) {
|
|
1981
|
|
- if googleOAuth.loadTokens() == nil {
|
|
|
1981
|
+ if hasGoogleSessionAvailable() == false {
|
|
1982
|
1982
|
scheduleConnectClicked()
|
|
1983
|
1983
|
} else {
|
|
1984
|
1984
|
performGoogleSignOut()
|
|
|
@@ -2297,7 +2297,7 @@ private extension ViewController {
|
|
2297
|
2297
|
])
|
|
2298
|
2298
|
mainContentHost = host
|
|
2299
|
2299
|
|
|
2300
|
|
- if googleOAuth.loadTokens() != nil, let profile = scheduleCurrentProfile {
|
|
|
2300
|
+ if hasGoogleSessionAvailable(), let profile = scheduleCurrentProfile {
|
|
2301
|
2301
|
applyGoogleProfile(profile)
|
|
2302
|
2302
|
}
|
|
2303
|
2303
|
|
|
|
@@ -3612,7 +3612,7 @@ private extension ViewController {
|
|
3612
|
3612
|
titleRowSpacer.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
3613
|
3613
|
|
|
3614
|
3614
|
titleRow.addArrangedSubview(titleLabel)
|
|
3615
|
|
- if googleOAuth.loadTokens() != nil && storeKitCoordinator.hasPremiumAccess {
|
|
|
3615
|
+ if hasGoogleSessionAvailable() && storeKitCoordinator.hasPremiumAccess {
|
|
3616
|
3616
|
titleRow.addArrangedSubview(makeSchedulePageAddButton())
|
|
3617
|
3617
|
titleRow.setCustomSpacing(12, after: titleLabel)
|
|
3618
|
3618
|
}
|
|
|
@@ -5031,8 +5031,16 @@ private final class SettingsMenuViewController: NSViewController {
|
|
5031
|
5031
|
}
|
|
5032
|
5032
|
|
|
5033
|
5033
|
private extension ViewController {
|
|
|
5034
|
+ private func hasGoogleSessionAvailable() -> Bool {
|
|
|
5035
|
+ guard let tokens = googleOAuth.loadTokens() else { return false }
|
|
|
5036
|
+ if tokens.expiresAt.timeIntervalSinceNow > 60 {
|
|
|
5037
|
+ return true
|
|
|
5038
|
+ }
|
|
|
5039
|
+ return (tokens.refreshToken?.isEmpty == false)
|
|
|
5040
|
+ }
|
|
|
5041
|
+
|
|
5034
|
5042
|
private func requireGoogleLoginForCalendarScheduling() -> Bool {
|
|
5035
|
|
- guard googleOAuth.loadTokens() != nil else {
|
|
|
5043
|
+ guard hasGoogleSessionAvailable() else {
|
|
5036
|
5044
|
showSimpleAlert(
|
|
5037
|
5045
|
title: "Connect Google",
|
|
5038
|
5046
|
message: "Sign in with Google first to schedule a meeting from Calendar."
|
|
|
@@ -5492,7 +5500,7 @@ private extension ViewController {
|
|
5492
|
5500
|
f.dateFormat = "EEE, d MMM"
|
|
5493
|
5501
|
|
|
5494
|
5502
|
if meetings.isEmpty {
|
|
5495
|
|
- calendarPageDaySummaryLabel?.stringValue = googleOAuth.loadTokens() == nil
|
|
|
5503
|
+ calendarPageDaySummaryLabel?.stringValue = hasGoogleSessionAvailable() == false
|
|
5496
|
5504
|
? "Connect Google to see meetings"
|
|
5497
|
5505
|
: "No meetings on \(f.string(from: selectedDay))"
|
|
5498
|
5506
|
} else if meetings.count == 1 {
|
|
|
@@ -6128,11 +6136,11 @@ private extension ViewController {
|
|
6128
|
6136
|
}
|
|
6129
|
6137
|
|
|
6130
|
6138
|
private func scheduleInitialHeadingText() -> String {
|
|
6131
|
|
- googleOAuth.loadTokens() == nil ? "Connect Google to see meetings" : "Loading…"
|
|
|
6139
|
+ hasGoogleSessionAvailable() ? "Loading…" : "Connect Google to see meetings"
|
|
6132
|
6140
|
}
|
|
6133
|
6141
|
|
|
6134
|
6142
|
private func schedulePageInitialHeadingText() -> String {
|
|
6135
|
|
- googleOAuth.loadTokens() == nil ? "Connect Google to see meetings" : "Loading schedule…"
|
|
|
6143
|
+ hasGoogleSessionAvailable() ? "Loading schedule…" : "Connect Google to see meetings"
|
|
6136
|
6144
|
}
|
|
6137
|
6145
|
|
|
6138
|
6146
|
@objc func scheduleFilterDropdownChanged(_ sender: NSPopUpButton) {
|
|
|
@@ -6239,7 +6247,7 @@ private extension ViewController {
|
|
6239
|
6247
|
|
|
6240
|
6248
|
private func scheduleHeadingText(for meetings: [ScheduledMeeting]) -> String {
|
|
6241
|
6249
|
guard let first = meetings.first else {
|
|
6242
|
|
- return googleOAuth.loadTokens() == nil ? "Connect Google to see meetings" : "No upcoming meetings"
|
|
|
6250
|
+ return hasGoogleSessionAvailable() ? "No upcoming meetings" : "Connect Google to see meetings"
|
|
6243
|
6251
|
}
|
|
6244
|
6252
|
|
|
6245
|
6253
|
let day = Calendar.current.startOfDay(for: first.startDate)
|
|
|
@@ -6276,7 +6284,7 @@ private extension ViewController {
|
|
6276
|
6284
|
empty.heightAnchor.constraint(equalToConstant: 150).isActive = true
|
|
6277
|
6285
|
styleSurface(empty, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
6278
|
6286
|
|
|
6279
|
|
- let label = textLabel(googleOAuth.loadTokens() == nil ? "Connect to load schedule" : "No meetings", font: typography.cardSubtitle, color: palette.textSecondary)
|
|
|
6287
|
+ let label = textLabel(hasGoogleSessionAvailable() ? "No meetings" : "Connect to load schedule", font: typography.cardSubtitle, color: palette.textSecondary)
|
|
6280
|
6288
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
6281
|
6289
|
empty.addSubview(label)
|
|
6282
|
6290
|
NSLayoutConstraint.activate([
|
|
|
@@ -6413,7 +6421,7 @@ private extension ViewController {
|
|
6413
|
6421
|
empty.translatesAutoresizingMaskIntoConstraints = false
|
|
6414
|
6422
|
empty.heightAnchor.constraint(equalToConstant: 140).isActive = true
|
|
6415
|
6423
|
styleSurface(empty, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
6416
|
|
- let label = textLabel(googleOAuth.loadTokens() == nil ? "Connect to load schedule" : "No meetings for selected filters", font: typography.cardSubtitle, color: palette.textSecondary)
|
|
|
6424
|
+ let label = textLabel(hasGoogleSessionAvailable() ? "No meetings for selected filters" : "Connect to load schedule", font: typography.cardSubtitle, color: palette.textSecondary)
|
|
6417
|
6425
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
6418
|
6426
|
empty.addSubview(label)
|
|
6419
|
6427
|
NSLayoutConstraint.activate([
|
|
|
@@ -6480,7 +6488,7 @@ private extension ViewController {
|
|
6480
|
6488
|
|
|
6481
|
6489
|
private func loadSchedule() async {
|
|
6482
|
6490
|
do {
|
|
6483
|
|
- if googleOAuth.loadTokens() == nil {
|
|
|
6491
|
+ if hasGoogleSessionAvailable() == false {
|
|
6484
|
6492
|
await MainActor.run {
|
|
6485
|
6493
|
updateGoogleAuthButtonTitle()
|
|
6486
|
6494
|
applyGoogleProfile(nil)
|
|
|
@@ -6523,7 +6531,7 @@ private extension ViewController {
|
|
6523
|
6531
|
} catch {
|
|
6524
|
6532
|
await MainActor.run {
|
|
6525
|
6533
|
updateGoogleAuthButtonTitle()
|
|
6526
|
|
- if googleOAuth.loadTokens() == nil {
|
|
|
6534
|
+ if hasGoogleSessionAvailable() == false {
|
|
6527
|
6535
|
applyGoogleProfile(nil)
|
|
6528
|
6536
|
}
|
|
6529
|
6537
|
scheduleDateHeadingLabel?.stringValue = "Couldn’t load schedule"
|
|
|
@@ -6576,7 +6584,7 @@ private extension ViewController {
|
|
6576
|
6584
|
Task { [weak self] in
|
|
6577
|
6585
|
guard let self else { return }
|
|
6578
|
6586
|
do {
|
|
6579
|
|
- if self.googleOAuth.loadTokens() != nil {
|
|
|
6587
|
+ if self.hasGoogleSessionAvailable() {
|
|
6580
|
6588
|
await MainActor.run { self.showGoogleAccountMenu() }
|
|
6581
|
6589
|
return
|
|
6582
|
6590
|
}
|
|
|
@@ -6634,25 +6642,30 @@ private extension ViewController {
|
|
6634
|
6642
|
}
|
|
6635
|
6643
|
|
|
6636
|
6644
|
private func performGoogleSignOut() {
|
|
6637
|
|
- do {
|
|
6638
|
|
- try googleOAuth.signOut()
|
|
6639
|
|
- applyGoogleProfile(nil)
|
|
6640
|
|
- updateGoogleAuthButtonTitle()
|
|
6641
|
|
- pageCache[.joinMeetings] = nil
|
|
6642
|
|
- pageCache[.photo] = nil
|
|
6643
|
|
- pageCache[.video] = nil
|
|
6644
|
|
- pageCache[.settings] = nil
|
|
6645
|
|
- showSidebarPage(selectedSidebarPage)
|
|
6646
|
|
- Task { [weak self] in
|
|
6647
|
|
- await self?.loadSchedule()
|
|
|
6645
|
+ Task { [weak self] in
|
|
|
6646
|
+ guard let self else { return }
|
|
|
6647
|
+ do {
|
|
|
6648
|
+ try await self.googleOAuth.signOutAndRevoke()
|
|
|
6649
|
+ await MainActor.run {
|
|
|
6650
|
+ self.applyGoogleProfile(nil)
|
|
|
6651
|
+ self.updateGoogleAuthButtonTitle()
|
|
|
6652
|
+ self.pageCache[.joinMeetings] = nil
|
|
|
6653
|
+ self.pageCache[.photo] = nil
|
|
|
6654
|
+ self.pageCache[.video] = nil
|
|
|
6655
|
+ self.pageCache[.settings] = nil
|
|
|
6656
|
+ self.showSidebarPage(self.selectedSidebarPage)
|
|
|
6657
|
+ }
|
|
|
6658
|
+ await self.loadSchedule()
|
|
|
6659
|
+ } catch {
|
|
|
6660
|
+ await MainActor.run {
|
|
|
6661
|
+ self.showSimpleError("Couldn’t logout Google account.", error: error)
|
|
|
6662
|
+ }
|
|
6648
|
6663
|
}
|
|
6649
|
|
- } catch {
|
|
6650
|
|
- showSimpleError("Couldn’t logout Google account.", error: error)
|
|
6651
|
6664
|
}
|
|
6652
|
6665
|
}
|
|
6653
|
6666
|
|
|
6654
|
6667
|
private func updateGoogleAuthButtonTitle() {
|
|
6655
|
|
- let signedIn = (googleOAuth.loadTokens() != nil)
|
|
|
6668
|
+ let signedIn = hasGoogleSessionAvailable()
|
|
6656
|
6669
|
guard let button = scheduleGoogleAuthButton else { return }
|
|
6657
|
6670
|
|
|
6658
|
6671
|
let profileName = scheduleCurrentProfile?.name ?? "Google account"
|
|
|
@@ -6786,7 +6799,7 @@ private extension ViewController {
|
|
6786
|
6799
|
|
|
6787
|
6800
|
private func applyGoogleAuthButtonSurface() {
|
|
6788
|
6801
|
guard let button = scheduleGoogleAuthButton else { return }
|
|
6789
|
|
- let signedIn = (googleOAuth.loadTokens() != nil)
|
|
|
6802
|
+ let signedIn = hasGoogleSessionAvailable()
|
|
6790
|
6803
|
let isDark = darkModeEnabled
|
|
6791
|
6804
|
if signedIn {
|
|
6792
|
6805
|
button.layer?.backgroundColor = NSColor.clear.cgColor
|
|
|
@@ -6812,43 +6825,9 @@ private extension ViewController {
|
|
6812
|
6825
|
|
|
6813
|
6826
|
@MainActor
|
|
6814
|
6827
|
func ensureGoogleClientIdConfigured(presentingWindow: NSWindow?) async throws {
|
|
6815
|
|
- if googleOAuth.configuredClientId() != nil, googleOAuth.configuredClientSecret() != nil { return }
|
|
6816
|
|
-
|
|
6817
|
|
- let alert = NSAlert()
|
|
6818
|
|
- alert.messageText = "Enter Google OAuth credentials"
|
|
6819
|
|
- alert.informativeText = "Paste the OAuth Client ID and Client Secret from your downloaded Desktop OAuth JSON."
|
|
6820
|
|
-
|
|
6821
|
|
- let accessory = NSStackView()
|
|
6822
|
|
- accessory.orientation = .vertical
|
|
6823
|
|
- accessory.spacing = 8
|
|
6824
|
|
- accessory.alignment = .leading
|
|
6825
|
|
-
|
|
6826
|
|
- let idField = NSTextField(string: googleOAuth.configuredClientId() ?? "")
|
|
6827
|
|
- idField.placeholderString = "Client ID (....apps.googleusercontent.com)"
|
|
6828
|
|
- idField.frame = NSRect(x: 0, y: 0, width: 460, height: 24)
|
|
6829
|
|
-
|
|
6830
|
|
- let secretField = NSSecureTextField(string: googleOAuth.configuredClientSecret() ?? "")
|
|
6831
|
|
- secretField.placeholderString = "Client Secret (GOCSPX-...)"
|
|
6832
|
|
- secretField.frame = NSRect(x: 0, y: 0, width: 460, height: 24)
|
|
6833
|
|
-
|
|
6834
|
|
- accessory.addArrangedSubview(idField)
|
|
6835
|
|
- accessory.addArrangedSubview(secretField)
|
|
6836
|
|
- alert.accessoryView = accessory
|
|
6837
|
|
-
|
|
6838
|
|
- alert.addButton(withTitle: "Save")
|
|
6839
|
|
- alert.addButton(withTitle: "Cancel")
|
|
6840
|
|
-
|
|
6841
|
|
- // Keep this synchronous to avoid additional sheet state handling.
|
|
6842
|
|
- let response = alert.runModal()
|
|
6843
|
|
- if response != .alertFirstButtonReturn { throw GoogleOAuthError.missingClientId }
|
|
6844
|
|
-
|
|
6845
|
|
- let idValue = idField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
6846
|
|
- let secretValue = secretField.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
6847
|
|
- if idValue.isEmpty { throw GoogleOAuthError.missingClientId }
|
|
6848
|
|
- if secretValue.isEmpty { throw GoogleOAuthError.missingClientSecret }
|
|
6849
|
|
-
|
|
6850
|
|
- googleOAuth.setClientIdForTesting(idValue)
|
|
6851
|
|
- googleOAuth.setClientSecretForTesting(secretValue)
|
|
|
6828
|
+ _ = presentingWindow
|
|
|
6829
|
+ guard googleOAuth.configuredClientId() != nil else { throw GoogleOAuthError.missingClientId }
|
|
|
6830
|
+ guard googleOAuth.configuredClientSecret() != nil else { throw GoogleOAuthError.missingClientSecret }
|
|
6852
|
6831
|
}
|
|
6853
|
6832
|
|
|
6854
|
6833
|
func showSimpleError(_ title: String, error: Error) {
|