Skip to content

Commit 1ed4753

Browse files
author
Ivo Bellin Salarin
committed
feat: change the system tray color to red during recording
1 parent def4b46 commit 1ed4753

File tree

3 files changed

+191
-53
lines changed

3 files changed

+191
-53
lines changed

Recap/MenuBar/Manager/MenuBarPanelManager.swift

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import SwiftUI
22
import AppKit
3+
import Combine
34

45
@MainActor
56
final 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

209253
extension MenuBarPanelManager: SlidingPanelDelegate {

0 commit comments

Comments
 (0)