Просмотр исходного кода

Improve meeting join consent modal controls and dismiss flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
huzaifahayat12 2 месяцев назад
Родитель
Сommit
9062f4251a
1 измененных файлов с 149 добавлено и 7 удалено
  1. 149 7
      meetings_app/ViewController.swift

+ 149 - 7
meetings_app/ViewController.swift

@@ -509,6 +509,8 @@ final class ViewController: NSViewController {
     private let aiCompanionLocalRecordingsDefaultsKey = "aiCompanion.localRecordings"
     private let aiCompanionPreferredLanguage1DefaultsKey = "aiCompanion.preferredLanguage1"
     private let aiCompanionPreferredLanguage2DefaultsKey = "aiCompanion.preferredLanguage2"
+    private weak var activeMeetingConsentWindow: NSWindow?
+    private var didTapInlineMeetingConsentClose = false
     private let ratingEligibleUsageSeconds: TimeInterval = 30 * 60
     private let meetingTranscriptionService = MeetingTranscriptionService()
     private let meetingNotesService = MeetingNotesService()
@@ -876,7 +878,8 @@ private extension ViewController {
             return
         }
 
-        beginMeetingRecordingIfConsented(meetingTitle: "Quick Join Meeting", meetingURL: url)
+        let shouldOpenMeeting = beginMeetingRecordingIfConsented(meetingTitle: "Quick Join Meeting", meetingURL: url)
+        guard shouldOpenMeeting else { return }
         openInDefaultBrowser(url: url)
         consumeNonPremiumJoinTrialIfNeeded()
     }
@@ -1319,15 +1322,147 @@ private extension ViewController {
         alert.runModal()
     }
 
