Przeglądaj źródła

Replace settings popover with a centered settings page.

Route sidebar Settings to a full page, move popup actions into a professional sectioned layout, and add Google sign-in/sign-out controls while preserving existing top-bar account access.

Made-with: Cursor
huzaifahayat12 1 tydzień temu
rodzic
commit
061fbed03f
1 zmienionych plików z 186 dodań i 27 usunięć
  1. 186 27
      meetings_app/ViewController.swift

+ 186 - 27
meetings_app/ViewController.swift

@@ -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()