|
@@ -522,12 +522,6 @@ private extension ViewController {
|
|
|
private func activateSidebarItem(_ view: NSView) {
|
|
private func activateSidebarItem(_ view: NSView) {
|
|
|
guard let page = sidebarPageByView[ObjectIdentifier(view)],
|
|
guard let page = sidebarPageByView[ObjectIdentifier(view)],
|
|
|
page != selectedSidebarPage || page == .settings else { return }
|
|
page != selectedSidebarPage || page == .settings else { return }
|
|
|
-
|
|
|
|
|
- if page == .settings {
|
|
|
|
|
- showSettingsPopover()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
showSidebarPage(page)
|
|
showSidebarPage(page)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -554,12 +548,6 @@ private extension ViewController {
|
|
|
@objc private func sidebarButtonClicked(_ sender: NSButton) {
|
|
@objc private func sidebarButtonClicked(_ sender: NSButton) {
|
|
|
guard let page = SidebarPage(rawValue: sender.tag),
|
|
guard let page = SidebarPage(rawValue: sender.tag),
|
|
|
page != selectedSidebarPage || page == .settings else { return }
|
|
page != selectedSidebarPage || page == .settings else { return }
|
|
|
-
|
|
|
|
|
- if page == .settings {
|
|
|
|
|
- showSettingsPopover()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
showSidebarPage(page)
|
|
showSidebarPage(page)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -831,8 +819,6 @@ private extension ViewController {
|
|
|
NSApp.appearance = NSAppearance(named: enabled ? .darkAqua : .aqua)
|
|
NSApp.appearance = NSAppearance(named: enabled ? .darkAqua : .aqua)
|
|
|
view.appearance = NSAppearance(named: enabled ? .darkAqua : .aqua)
|
|
view.appearance = NSAppearance(named: enabled ? .darkAqua : .aqua)
|
|
|
palette = Palette(isDarkMode: enabled)
|
|
palette = Palette(isDarkMode: enabled)
|
|
|
- settingsPopover?.performClose(nil)
|
|
|
|
|
- settingsPopover = nil
|
|
|
|
|
reloadTheme()
|
|
reloadTheme()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -870,8 +856,6 @@ private extension ViewController {
|
|
|
private func handleSettingsAction(_ action: SettingsAction) {
|
|
private func handleSettingsAction(_ action: SettingsAction) {
|
|
|
switch action {
|
|
switch action {
|
|
|
case .restore:
|
|
case .restore:
|
|
|
- settingsPopover?.performClose(nil)
|
|
|
|
|
- settingsPopover = nil
|
|
|
|
|
Task { [weak self] in
|
|
Task { [weak self] in
|
|
|
guard let self else { return }
|
|
guard let self else { return }
|
|
|
let message = await self.storeKitCoordinator.restorePurchases()
|
|
let message = await self.storeKitCoordinator.restorePurchases()
|
|
@@ -879,30 +863,20 @@ private extension ViewController {
|
|
|
self.showSimpleAlert(title: "Restore Purchases", message: message)
|
|
self.showSimpleAlert(title: "Restore Purchases", message: message)
|
|
|
}
|
|
}
|
|
|
case .rateUs:
|
|
case .rateUs:
|
|
|
- settingsPopover?.performClose(nil)
|
|
|
|
|
- settingsPopover = nil
|
|
|
|
|
openRateUsDestination()
|
|
openRateUsDestination()
|
|
|
case .support:
|
|
case .support:
|
|
|
- settingsPopover?.performClose(nil)
|
|
|
|
|
- settingsPopover = nil
|
|
|
|
|
if let supportURL = Bundle.main.object(forInfoDictionaryKey: "SupportURL") as? String,
|
|
if let supportURL = Bundle.main.object(forInfoDictionaryKey: "SupportURL") as? String,
|
|
|
let url = URL(string: supportURL) {
|
|
let url = URL(string: supportURL) {
|
|
|
openInAppBrowser(with: url, policy: inAppBrowserDefaultPolicy)
|
|
openInAppBrowser(with: url, policy: inAppBrowserDefaultPolicy)
|
|
|
}
|
|
}
|
|
|
case .moreApps:
|
|
case .moreApps:
|
|
|
- settingsPopover?.performClose(nil)
|
|
|
|
|
- settingsPopover = nil
|
|
|
|
|
if let moreAppsURL = Bundle.main.object(forInfoDictionaryKey: "MoreAppsURL") as? String,
|
|
if let moreAppsURL = Bundle.main.object(forInfoDictionaryKey: "MoreAppsURL") as? String,
|
|
|
let url = URL(string: moreAppsURL) {
|
|
let url = URL(string: moreAppsURL) {
|
|
|
openInAppBrowser(with: url, policy: inAppBrowserDefaultPolicy)
|
|
openInAppBrowser(with: url, policy: inAppBrowserDefaultPolicy)
|
|
|
}
|
|
}
|
|
|
case .shareApp:
|
|
case .shareApp:
|
|
|
- settingsPopover?.performClose(nil)
|
|
|
|
|
- settingsPopover = nil
|
|
|
|
|
shareAppFromSettingsMenu()
|
|
shareAppFromSettingsMenu()
|
|
|
case .upgrade:
|
|
case .upgrade:
|
|
|
- settingsPopover?.performClose(nil)
|
|
|
|
|
- settingsPopover = nil
|
|
|
|
|
showPaywall(upgradeFlow: true, preferredPlan: .lifetime)
|
|
showPaywall(upgradeFlow: true, preferredPlan: .lifetime)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1510,7 +1484,7 @@ private extension ViewController {
|
|
|
case .video:
|
|
case .video:
|
|
|
built = makeCalendarPageContent()
|
|
built = makeCalendarPageContent()
|
|
|
case .settings:
|
|
case .settings:
|
|
|
- built = makePlaceholderPage(title: "Settings", subtitle: "Preferences and account options.")
|
|
|
|
|
|
|
+ built = makeSettingsPageContent()
|
|
|
}
|
|
}
|
|
|
pageCache[page] = built
|
|
pageCache[page] = built
|
|
|
return built
|
|
return built
|
|
@@ -1534,6 +1508,187 @@ private extension ViewController {
|
|
|
return panel
|
|
return panel
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private func makeSettingsPageContent() -> NSView {
|
|
|
|
|
+ let panel = NSView()
|
|
|
|
|
+ panel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+
|
|
|
|
|
+ let card = roundedContainer(cornerRadius: 16, color: palette.sectionCard)
|
|
|
|
|
+ card.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ styleSurface(card, borderColor: palette.inputBorder, borderWidth: 1, shadow: true)
|
|
|
|
|
+ panel.addSubview(card)
|
|
|
|
|
+
|
|
|
|
|
+ let stack = NSStackView()
|
|
|
|
|
+ stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ stack.orientation = .vertical
|
|
|
|
|
+ stack.spacing = 18
|
|
|
|
|
+ stack.alignment = .leading
|
|
|
|
|
+ card.addSubview(stack)
|
|
|
|
|
+
|
|
|
|
|
+ let pageTitle = textLabel("Settings", font: typography.pageTitle, color: palette.textPrimary)
|
|
|
|
|
+ let pageSubtitle = textLabel("Manage appearance, account, and app options.", font: typography.fieldLabel, color: palette.textSecondary)
|
|
|
|
|
+ stack.addArrangedSubview(pageTitle)
|
|
|
|
|
+ stack.addArrangedSubview(pageSubtitle)
|
|
|
|
|
+ stack.setCustomSpacing(24, after: pageSubtitle)
|
|
|
|
|
+
|
|
|
|
|
+ let appearanceTitle = textLabel("Appearance", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
|
|
|
+ stack.addArrangedSubview(appearanceTitle)
|
|
|
|
|
+ let darkModeRow = makeSettingsDarkModeRow()
|
|
|
|
|
+ stack.addArrangedSubview(darkModeRow)
|
|
|
|
|
+ darkModeRow.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
|
|
+ stack.setCustomSpacing(24, after: darkModeRow)
|
|
|
|
|
+
|
|
|
|
|
+ let accountTitle = textLabel("Account", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
|
|
|
+ stack.addArrangedSubview(accountTitle)
|
|
|
|
|
+ let googleAccountRow = makeSettingsGoogleAccountRow()
|
|
|
|
|
+ stack.addArrangedSubview(googleAccountRow)
|
|
|
|
|
+ googleAccountRow.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
|
|
+ stack.setCustomSpacing(24, after: googleAccountRow)
|
|
|
|
|
+
|
|
|
|
|
+ let appTitle = textLabel("App", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
|
|
|
+ stack.addArrangedSubview(appTitle)
|
|
|
|
|
+
|
|
|
|
|
+ if shouldShowRateUsInSettings {
|
|
|
|
|
+ let rateButton = makeSettingsActionButton(icon: "★", title: "Rate Us", action: .rateUs)
|
|
|
|
|
+ stack.addArrangedSubview(rateButton)
|
|
|
|
|
+ rateButton.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
|
|
+ }
|
|
|
|
|
+ let supportButton = makeSettingsActionButton(icon: "💬", title: "Support", action: .support)
|
|
|
|
|
+ stack.addArrangedSubview(supportButton)
|
|
|
|
|
+ supportButton.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
|
|
+ let shareButton = makeSettingsActionButton(icon: "⤴︎", title: "Share App", action: .shareApp)
|
|
|
|
|
+ stack.addArrangedSubview(shareButton)
|
|
|
|
|
+ shareButton.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
|
|
+ if storeKitCoordinator.hasPremiumAccess && !storeKitCoordinator.hasLifetimeAccess {
|
|
|
|
|
+ let upgradeButton = makeSettingsActionButton(icon: "⬆︎", title: "Upgrade", action: .upgrade)
|
|
|
|
|
+ stack.addArrangedSubview(upgradeButton)
|
|
|
|
|
+ upgradeButton.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ card.centerXAnchor.constraint(equalTo: panel.centerXAnchor),
|
|
|
|
|
+ card.topAnchor.constraint(equalTo: panel.topAnchor, constant: 36),
|
|
|
|
|
+ card.bottomAnchor.constraint(lessThanOrEqualTo: panel.bottomAnchor, constant: -36),
|
|
|
|
|
+ card.widthAnchor.constraint(lessThanOrEqualToConstant: 620),
|
|
|
|
|
+ card.widthAnchor.constraint(greaterThanOrEqualToConstant: 460),
|
|
|
|
|
+ card.leadingAnchor.constraint(greaterThanOrEqualTo: panel.leadingAnchor, constant: 30),
|
|
|
|
|
+ card.trailingAnchor.constraint(lessThanOrEqualTo: panel.trailingAnchor, constant: -30),
|
|
|
|
|
+
|
|
|
|
|
+ stack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 28),
|
|
|
|
|
+ stack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -28),
|
|
|
|
|
+ stack.topAnchor.constraint(equalTo: card.topAnchor, constant: 24),
|
|
|
|
|
+ stack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -24)
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ return panel
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func makeSettingsDarkModeRow() -> NSView {
|
|
|
|
|
+ let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
|
|
|
|
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ row.heightAnchor.constraint(equalToConstant: 52).isActive = true
|
|
|
|
|
+ styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
+
|
|
|
|
|
+ let icon = textLabel("◐", font: NSFont.systemFont(ofSize: 18, weight: .medium), color: palette.textPrimary)
|
|
|
|
|
+ let title = textLabel("Dark Mode", font: NSFont.systemFont(ofSize: 15, weight: .semibold), color: palette.textPrimary)
|
|
|
|
|
+ let toggle = NSSwitch()
|
|
|
|
|
+ toggle.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ toggle.state = darkModeEnabled ? .on : .off
|
|
|
|
|
+ toggle.target = self
|
|
|
|
|
+ toggle.action = #selector(settingsPageDarkModeToggled(_:))
|
|
|
|
|
+
|
|
|
|
|
+ row.addSubview(icon)
|
|
|
|
|
+ row.addSubview(title)
|
|
|
|
|
+ row.addSubview(toggle)
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ icon.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 14),
|
|
|
|
|
+ icon.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
|
|
|
+ title.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 10),
|
|
|
|
|
+ title.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
|
|
|
+ toggle.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -14),
|
|
|
|
|
+ toggle.centerYAnchor.constraint(equalTo: row.centerYAnchor)
|
|
|
|
|
+ ])
|
|
|
|
|
+ return row
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func makeSettingsGoogleAccountRow() -> NSView {
|
|
|
|
|
+ let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
|
|
|
|
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
+
|
|
|
|
|
+ let signedIn = googleOAuth.loadTokens() != nil
|
|
|
|
|
+ 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 title = textLabel(titleText, font: NSFont.systemFont(ofSize: 15, weight: .semibold), color: palette.textPrimary)
|
|
|
|
|
+ let subtitle = textLabel(subtitleText, font: NSFont.systemFont(ofSize: 13, weight: .regular), color: palette.textSecondary)
|
|
|
|
|
+ subtitle.maximumNumberOfLines = 2
|
|
|
|
|
+ subtitle.lineBreakMode = .byTruncatingTail
|
|
|
|
|
+
|
|
|
|
|
+ let actionButton = NSButton(title: signedIn ? "Sign Out" : "Sign in with Google", target: self, action: #selector(settingsGoogleActionButtonClicked(_:)))
|
|
|
|
|
+ actionButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ actionButton.bezelStyle = .rounded
|
|
|
|
|
+ actionButton.controlSize = .regular
|
|
|
|
|
+
|
|
|
|
|
+ row.addSubview(title)
|
|
|
|
|
+ row.addSubview(subtitle)
|
|
|
|
|
+ row.addSubview(actionButton)
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ row.heightAnchor.constraint(equalToConstant: 78),
|
|
|
|
|
+ title.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 14),
|
|
|
|
|
+ title.topAnchor.constraint(equalTo: row.topAnchor, constant: 12),
|
|
|
|
|
+ subtitle.leadingAnchor.constraint(equalTo: title.leadingAnchor),
|
|
|
|
|
+ subtitle.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 4),
|
|
|
|
|
+ subtitle.trailingAnchor.constraint(lessThanOrEqualTo: actionButton.leadingAnchor, constant: -14),
|
|
|
|
|
+ actionButton.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -14),
|
|
|
|
|
+ actionButton.centerYAnchor.constraint(equalTo: row.centerYAnchor)
|
|
|
|
|
+ ])
|
|
|
|
|
+
|
|
|
|
|
+ return row
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func makeSettingsActionButton(icon: String, title: String, action: SettingsAction) -> NSButton {
|
|
|
|
|
+ let button = HoverButton(title: "", target: self, action: #selector(settingsPageActionButtonClicked(_:)))
|
|
|
|
|
+ button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ button.isBordered = false
|
|
|
|
|
+ button.wantsLayer = true
|
|
|
|
|
+ button.layer?.cornerRadius = 10
|
|
|
|
|
+ button.layer?.backgroundColor = palette.inputBackground.cgColor
|
|
|
|
|
+ styleSurface(button, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
|
|
+ button.heightAnchor.constraint(equalToConstant: 46).isActive = true
|
|
|
|
|
+ button.tag = action.rawValue
|
|
|
|
|
+
|
|
|
|
|
+ let iconLabel = textLabel(icon, font: NSFont.systemFont(ofSize: 17, weight: .medium), color: palette.textPrimary)
|
|
|
|
|
+ let titleLabel = textLabel(title, font: NSFont.systemFont(ofSize: 15, weight: .semibold), color: palette.textPrimary)
|
|
|
|
|
+ button.addSubview(iconLabel)
|
|
|
|
|
+ button.addSubview(titleLabel)
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ iconLabel.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: 14),
|
|
|
|
|
+ iconLabel.centerYAnchor.constraint(equalTo: button.centerYAnchor),
|
|
|
|
|
+ titleLabel.leadingAnchor.constraint(equalTo: iconLabel.trailingAnchor, constant: 10),
|
|
|
|
|
+ titleLabel.centerYAnchor.constraint(equalTo: button.centerYAnchor)
|
|
|
|
|
+ ])
|
|
|
|
|
+ return button
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @objc private func settingsPageDarkModeToggled(_ sender: NSSwitch) {
|
|
|
|
|
+ setDarkMode(sender.state == .on)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @objc private func settingsPageActionButtonClicked(_ sender: NSButton) {
|
|
|
|
|
+ guard let action = SettingsAction(rawValue: sender.tag) else { return }
|
|
|
|
|
+ handleSettingsAction(action)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @objc private func settingsGoogleActionButtonClicked(_ sender: NSButton) {
|
|
|
|
|
+ if googleOAuth.loadTokens() == nil {
|
|
|
|
|
+ scheduleConnectClicked()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ performGoogleSignOut()
|
|
|
|
|
+ pageCache[.settings] = nil
|
|
|
|
|
+ showSidebarPage(.settings)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
func makeBrowseWebContent() -> NSView {
|
|
func makeBrowseWebContent() -> NSView {
|
|
|
let panel = NSView()
|
|
let panel = NSView()
|
|
|
panel.translatesAutoresizingMaskIntoConstraints = false
|
|
panel.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -6051,6 +6206,7 @@ private extension ViewController {
|
|
|
self.pageCache[.joinMeetings] = nil
|
|
self.pageCache[.joinMeetings] = nil
|
|
|
self.pageCache[.photo] = nil
|
|
self.pageCache[.photo] = nil
|
|
|
self.pageCache[.video] = nil
|
|
self.pageCache[.video] = nil
|
|
|
|
|
+ self.pageCache[.settings] = nil
|
|
|
self.showSidebarPage(self.selectedSidebarPage)
|
|
self.showSidebarPage(self.selectedSidebarPage)
|
|
|
}
|
|
}
|
|
|
} catch {
|
|
} catch {
|
|
@@ -6097,8 +6253,11 @@ private extension ViewController {
|
|
|
do {
|
|
do {
|
|
|
try googleOAuth.signOut()
|
|
try googleOAuth.signOut()
|
|
|
pageCache[.photo] = nil
|
|
pageCache[.photo] = nil
|
|
|
|
|
+ pageCache[.settings] = nil
|
|
|
if selectedSidebarPage == .photo {
|
|
if selectedSidebarPage == .photo {
|
|
|
showSidebarPage(.photo)
|
|
showSidebarPage(.photo)
|
|
|
|
|
+ } else if selectedSidebarPage == .settings {
|
|
|
|
|
+ showSidebarPage(.settings)
|
|
|
} else {
|
|
} else {
|
|
|
Task { [weak self] in
|
|
Task { [weak self] in
|
|
|
await self?.loadSchedule()
|
|
await self?.loadSchedule()
|