Skip to content

Commit 1dd5f7d

Browse files
feature: add onboarding process with a bit of refactor
1 parent 3aa7710 commit 1dd5f7d

48 files changed

Lines changed: 1451 additions & 80 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/linter.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Run SwiftLint
1+
name: Build and Lint
22

33
on:
44
push:
@@ -51,7 +51,7 @@ jobs:
5151
-project Recap.xcodeproj \
5252
-scheme Recap
5353
54-
- name: Build Debug
54+
- name: Build Project
5555
run: |
5656
xcodebuild build \
5757
-project Recap.xcodeproj \

Recap.xcodeproj/project.pbxproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
Helpers/Constants/AppConstants.swift,
5151
Helpers/Constants/UIConstants.swift,
5252
Helpers/MeetingDetection/MeetingPatternMatcher.swift,
53+
Helpers/Permissions/PermissionsHelper.swift,
54+
Helpers/Permissions/PermissionsHelperType.swift,
5355
Repositories/Models/LLMProvider.swift,
5456
Repositories/Models/RecordingInfo.swift,
5557
Repositories/Models/UserPreferencesInfo.swift,
@@ -76,9 +78,11 @@
7678
Services/Summarization/Models/SummarizationResult.swift,
7779
Services/Summarization/SummarizationServiceType.swift,
7880
Services/Transcription/TranscriptionServiceType.swift,
79-
Services/Warnings/WarningManagerType.swift,
81+
Services/Utilities/Warnings/WarningManagerType.swift,
8082
UIComponents/Buttons/PillButton.swift,
8183
UIComponents/Cards/ActionableWarningCard.swift,
84+
UseCases/Onboarding/ViewModel/OnboardingViewModel.swift,
85+
UseCases/Onboarding/ViewModel/OnboardingViewModelType.swift,
8286
UseCases/Settings/Components/MeetingDetection/MeetingDetectionView.swift,
8387
UseCases/Settings/Components/Reusable/CustomToggle.swift,
8488
UseCases/Settings/Components/SettingsCard.swift,

Recap/Audio/Capture/MicrophoneCapture.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,6 @@ final class MicrophoneCapture: MicrophoneCaptureType {
3939
cleanup()
4040
}
4141

