|
|
@@ -522,12 +522,6 @@ private extension ViewController {
|
|
522
|
522
|
private func activateSidebarItem(_ view: NSView) {
|
|
523
|
523
|
guard let page = sidebarPageByView[ObjectIdentifier(view)],
|
|
524
|
524
|
page != selectedSidebarPage || page == .settings else { return }
|
|
525
|
|
-
|
|
526
|
|
- if page == .settings {
|
|
527
|
|
- showSettingsPopover()
|
|
528
|
|
- return
|
|
529
|
|
- }
|
|
530
|
|
-
|
|
531
|
525
|
showSidebarPage(page)
|
|
532
|
526
|
}
|
|
533
|
527
|
|
|
|
@@ -554,12 +548,6 @@ private extension ViewController {
|
|
554
|
548
|
@objc private func sidebarButtonClicked(_ sender: NSButton) {
|
|
555
|
549
|
guard let page = SidebarPage(rawValue: sender.tag),
|
|
556
|
550
|
page != selectedSidebarPage || page == .settings else { return }
|
|
557
|
|
-
|
|
558
|
|
- if page == .settings {
|
|
559
|
|
- showSettingsPopover()
|
|
560
|
|
- return
|
|
561
|
|
- }
|
|
562
|
|
-
|
|
563
|
551
|
showSidebarPage(page)
|
|
564
|
552
|
}
|
|
565
|
553
|
|
|
|
@@ -831,8 +819,6 @@ private extension ViewController {
|
|
831
|
819
|
NSApp.appearance = NSAppearance(named: enabled ? .darkAqua : .aqua)
|
|
832
|
820
|
view.appearance = NSAppearance(named: enabled ? .darkAqua : .aqua)
|
|
833
|
821
|
palette = Palette(isDarkMode: enabled)
|
|
834
|
|
- settingsPopover?.performClose(nil)
|
|
835
|
|
- settingsPopover = nil
|
|
836
|
822
|
reloadTheme()
|
|
837
|
823
|
}
|
|
838
|
824
|
|
|
|
@@ -870,8 +856,6 @@ private extension ViewController {
|
|
870
|
856
|
private func handleSettingsAction(_ action: SettingsAction) {
|
|
871
|
857
|
switch action {
|
|
872
|
858
|
case .restore:
|
|
873
|
|
- settingsPopover?.performClose(nil)
|
|
874
|
|
- settingsPopover = nil
|
|
875
|
859
|
Task { [weak self] in
|
|
876
|
860
|
guard let self else { return }
|
|
877
|
861
|
let message = await self.storeKitCoordinator.restorePurchases()
|
|
|
@@ -879,30 +863,20 @@ private extension ViewController {
|
|
879
|
863
|
self.showSimpleAlert(title: "Restore Purchases", message: message)
|
|
880
|
864
|
}
|
|
881
|
865
|
case .rateUs:
|
|
882
|
|
- settingsPopover?.performClose(nil)
|
|
883
|
|
- settingsPopover = nil
|
|
884
|
866
|
openRateUsDestination()
|
|
885
|
867
|
case .support:
|
|
886
|
|
- settingsPopover?.performClose(nil)
|
|
887
|
|
- settingsPopover = nil
|
|
888
|
868
|
if let supportURL = Bundle.main.object(forInfoDictionaryKey: "SupportURL") as? String,
|
|
889
|
869
|
let url = URL(string: supportURL) {
|
|
890
|
870
|
openInAppBrowser(with: url, policy: inAppBrowserDefaultPolicy)
|
|
891
|
871
|
}
|
|
892
|
872
|
case .moreApps:
|
|
893
|
|
- settingsPopover?.performClose(nil)
|
|
894
|
|
- settingsPopover = nil
|
|
895
|
873
|
if let moreAppsURL = Bundle.main.object(forInfoDictionaryKey: "MoreAppsURL") as? String,
|
|
896
|
874
|
let url = URL(string: moreAppsURL) {
|
|
897
|
875
|
openInAppBrowser(with: url, policy: inAppBrowserDefaultPolicy)
|
|
898
|
876
|
}
|
|
899
|
877
|
case .shareApp:
|
|
900
|
|
- settingsPopover?.performClose(nil)
|
|
901
|
|
- settingsPopover = nil
|
|
902
|
878
|
shareAppFromSettingsMenu()
|
|
903
|
879
|
case .upgrade:
|
|
904
|
|
- settingsPopover?.performClose(nil)
|
|
905
|
|
- settingsPopover = nil
|
|
906
|
880
|
showPaywall(upgradeFlow: true, preferredPlan: .lifetime)
|
|
907
|
881
|
}
|
|
908
|
882
|
}
|
|
|
@@ -1510,7 +1484,7 @@ private extension ViewController {
|
|
1510
|
1484
|
case .video:
|
|
1511
|
1485
|
built = makeCalendarPageContent()
|
|
1512
|
1486
|
case .settings:
|
|
1513
|
|
- built = makePlaceholderPage(title: "Settings", subtitle: "Preferences and account options.")
|
|
|
1487
|
+ built = makeSettingsPageContent()
|
|
1514
|
1488
|
}
|
|
1515
|
1489
|
pageCache[page] = built
|
|
1516
|
1490
|
return built
|
|
|
@@ -1534,6 +1508,187 @@ private extension ViewController {
|
|
1534
|
1508
|
return panel
|
|
1535
|
1509
|
}
|
|
1536
|
1510
|
|
|
|
1511
|
+ private func makeSettingsPageContent() -> NSView {
|
|
|
1512
|
+ let panel = NSView()
|
|
|
1513
|
+ panel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1514
|
+
|
|
|
1515
|
+ let card = roundedContainer(cornerRadius: 16, color: palette.sectionCard)
|
|
|
1516
|
+ card.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1517
|
+ styleSurface(card, borderColor: palette.inputBorder, borderWidth: 1, shadow: true)
|
|
|
1518
|
+ panel.addSubview(card)
|
|
|
1519
|
+
|
|
|
1520
|
+ let stack = NSStackView()
|
|
|
1521
|
+ stack.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1522
|
+ stack.orientation = .vertical
|
|
|
1523
|
+ stack.spacing = 18
|
|
|
1524
|
+ stack.alignment = .leading
|
|
|
1525
|
+ card.addSubview(stack)
|
|
|
1526
|
+
|
|
|
1527
|
+ let pageTitle = textLabel("Settings", font: typography.pageTitle, color: palette.textPrimary)
|
|
|
1528
|
+ let pageSubtitle = textLabel("Manage appearance, account, and app options.", font: typography.fieldLabel, color: palette.textSecondary)
|
|
|
1529
|
+ stack.addArrangedSubview(pageTitle)
|
|
|
1530
|
+ stack.addArrangedSubview(pageSubtitle)
|
|
|
1531
|
+ stack.setCustomSpacing(24, after: pageSubtitle)
|
|
|
1532
|
+
|
|
|
1533
|
+ let appearanceTitle = textLabel("Appearance", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
|
1534
|
+ stack.addArrangedSubview(appearanceTitle)
|
|
|
1535
|
+ let darkModeRow = makeSettingsDarkModeRow()
|
|
|
1536
|
+ stack.addArrangedSubview(darkModeRow)
|
|
|
1537
|
+ darkModeRow.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
1538
|
+ stack.setCustomSpacing(24, after: darkModeRow)
|
|
|
1539
|
+
|
|
|
1540
|
+ let accountTitle = textLabel("Account", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
|
1541
|
+ stack.addArrangedSubview(accountTitle)
|
|
|
1542
|
+ let googleAccountRow = makeSettingsGoogleAccountRow()
|
|
|
1543
|
+ stack.addArrangedSubview(googleAccountRow)
|
|
|
1544
|
+ googleAccountRow.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
1545
|
+ stack.setCustomSpacing(24, after: googleAccountRow)
|
|
|
1546
|
+
|
|
|
1547
|
+ let appTitle = textLabel("App", font: typography.joinWithURLTitle, color: palette.textPrimary)
|
|
|
1548
|
+ stack.addArrangedSubview(appTitle)
|
|
|
1549
|
+
|
|
|
1550
|
+ if shouldShowRateUsInSettings {
|
|
|
1551
|
+ let rateButton = makeSettingsActionButton(icon: "★", title: "Rate Us", action: .rateUs)
|
|
|
1552
|
+ stack.addArrangedSubview(rateButton)
|
|
|
1553
|
+ rateButton.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
1554
|
+ }
|
|
|
1555
|
+ let supportButton = makeSettingsActionButton(icon: "💬", title: "Support", action: .support)
|
|
|
1556
|
+ stack.addArrangedSubview(supportButton)
|
|
|
1557
|
+ supportButton.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
1558
|
+ let shareButton = makeSettingsActionButton(icon: "⤴︎", title: "Share App", action: .shareApp)
|
|
|
1559
|
+ stack.addArrangedSubview(shareButton)
|
|
|
1560
|
+ shareButton.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
1561
|
+ if storeKitCoordinator.hasPremiumAccess && !storeKitCoordinator.hasLifetimeAccess {
|
|
|
1562
|
+ let upgradeButton = makeSettingsActionButton(icon: "⬆︎", title: "Upgrade", action: .upgrade)
|
|
|
1563
|
+ stack.addArrangedSubview(upgradeButton)
|
|
|
1564
|
+ upgradeButton.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
|
|
|
1565
|
+ }
|
|
|
1566
|
+
|
|
|
1567
|
+ NSLayoutConstraint.activate([
|
|
|
1568
|
+ card.centerXAnchor.constraint(equalTo: panel.centerXAnchor),
|
|
|
1569
|
+ card.topAnchor.constraint(equalTo: panel.topAnchor, constant: 36),
|
|
|
1570
|
+ card.bottomAnchor.constraint(lessThanOrEqualTo: panel.bottomAnchor, constant: -36),
|
|
|
1571
|
+ card.widthAnchor.constraint(lessThanOrEqualToConstant: 620),
|
|
|
1572
|
+ card.widthAnchor.constraint(greaterThanOrEqualToConstant: 460),
|
|
|
1573
|
+ card.leadingAnchor.constraint(greaterThanOrEqualTo: panel.leadingAnchor, constant: 30),
|
|
|
1574
|
+ card.trailingAnchor.constraint(lessThanOrEqualTo: panel.trailingAnchor, constant: -30),
|
|
|
1575
|
+
|
|
|
1576
|
+ stack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 28),
|
|
|
1577
|
+ stack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -28),
|
|
|
1578
|
+ stack.topAnchor.constraint(equalTo: card.topAnchor, constant: 24),
|
|
|
1579
|
+ stack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -24)
|
|
|
1580
|
+ ])
|
|
|
1581
|
+
|
|
|
1582
|
+ return panel
|
|
|
1583
|
+ }
|
|
|
1584
|
+
|
|
|
1585
|
+ private func makeSettingsDarkModeRow() -> NSView {
|
|
|
1586
|
+ let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
|
|
|
1587
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1588
|
+ row.heightAnchor.constraint(equalToConstant: 52).isActive = true
|
|
|
1589
|
+ styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
1590
|
+
|
|
|
1591
|
+ let icon = textLabel("◐", font: NSFont.systemFont(ofSize: 18, weight: .medium), color: palette.textPrimary)
|
|
|
1592
|
+ let title = textLabel("Dark Mode", font: NSFont.systemFont(ofSize: 15, weight: .semibold), color: palette.textPrimary)
|
|
|
1593
|
+ let toggle = NSSwitch()
|
|
|
1594
|
+ toggle.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1595
|
+ toggle.state = darkModeEnabled ? .on : .off
|
|
|
1596
|
+ toggle.target = self
|
|
|
1597
|
+ toggle.action = #selector(settingsPageDarkModeToggled(_:))
|
|
|
1598
|
+
|
|
|
1599
|
+ row.addSubview(icon)
|
|
|
1600
|
+ row.addSubview(title)
|
|
|
1601
|
+ row.addSubview(toggle)
|
|
|
1602
|
+ NSLayoutConstraint.activate([
|
|
|
1603
|
+ icon.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 14),
|
|
|
1604
|
+ icon.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
|
1605
|
+ title.leadingAnchor.constraint(equalTo: icon.trailingAnchor, constant: 10),
|
|
|
1606
|
+ title.centerYAnchor.constraint(equalTo: row.centerYAnchor),
|
|
|
1607
|
+ toggle.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -14),
|
|
|
1608
|
+ toggle.centerYAnchor.constraint(equalTo: row.centerYAnchor)
|
|
|
1609
|
+ ])
|
|
|
1610
|
+ return row
|
|
|
1611
|
+ }
|
|
|
1612
|
+
|
|
|
1613
|
+ private func makeSettingsGoogleAccountRow() -> NSView {
|
|
|
1614
|
+ let row = roundedContainer(cornerRadius: 10, color: palette.inputBackground)
|
|
|
1615
|
+ row.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1616
|
+ styleSurface(row, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
1617
|
+
|
|
|
1618
|
+ let signedIn = googleOAuth.loadTokens() != nil
|
|
|
1619
|
+ let titleText = signedIn ? (scheduleCurrentProfile?.name ?? "Google account connected") : "Google account not connected"
|
|
|
1620
|
+ let subtitleText = signedIn ? (scheduleCurrentProfile?.email ?? "Signed in") : "Sign in to sync your meetings and calendar."
|
|
|
1621
|
+
|
|
|
1622
|
+ let title = textLabel(titleText, font: NSFont.systemFont(ofSize: 15, weight: .semibold), color: palette.textPrimary)
|
|
|
1623
|
+ let subtitle = textLabel(subtitleText, font: NSFont.systemFont(ofSize: 13, weight: .regular), color: palette.textSecondary)
|
|
|
1624
|
+ subtitle.maximumNumberOfLines = 2
|
|
|
1625
|
+ subtitle.lineBreakMode = .byTruncatingTail
|
|
|
1626
|
+
|
|
|
1627
|
+ let actionButton = NSButton(title: signedIn ? "Sign Out" : "Sign in with Google", target: self, action: #selector(settingsGoogleActionButtonClicked(_:)))
|
|
|
1628
|
+ actionButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1629
|
+ actionButton.bezelStyle = .rounded
|
|
|
1630
|
+ actionButton.controlSize = .regular
|
|
|
1631
|
+
|
|
|
1632
|
+ row.addSubview(title)
|
|
|
1633
|
+ row.addSubview(subtitle)
|
|
|
1634
|
+ row.addSubview(actionButton)
|
|
|
1635
|
+ NSLayoutConstraint.activate([
|
|
|
1636
|
+ row.heightAnchor.constraint(equalToConstant: 78),
|
|
|
1637
|
+ title.leadingAnchor.constraint(equalTo: row.leadingAnchor, constant: 14),
|
|
|
1638
|
+ title.topAnchor.constraint(equalTo: row.topAnchor, constant: 12),
|
|
|
1639
|
+ subtitle.leadingAnchor.constraint(equalTo: title.leadingAnchor),
|
|
|
1640
|
+ subtitle.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 4),
|
|
|
1641
|
+ subtitle.trailingAnchor.constraint(lessThanOrEqualTo: actionButton.leadingAnchor, constant: -14),
|
|
|
1642
|
+ actionButton.trailingAnchor.constraint(equalTo: row.trailingAnchor, constant: -14),
|
|
|
1643
|
+ actionButton.centerYAnchor.constraint(equalTo: row.centerYAnchor)
|
|
|
1644
|
+ ])
|
|
|
1645
|
+
|
|
|
1646
|
+ return row
|
|
|
1647
|
+ }
|
|
|
1648
|
+
|
|
|
1649
|
+ private func makeSettingsActionButton(icon: String, title: String, action: SettingsAction) -> NSButton {
|
|
|
1650
|
+ let button = HoverButton(title: "", target: self, action: #selector(settingsPageActionButtonClicked(_:)))
|
|
|
1651
|
+ button.translatesAutoresizingMaskIntoConstraints = false
|
|
|
1652
|
+ button.isBordered = false
|
|
|
1653
|
+ button.wantsLayer = true
|
|
|
1654
|
+ button.layer?.cornerRadius = 10
|
|
|
1655
|
+ button.layer?.backgroundColor = palette.inputBackground.cgColor
|
|
|
1656
|
+ styleSurface(button, borderColor: palette.inputBorder, borderWidth: 1, shadow: false)
|
|
|
1657
|
+ button.heightAnchor.constraint(equalToConstant: 46).isActive = true
|
|
|
1658
|
+ button.tag = action.rawValue
|
|
|
1659
|
+
|
|
|
1660
|
+ let iconLabel = textLabel(icon, font: NSFont.systemFont(ofSize: 17, weight: .medium), color: palette.textPrimary)
|
|
|
1661
|
+ let titleLabel = textLabel(title, font: NSFont.systemFont(ofSize: 15, weight: .semibold), color: palette.textPrimary)
|
|
|
1662
|
+ button.addSubview(iconLabel)
|
|
|
1663
|
+ button.addSubview(titleLabel)
|
|
|
1664
|
+ NSLayoutConstraint.activate([
|
|
|
1665
|
+ iconLabel.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: 14),
|
|
|
1666
|
+ iconLabel.centerYAnchor.constraint(equalTo: button.centerYAnchor),
|
|
|
1667
|
+ titleLabel.leadingAnchor.constraint(equalTo: iconLabel.trailingAnchor, constant: 10),
|
|
|
1668
|
+ titleLabel.centerYAnchor.constraint(equalTo: button.centerYAnchor)
|
|
|
1669
|
+ ])
|
|
|
1670
|
+ return button
|
|
|
1671
|
+ }
|
|
|
1672
|
+
|
|
|
1673
|
+ @objc private func settingsPageDarkModeToggled(_ sender: NSSwitch) {
|
|
|
1674
|
+ setDarkMode(sender.state == .on)
|
|
|
1675
|
+ }
|
|
|
1676
|
+
|
|
|
1677
|
+ @objc private func settingsPageActionButtonClicked(_ sender: NSButton) {
|
|
|
1678
|
+ guard let action = SettingsAction(rawValue: sender.tag) else { return }
|
|
|
1679
|
+ handleSettingsAction(action)
|
|
|
1680
|
+ }
|
|
|
1681
|
+
|
|
|
1682
|
+ @objc private func settingsGoogleActionButtonClicked(_ sender: NSButton) {
|
|
|
1683
|
+ if googleOAuth.loadTokens() == nil {
|
|
|
1684
|
+ scheduleConnectClicked()
|
|
|
1685
|
+ } else {
|
|
|
1686
|
+ performGoogleSignOut()
|
|
|
1687
|
+ pageCache[.settings] = nil
|
|
|
1688
|
+ showSidebarPage(.settings)
|
|
|
1689
|
+ }
|
|
|
1690
|
+ }
|
|
|
1691
|
+
|
|
1537
|
1692
|
func makeBrowseWebContent() -> NSView {
|
|
1538
|
1693
|
let panel = NSView()
|
|
1539
|
1694
|
panel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -6051,6 +6206,7 @@ private extension ViewController {
|
|
6051
|
6206
|
self.pageCache[.joinMeetings] = nil
|
|
6052
|
6207
|
self.pageCache[.photo] = nil
|
|
6053
|
6208
|
self.pageCache[.video] = nil
|
|
|
6209
|
+ self.pageCache[.settings] = nil
|
|
6054
|
6210
|
self.showSidebarPage(self.selectedSidebarPage)
|
|
6055
|
6211
|
}
|
|
6056
|
6212
|
} catch {
|
|
|
@@ -6097,8 +6253,11 @@ private extension ViewController {
|
|
6097
|
6253
|
do {
|
|
6098
|
6254
|
try googleOAuth.signOut()
|
|
6099
|
6255
|
pageCache[.photo] = nil
|
|
|
6256
|
+ pageCache[.settings] = nil
|
|
6100
|
6257
|
if selectedSidebarPage == .photo {
|
|
6101
|
6258
|
showSidebarPage(.photo)
|
|
|
6259
|
+ } else if selectedSidebarPage == .settings {
|
|
|
6260
|
+ showSidebarPage(.settings)
|
|
6102
|
6261
|
} else {
|
|
6103
|
6262
|
Task { [weak self] in
|
|
6104
|
6263
|
await self?.loadSchedule()
|