浏览代码

Enable Ai companion audio playback and stable scrolling layout.

Make ended-meeting audio items clickable via a Play Audio action and wrap the page content in a fixed-height scroll view so window height stays constant while cards overflow.

Made-with: Cursor
huzaifahayat12 2 月之前
父节点
当前提交
03d6e9349c
共有 1 个文件被更改,包括 58 次插入10 次删除
  1. 58 10
      meetings_app/ViewController.swift

+ 58 - 10
meetings_app/ViewController.swift

@@ -266,6 +266,7 @@ final class ViewController: NSViewController {
     private var zoomJoinModeByView = [ObjectIdentifier: ZoomJoinMode]()
     private var zoomJoinModeViews: [ZoomJoinMode: NSView] = [:]
     private var settingsActionByView = [ObjectIdentifier: SettingsAction]()
+    private var aiCompanionAudioURLByView = [ObjectIdentifier: URL]()
     private var paywallWindow: NSWindow?
     private weak var paywallOverlayView: NSView?
     private let paywallContentWidth: CGFloat = 520
@@ -1944,6 +1945,24 @@ private extension ViewController {
         panel.translatesAutoresizingMaskIntoConstraints = false
         panel.userInterfaceLayoutDirection = .leftToRight
 
+        let scroll = NSScrollView()
+        scroll.translatesAutoresizingMaskIntoConstraints = false
+        scroll.drawsBackground = false
+        scroll.hasHorizontalScroller = false
+        scroll.hasVerticalScroller = true
+        scroll.autohidesScrollers = true
+        scroll.borderType = .noBorder
+        scroll.scrollerStyle = .overlay
+        scroll.automaticallyAdjustsContentInsets = false
+        let clip = TopAlignedClipView()
+        clip.drawsBackground = false
+        scroll.contentView = clip
+        panel.addSubview(scroll)
+
+        let content = NSView()
+        content.translatesAutoresizingMaskIntoConstraints = false
+        scroll.documentView = content
+
         let contentStack = NSStackView()
         contentStack.translatesAutoresizingMaskIntoConstraints = false
         contentStack.userInterfaceLayoutDirection = .leftToRight
@@ -1987,12 +2006,23 @@ private extension ViewController {
             }
         }
 
-        panel.addSubview(contentStack)
+        content.addSubview(contentStack)
         NSLayoutConstraint.activate([
-            contentStack.leftAnchor.constraint(equalTo: panel.leftAnchor, constant: 28),
-            contentStack.rightAnchor.constraint(equalTo: panel.rightAnchor, constant: -28),
-            contentStack.topAnchor.constraint(equalTo: panel.topAnchor),
-            contentStack.bottomAnchor.constraint(lessThanOrEqualTo: panel.bottomAnchor, constant: -16)
+            scroll.leadingAnchor.constraint(equalTo: panel.leadingAnchor),
+            scroll.trailingAnchor.constraint(equalTo: panel.trailingAnchor),
+            scroll.topAnchor.constraint(equalTo: panel.topAnchor),
+            scroll.bottomAnchor.constraint(equalTo: panel.bottomAnchor),
+
+            content.leadingAnchor.constraint(equalTo: scroll.contentView.leadingAnchor),
+            content.trailingAnchor.constraint(equalTo: scroll.contentView.trailingAnchor),
+            content.topAnchor.constraint(equalTo: scroll.contentView.topAnchor),
+            content.bottomAnchor.constraint(greaterThanOrEqualTo: scroll.contentView.bottomAnchor),
+            content.widthAnchor.constraint(equalTo: scroll.contentView.widthAnchor),
+
+            contentStack.leftAnchor.constraint(equalTo: content.leftAnchor, constant: 28),
+            contentStack.rightAnchor.constraint(equalTo: content.rightAnchor, constant: -28),
+            contentStack.topAnchor.constraint(equalTo: content.topAnchor),
+            contentStack.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -16)
         ])
 
         return panel
@@ -2019,14 +2049,27 @@ private extension ViewController {
         dateLabel.alignment = .left
 
         let audioLink = mockAudioURLString(for: meeting)
-        let audioLabel = textLabel("Mock Audio: \(audioLink)", font: typography.fieldLabel, color: palette.primaryBlue)
-        audioLabel.alignment = .left
-        audioLabel.maximumNumberOfLines = 2
-        audioLabel.lineBreakMode = .byTruncatingTail
+        let audioButton = NSButton(title: "Play Audio", target: self, action: #selector(aiCompanionAudioTapped(_:)))
+        audioButton.translatesAutoresizingMaskIntoConstraints = false
+        audioButton.isBordered = false
+        audioButton.bezelStyle = .inline
+        audioButton.font = NSFont.systemFont(ofSize: 13, weight: .semibold)
+        audioButton.contentTintColor = palette.primaryBlue
+        audioButton.alignment = .left
+        audioButton.setButtonType(.momentaryPushIn)
+        if let audioURL = URL(string: audioLink) {
+            aiCompanionAudioURLByView[ObjectIdentifier(audioButton)] = audioURL
+        }
+
+        let audioURLLabel = textLabel(audioLink, font: typography.fieldLabel, color: palette.textMuted)
+        audioURLLabel.alignment = .left
+        audioURLLabel.maximumNumberOfLines = 2
+        audioURLLabel.lineBreakMode = .byTruncatingTail
 
         stack.addArrangedSubview(title)
         stack.addArrangedSubview(dateLabel)
-        stack.addArrangedSubview(audioLabel)
+        stack.addArrangedSubview(audioButton)
+        stack.addArrangedSubview(audioURLLabel)
 
         card.addSubview(stack)
         NSLayoutConstraint.activate([
@@ -2039,6 +2082,11 @@ private extension ViewController {
         return card
     }
 
+    @objc private func aiCompanionAudioTapped(_ sender: NSButton) {
+        guard let url = aiCompanionAudioURLByView[ObjectIdentifier(sender)] else { return }
+        NSWorkspace.shared.open(url)
+    }
+
     private func mockAudioURLString(for meeting: ScheduledMeeting) -> String {
         let slug = meeting.id.replacingOccurrences(of: "[^A-Za-z0-9_-]", with: "-", options: .regularExpression)
         return "https://mock-audio.local/\(slug).mp3"