-    private func requestMeetingRecordingConsent(title: String) -> Bool {
+    private enum MeetingRecordingConsentChoice {
+        case allowAndContinue
+        case continueWithoutRecording
+        case dismissed
+    }
+
+    private func requestMeetingRecordingConsent(title: String) -> MeetingRecordingConsentChoice {
         let alert = NSAlert()
         alert.messageText = "Record this meeting locally?"
         alert.informativeText = "With your consent, the app will record meeting audio on this Mac and show it in AI Companion after the meeting."
         alert.addButton(withTitle: "Allow and Continue")
         alert.addButton(withTitle: "Continue Without Recording")
+        alert.window.title = title
+
+        let speechOptions = aiCompanionSupportedSpeechLocaleOptions()
+        let languageContainer = NSView(frame: NSRect(x: 0, y: 0, width: 360, height: 184))
+        languageContainer.translatesAutoresizingMaskIntoConstraints = false
+        languageContainer.widthAnchor.constraint(equalToConstant: 360).isActive = true
+        languageContainer.heightAnchor.constraint(equalToConstant: 184).isActive = true
+
+        let languageTitle = NSTextField(labelWithString: "Transcription Languages")
+        languageTitle.translatesAutoresizingMaskIntoConstraints = false
+        languageTitle.font = NSFont.systemFont(ofSize: 12, weight: .semibold)
+        languageTitle.textColor = NSColor.secondaryLabelColor
+
+        let language1Popup = NSPopUpButton(frame: .zero, pullsDown: false)
+        language1Popup.translatesAutoresizingMaskIntoConstraints = false
+        language1Popup.controlSize = .regular
+        for option in speechOptions {
+            language1Popup.addItem(withTitle: option.displayName)
+            language1Popup.lastItem?.representedObject = option.identifier
+        }
+
+        let selectedPrimary = UserDefaults.standard.string(forKey: aiCompanionPreferredLanguage1DefaultsKey)
+            ?? speechOptions.first?.identifier
+            ?? Locale.current.identifier
+        selectSettingsPageLanguage(identifier: selectedPrimary, in: language1Popup)
+
+        let language2Popup = NSPopUpButton(frame: .zero, pullsDown: false)
+        language2Popup.translatesAutoresizingMaskIntoConstraints = false
+        language2Popup.controlSize = .regular
+        language2Popup.addItem(withTitle: "None")
+        language2Popup.lastItem?.representedObject = ""
+        for option in speechOptions {
+            language2Popup.addItem(withTitle: option.displayName)
+            language2Popup.lastItem?.representedObject = option.identifier
+        }
+        if let selectedSecondary = UserDefaults.standard.string(forKey: aiCompanionPreferredLanguage2DefaultsKey),
+           selectedSecondary.isEmpty == false {
+            selectSettingsPageLanguage(identifier: selectedSecondary, in: language2Popup)
+        } else {
+            language2Popup.selectItem(at: 0)
+        }
+
+        let language1Label = NSTextField(labelWithString: "Preferred Language 1")
+        language1Label.translatesAutoresizingMaskIntoConstraints = false
+        language1Label.font = NSFont.systemFont(ofSize: 12, weight: .semibold)
+        language1Label.textColor = NSColor.secondaryLabelColor
+        language1Label.alignment = .center
+
+        let language2Label = NSTextField(labelWithString: "Preferred Language 2")
+        language2Label.translatesAutoresizingMaskIntoConstraints = false
+        language2Label.font = NSFont.systemFont(ofSize: 12, weight: .semibold)
+        language2Label.textColor = NSColor.secondaryLabelColor
+        language2Label.alignment = .center
+
+        languageContainer.addSubview(languageTitle)
+        languageContainer.addSubview(language1Label)
+        languageContainer.addSubview(language1Popup)
+        languageContainer.addSubview(language2Label)
+        languageContainer.addSubview(language2Popup)
+
+        NSLayoutConstraint.activate([
+            languageTitle.topAnchor.constraint(equalTo: languageContainer.topAnchor, constant: 4),
+            languageTitle.centerXAnchor.constraint(equalTo: languageContainer.centerXAnchor),
+
+            language1Label.topAnchor.constraint(equalTo: languageTitle.bottomAnchor, constant: 12),
+            language1Label.centerXAnchor.constraint(equalTo: languageContainer.centerXAnchor),
+            language1Popup.topAnchor.constraint(equalTo: language1Label.bottomAnchor, constant: 4),
+            language1Popup.centerXAnchor.constraint(equalTo: languageContainer.centerXAnchor),
+            language1Popup.widthAnchor.constraint(equalToConstant: 280),
+
+            language2Label.topAnchor.constraint(equalTo: language1Popup.bottomAnchor, constant: 10),
+            language2Label.centerXAnchor.constraint(equalTo: languageContainer.centerXAnchor),
+            language2Popup.topAnchor.constraint(equalTo: language2Label.bottomAnchor, constant: 4),
+            language2Popup.centerXAnchor.constraint(equalTo: languageContainer.centerXAnchor),
+            language2Popup.widthAnchor.constraint(equalToConstant: 280),
+            language2Popup.bottomAnchor.constraint(lessThanOrEqualTo: languageContainer.bottomAnchor, constant: -4)
+        ])
+
+        alert.accessoryView = languageContainer
+        didTapInlineMeetingConsentClose = false
+        activeMeetingConsentWindow = alert.window
+        if let contentView = alert.window.contentView {
+            let topCloseButton = NSButton(title: "✕", target: self, action: #selector(meetingConsentInlineCloseTapped(_:)))
+            topCloseButton.translatesAutoresizingMaskIntoConstraints = false
+            topCloseButton.isBordered = false
+            topCloseButton.font = NSFont.systemFont(ofSize: 15, weight: .semibold)
+            topCloseButton.contentTintColor = NSColor.tertiaryLabelColor
+            topCloseButton.wantsLayer = true
+            topCloseButton.layer?.cornerRadius = 20
+            topCloseButton.layer?.backgroundColor = NSColor.windowBackgroundColor.withAlphaComponent(0.35).cgColor
+            contentView.addSubview(topCloseButton)
+            NSLayoutConstraint.activate([
+                topCloseButton.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
+                topCloseButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
+                topCloseButton.widthAnchor.constraint(equalToConstant: 40),
+                topCloseButton.heightAnchor.constraint(equalToConstant: 40)
+            ])
+        }
         let response = alert.runModal()
-        if response == .alertFirstButtonReturn { return true }
-        return false
+        activeMeetingConsentWindow = nil
+
+        if didTapInlineMeetingConsentClose {
+            return .dismissed
+        }
+
+        if let primary = language1Popup.selectedItem?.representedObject as? String,
+           primary.isEmpty == false {
+            var secondary = language2Popup.selectedItem?.representedObject as? String
+            if secondary?.isEmpty == true {
+                secondary = nil
+            }
+            updateAiCompanionPreferredSpeechLanguages(primary: primary, secondary: secondary)
+        }
+
+        switch response {
+        case .alertFirstButtonReturn:
+            return .allowAndContinue
+        case .alertSecondButtonReturn:
+            return .continueWithoutRecording
+        default:
+            return .dismissed
+        }
+    }
+
+    @objc private func meetingConsentInlineCloseTapped(_ sender: NSButton) {
+        didTapInlineMeetingConsentClose = true
+        guard let consentWindow = activeMeetingConsentWindow else { return }
+        NSApp.stopModal(withCode: .abort)
+        consentWindow.orderOut(nil)
     }
 
     private func loadAiCompanionLocalRecordings() {
@@ -1523,11 +1658,16 @@ private extension ViewController {
         return directory
     }
 
-    private func beginMeetingRecordingIfConsented(meetingTitle: String, meetingURL: URL) {
+    private func beginMeetingRecordingIfConsented(meetingTitle: String, meetingURL: URL) -> Bool {
+        let consentChoice = requestMeetingRecordingConsent(title: meetingTitle)
+        if consentChoice == .dismissed {
+            return false
+        }
+
         meetingRecordingMonitorTask?.cancel()
         meetingRecordingMonitorTask = nil
         if activeMeetingRecordingSession != nil { finishActiveMeetingRecording(cancelMonitoring: false) }
-        guard requestMeetingRecordingConsent(title: meetingTitle) else { return }
+        guard consentChoice == .allowAndContinue else { return true }
         let permission = AVCaptureDevice.authorizationStatus(for: .audio)
         switch permission {
         case .authorized:
@@ -1548,6 +1688,7 @@ private extension ViewController {
         @unknown default:
             break
         }
+        return true
     }
 
     private func startAutomaticRecordingFlow(meetingTitle: String, meetingURL: URL) {
@@ -8858,7 +8999,8 @@ private extension ViewController {
 
     private func openMeetingURL(_ url: URL, meeting: ScheduledMeeting? = nil) {
         let title = meeting?.title ?? "Scheduled Meeting"
-        beginMeetingRecordingIfConsented(meetingTitle: title, meetingURL: url)
+        let shouldOpenMeeting = beginMeetingRecordingIfConsented(meetingTitle: title, meetingURL: url)
+        guard shouldOpenMeeting else { return }
         NSWorkspace.shared.open(url)
     }