Ver código fonte

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 3 meses atrás
pai
commit
061fbed03f
1 arquivos alterados com 186 adições e 27 exclusões
  1. 186 27
      meetings_app/ViewController.swift

+ 186 - 27
meetings_app/ViewController.swift

@@ -522,12 +522,6 @@ private extension ViewController {
     private func activateSidebarItem(_ view: NSView) {
         guard let page = sidebarPageByView[ObjectIdentifier(view)],
               page != selectedSidebarPage || page == .settings else { return }
-
-        if page == .settings {
-            showSettingsPopover()
-            return
-        }
-
         showSidebarPage(page)
     }
 
@@ -554,12 +548,6 @@ private extension ViewController {
     @objc private func sidebarButtonClicked(_ sender: NSButton) {
         guard let page = SidebarPage(rawValue: sender.tag),
               page != selectedSidebarPage || page == .settings else { return }
-
-        if page == .settings {
-            showSettingsPopover()
-            return
-        }
-
         showSidebarPage(page)
     }
 
@@ -831,8 +819,6 @@ private extension ViewController {
         NSApp.appearance = NSAppearance(named: enabled ? .darkAqua : .aqua)
         view.appearance = NSAppearance(named: enabled ? .darkAqua : .aqua)
         palette = Palette(isDarkMode: enabled)
-        settingsPopover?.performClose(nil)
-        settingsPopover = nil
         reloadTheme()
     }
 
@@ -870,8 +856,6 @@ private extension ViewController {
     private func handleSettingsAction(_ action: SettingsAction) {
         switch action {
         case .restore:
-            settingsPopover?.performClose(nil)
-            settingsPopover = nil
             Task { [weak self] in
                 guard let self else { return }
                 let message = await self.storeKitCoordinator.restorePurchases()
@@ -879,30 +863,20 @@ private extension ViewController {
                 self.showSimpleAlert(title: "Restore Purchases", message: message)
             }
         case .rateUs:
-            settingsPopover?.performClose(nil)
-            settingsPopover = nil
             openRateUsDestination()
         case .support:
-            settingsPopover?.performClose(nil)
-            settingsPopover = nil
             if let supportURL = Bundle.main.object(forInfoDictionaryKey: "SupportURL") as? String,
                let url = URL(string: supportURL) {
                 openInAppBrowser(with: url, policy: inAppBrowserDefaultPolicy)
             }
         case .moreApps:
-            settingsPopover?.performClose(nil)
-            settingsPopover = nil
             if let moreAppsURL = Bundle.main.object(forInfoDictionaryKey: "MoreAppsURL") as? String,
                let url = URL(string: moreAppsURL) {
                 openInAppBrowser(with: url, policy: inAppBrowserDefaultPolicy)
             }
         case .shareApp:
-            settingsPopover?.performClose(nil)
-            settingsPopover = nil
             shareAppFromSettingsMenu()
         case .upgrade:
-            settingsPopover?.performClose(nil)
-            settingsPopover = nil
             showPaywall(upgradeFlow: true, preferredPlan: .lifetime)
         }
     }
@@ -1510,7 +1484,7 @@ private extension ViewController {
         case .video:
             built = makeCalendarPageContent()
         case .settings:
-            built = makePlaceholderPage(title: "Settings", subtitle: "Preferences and account options.")
+            built = makeSettingsPageContent()
         }
         pageCache[page] = built
         return built
@@ -1534,6 +1508,187 @@ private extension ViewController {
         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 {
         let panel = NSView()
         panel.translatesAutoresizingMaskIntoConstraints = false
@@ -6051,6 +6206,7 @@ private extension ViewController {
                     self.pageCache[.joinMeetings] = nil
                     self.pageCache[.photo] = nil
                     self.pageCache[.video] = nil
+                    self.pageCache[.settings] = nil
                     self.showSidebarPage(self.selectedSidebarPage)
                 }
             } catch {
@@ -6097,8 +6253,11 @@ private extension ViewController {
         do {
             try googleOAuth.signOut()
             pageCache[.photo] = nil
+            pageCache[.settings] = nil
             if selectedSidebarPage == .photo {
                 showSidebarPage(.photo)
+            } else if selectedSidebarPage == .settings {
+                showSidebarPage(.settings)
             } else {
                 Task { [weak self] in
                     await self?.loadSchedule()