42-
func requestPermission() async -> Bool {
43-
await withCheckedContinuation { continuation in
44-
AVCaptureDevice.requestAccess(for: .audio) { granted in
45-
self.logger.info("Microphone permission granted: \(granted)")
46-
continuation.resume(returning: granted)
47-
}
48-
}
49-
}
50-
5142
func start(outputURL: URL, targetFormat: AudioStreamBasicDescription? = nil) throws {
5243
self.outputURL = outputURL
5344

Recap/Audio/Capture/MicrophoneCaptureType.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import AudioToolbox
1212
protocol MicrophoneCaptureType: ObservableObject {
1313
var audioLevel: Float { get }
1414
var recordingFormat: AVAudioFormat? { get }
15-
16-
func requestPermission() async -> Bool
15+
1716
func start(outputURL: URL, targetFormat: AudioStreamBasicDescription?) throws
1817
func stop()
1918
}

Recap/Audio/Processing/Session/RecordingSessionManager.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ protocol RecordingSessionManaging {
88
final class RecordingSessionManager: RecordingSessionManaging {
99
private let logger = Logger(subsystem: AppConstants.Logging.subsystem, category: String(describing: RecordingSessionManager.self))
1010
private let microphoneCapture: MicrophoneCapture
11+
private let permissionsHelper: PermissionsHelperType
1112

12-
init(microphoneCapture: MicrophoneCapture) {
13+
init(microphoneCapture: MicrophoneCapture, permissionsHelper: PermissionsHelperType) {
1314
self.microphoneCapture = microphoneCapture
15+
self.permissionsHelper = permissionsHelper
1416
}
1517

1618
func startSession(configuration: RecordingConfiguration) async throws -> AudioRecordingCoordinatorType {
@@ -27,8 +29,8 @@ final class RecordingSessionManager: RecordingSessionManaging {
2729
let microphoneCaptureToUse = configuration.enableMicrophone ? microphoneCapture : nil
2830

2931
if configuration.enableMicrophone {
30-
let hasPermission = await microphoneCapture.requestPermission()
31-
guard hasPermission else {
32+
let hasPermission = await permissionsHelper.checkMicrophonePermissionStatus()
33+
guard hasPermission == .authorized else {
3234
throw AudioCaptureError.microphonePermissionDenied
3335
}
3436
}

Recap/DataModels/RecapDataModel.xcdatamodeld/RecapDataModel.xcdatamodel/contents

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<attribute name="createdAt" attributeType="Date" defaultDateTimeInterval="0" usesScalarValueType="NO"/>
1616
<attribute name="id" attributeType="String"/>
1717
<attribute name="modifiedAt" attributeType="Date" defaultDateTimeInterval="0" usesScalarValueType="NO"/>
18+
<attribute name="onboarded" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
1819
<attribute name="selectedLLMModelID" optional="YES" attributeType="String"/>
1920
<attribute name="selectedProvider" optional="YES" attributeType="String" defaultValueString="openrouter"/>
2021
<attribute name="summaryPromptTemplate" optional="YES" attributeType="String"/>

Recap/Services/Availability/AvailabilityCoordinator.swift renamed to Recap/Helpers/Availability/AvailabilityHelper.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@ import Foundation
22
import Combine
33

44
@MainActor
5-
final class AvailabilityCoordinator: AvailabilityCoordinatorType {
5+
protocol AvailabilityHelperType: AnyObject {
6+
var isAvailable: Bool { get }
7+
var availabilityPublisher: AnyPublisher<Bool, Never> { get }
8+
9+
func startMonitoring()
10+
func stopMonitoring()
11+
func checkAvailabilityNow() async -> Bool
12+
}
13+
14+
15+
@MainActor
16+
final class AvailabilityHelper: AvailabilityHelperType {
617
@Published private(set) var isAvailable: Bool = false
718
var availabilityPublisher: AnyPublisher<Bool, Never> {
819
$isAvailable.eraseToAnyPublisher()
@@ -50,4 +61,4 @@ final class AvailabilityCoordinator: AvailabilityCoordinatorType {
5061
isAvailable = available
5162
return available
5263
}
53-
}
64+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Foundation
2+
import AVFoundation
3+
import UserNotifications
4+
import ScreenCaptureKit
5+
6+
@MainActor
7+
final class PermissionsHelper: PermissionsHelperType {
8+
func requestMicrophonePermission() async -> Bool {
9+
await withCheckedContinuation { continuation in
10+
AVCaptureDevice.requestAccess(for: .audio) { granted in
11+
continuation.resume(returning: granted)
12+
}
13+
}
14+
}
15+
16+
func requestScreenRecordingPermission() async -> Bool {
17+
do {
18+
let _ = try await SCShareableContent.current
19+
return true
20+
} catch {
21+
return false
22+
}
23+
}
24+
25+
func requestNotificationPermission() async -> Bool {
26+
do {
27+
let center = UNUserNotificationCenter.current()
28+
let granted = try await center.requestAuthorization(options: [.alert, .sound, .badge])
29+
return granted
30+
} catch {
31+
return false
32+
}
33+
}
34+
35+
func checkMicrophonePermissionStatus() -> AVAuthorizationStatus {
36+
AVCaptureDevice.authorizationStatus(for: .audio)
37+
}
38+
39+
func checkNotificationPermissionStatus() async -> Bool {
40+
await withCheckedContinuation { continuation in
41+
UNUserNotificationCenter.current().getNotificationSettings { settings in
42+
continuation.resume(returning: settings.authorizationStatus == .authorized)
43+
}
44+
}
45+
}
46+
47+
func checkScreenRecordingPermission() -> Bool {
48+
if #available(macOS 11.0, *) {
49+
return CGPreflightScreenCaptureAccess()
50+
} else {
51+
return true
52+
}
53+
}
54+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Foundation
2+
import AVFoundation
3+
#if MOCKING
4+
import Mockable
5+
#endif
6+
7+
#if MOCKING
8+
@Mockable
9+
#endif
10+
@MainActor
11+
protocol PermissionsHelperType: AnyObject {
12+
func requestMicrophonePermission() async -> Bool
13+
func requestScreenRecordingPermission() async -> Bool
14+
func requestNotificationPermission() async -> Bool
15+
func checkMicrophonePermissionStatus() -> AVAuthorizationStatus
16+
func checkNotificationPermissionStatus() async -> Bool
17+
func checkScreenRecordingPermission() -> Bool
18+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import SwiftUI
2+
import AppKit
3+
4+
extension MenuBarPanelManager: OnboardingDelegate {
5+
func onboardingDidComplete() {
6+
Task {
7+
await transitionFromOnboardingToMain()
8+
}
9+
}
10+
11+
private func transitionFromOnboardingToMain() async {
12+
guard let currentPanel = panel else { return }
13+
14+
await slideOutCurrentPanel(currentPanel)
15+
await createAndShowMainPanel()
16+
}
17+
18+
private func slideOutCurrentPanel(_ currentPanel: SlidingPanel) async {
19+
await withCheckedContinuation { continuation in
20+
PanelAnimator.slideOut(panel: currentPanel) { [weak self] in
21+
self?.panel = nil
22+
self?.isVisible = false
23+
continuation.resume()
24+
}
25+
}
26+
}
27+
28+
private func createAndShowMainPanel() async {
29+
panel = createMainPanel()
30+
guard let newPanel = panel else { return }
31+
32+
positionPanel(newPanel)
33+
34+
await withCheckedContinuation { continuation in
35+
PanelAnimator.slideIn(panel: newPanel) { [weak self] in
36+
self?.isVisible = true
37+
continuation.resume()
38+
}
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)