11import SwiftUI
22import AppKit
3+ import Combine
34
45@MainActor
56final class MenuBarPanelManager : MenuBarPanelManagerType , ObservableObject {
@@ -14,12 +15,14 @@ final class MenuBarPanelManager: MenuBarPanelManagerType, ObservableObject {
1415 var isSettingsVisible = false
1516 var isSummaryVisible = false
1617 var isPreviousRecapsVisible = false
17-
18+
1819 let initialSize = CGSize ( width: 485 , height: 500 )
1920 let menuBarHeight : CGFloat = 24
2021 let panelOffset : CGFloat = 12
2122 let panelSpacing : CGFloat = 8
22-
23+
24+ private var cancellables = Set < AnyCancellable > ( )
25+
2326 let audioProcessController : AudioProcessController
2427 let appSelectionViewModel : AppSelectionViewModel
2528 let previousRecapsViewModel : PreviousRecapsViewModel
@@ -30,7 +33,7 @@ final class MenuBarPanelManager: MenuBarPanelManagerType, ObservableObject {
3033 let generalSettingsViewModel : GeneralSettingsViewModel
3134 let userPreferencesRepository : UserPreferencesRepositoryType
3235 let meetingDetectionService : any MeetingDetectionServiceType
33-
36+
3437 init (
3538 statusBarManager: StatusBarManagerType ,
3639 whisperModelsViewModel: WhisperModelsViewModel ,
@@ -58,47 +61,56 @@ final class MenuBarPanelManager: MenuBarPanelManagerType, ObservableObject {
5861 self . previousRecapsViewModel = previousRecapsViewModel
5962 setupDelegates ( )
6063 }
61-
64+
6265 private func setupDelegates( ) {
6366 statusBarManager. delegate = self
67+
68+ // Observe recording state changes to update status bar icon
69+ recapViewModel. $isRecording
70+ . receive ( on: DispatchQueue . main)
71+ . sink { [ weak self] isRecording in
72+ print ( " 🔴 Recording state changed to: \( isRecording) " )
73+ self ? . statusBarManager. setRecordingState ( isRecording)
74+ }
75+ . store ( in: & cancellables)
6476 }
65-
77+
6678 func createMainPanel( ) -> SlidingPanel {
6779 recapViewModel. delegate = self
6880 let contentView = RecapHomeView ( viewModel: recapViewModel)
6981 let hostingController = NSHostingController ( rootView: contentView)
7082 hostingController. view. wantsLayer = true
7183 hostingController. view. layer? . cornerRadius = 12
72-
84+
7385 let newPanel = SlidingPanel ( contentViewController: hostingController)
7486 newPanel. panelDelegate = self
7587 return newPanel
7688 }
77-
89+
7890 func positionPanel( _ panel: NSPanel , size: CGSize ? = nil ) {
7991 guard let statusButton = statusBarManager. statusButton,
8092 let statusWindow = statusButton. window,
8193 let screen = statusWindow. screen else { return }
82-
94+
8395 let panelSize = size ?? initialSize
8496 let screenFrame = screen. frame
8597 let finalX = screenFrame. maxX - panelSize. width - panelOffset
8698 let panelY = screenFrame. maxY - menuBarHeight - panelSize. height - panelSpacing
87-
99+
88100 panel. setFrame (
89101 NSRect ( x: finalX, y: panelY, width: panelSize. width, height: panelSize. height) ,
90102 display: false
91103 )
92104 }
93-
105+
94106 private func showPanel( ) {
95107 if panel == nil {
96108 createAndShowNewPanel ( )
97109 } else {
98110 showExistingPanel ( )
99111 }
100112 }
101-
113+
102114 private func createAndShowNewPanel( ) {
103115 Task {
104116 do {
@@ -107,75 +119,75 @@ final class MenuBarPanelManager: MenuBarPanelManagerType, ObservableObject {
107119 } catch {
108120 await createMainPanelAndPosition ( )
109121 }
110-
122+
111123 await animateAndShowPanel ( )
112124 }
113125 }
114-
126+
115127 private func createPanelBasedOnOnboardingStatus( isOnboarded: Bool ) async {
116128 if !isOnboarded {
117129 panel = createOnboardingPanel ( )
118130 } else {
119131 panel = createMainPanel ( )
120132 }
121-
133+
122134 if let panel = panel {
123135 positionPanel ( panel)
124136 }
125137 }
126-
138+
127139 private func createMainPanelAndPosition( ) async {
128140 panel = createMainPanel ( )
129141 if let panel = panel {
130142 positionPanel ( panel)
131143 }
132144 }
133-
145+
134146 private func animateAndShowPanel( ) async {
135147 guard let panel = panel else { return }
136148 panel. contentView? . wantsLayer = true
137-
149+
138150 await withCheckedContinuation { continuation in
139151 PanelAnimator . slideIn ( panel: panel) { [ weak self] in
140152 self ? . isVisible = true
141153 continuation. resume ( )
142154 }
143155 }
144156 }
145-
157+
146158 private func showExistingPanel( ) {
147159 guard let panel = panel else { return }
148-
160+
149161 positionPanel ( panel)
150162 panel. contentView? . wantsLayer = true
151-
163+
152164 PanelAnimator . slideIn ( panel: panel) { [ weak self] in
153165 self ? . isVisible = true
154166 }
155167 }
156-
168+
157169 func showMainPanel( ) {
158170 showPanel ( )
159171 }
160-
172+
161173 func hideMainPanel( ) {
162174 hidePanel ( )
163175 }
164-
176+
165177 private func hidePanel( ) {
166178 guard let panel = panel else { return }
167-
179+
168180 PanelAnimator . slideOut ( panel: panel) { [ weak self] in
169181 self ? . isVisible = false
170182 }
171183 }
172-
184+
173185 private func hideAllSidePanels( ) {
174186 if isSettingsVisible { hideSettingsPanel ( ) }
175187 if isSummaryVisible { hideSummaryPanel ( ) }
176188 if isPreviousRecapsVisible { hidePreviousRecapsWindow ( ) }
177189 }
178-
190+
179191 func toggleSidePanel(
180192 isVisible: Bool ,
181193 show: ( ) -> Void ,
@@ -185,7 +197,7 @@ final class MenuBarPanelManager: MenuBarPanelManagerType, ObservableObject {
185197 hideAllSidePanels ( )
186198 show ( )
187199 }
188-
200+
189201 deinit {
190202 panel = nil
191203 settingsPanel = nil
@@ -200,10 +212,42 @@ extension MenuBarPanelManager: StatusBarDelegate {
200212 showPanel ( )
201213 }
202214 }
203-
215+
216+ func startRecordingRequested( ) {
217+ Task {
218+ await startRecordingForAllApplications ( )
219+ }
220+ }
221+
222+ func stopRecordingRequested( ) {
223+ Task {
224+ await recapViewModel. stopRecording ( )
225+ statusBarManager. setRecordingState ( false )
226+ }
227+ }
228+
229+ func settingsRequested( ) {
230+ if isVisible {
231+ hidePanel ( )
232+ } else {
233+ showPanel ( )
234+ }
235+ }
236+
204237 func quitRequested( ) {
205238 NSApplication . shared. terminate ( nil )
206239 }
240+
241+ private func startRecordingForAllApplications( ) async {
242+ // Set the selected app to "All Apps" for system-wide recording
243+ recapViewModel. selectApp ( SelectableApp . allApps. audioProcess)
244+
245+ // Start the recording (respects user's microphone setting)
246+ await recapViewModel. startRecording ( )
247+
248+ // Update the status bar icon to show recording state
249+ statusBarManager. setRecordingState ( recapViewModel. isRecording)
250+ }
207251}
208252
209253extension MenuBarPanelManager : SlidingPanelDelegate {
0 commit comments