Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions Recap.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,16 @@
A7C35B1B2E3DFE1D00F9261F /* Exceptions for "Recap" folder in "RecapTests" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Audio/Models/AudioProcess.swift,
Audio/Models/AudioProcessGroup.swift,
Audio/Processing/Detection/AudioProcessControllerType.swift,
Components/Buttons/PillButton.swift,
Components/Cards/ActionableWarningCard.swift,
DataModels/RecapDataModel.xcdatamodeld,
"Helpers/Colors/Color+Extension.swift",
Helpers/UIConstants.swift,
Helpers/Constants/AppConstants.swift,
Helpers/Constants/UIConstants.swift,
Helpers/MeetingDetection/MeetingPatternMatcher.swift,
Repositories/Models/LLMProvider.swift,
Repositories/Models/RecordingInfo.swift,
Repositories/Models/UserPreferencesInfo.swift,
Expand All @@ -54,6 +61,12 @@
Repositories/UserPreferences/UserPreferencesRepositoryType.swift,
Services/CoreData/CoreDataManagerType.swift,
Services/LLM/Core/LLMError.swift,
Services/MeetingDetection/Core/MeetingDetectionService.swift,
Services/MeetingDetection/Core/MeetingDetectionServiceType.swift,
Services/MeetingDetection/Detectors/GoogleMeetDetector.swift,
Services/MeetingDetection/Detectors/MeetingDetectorType.swift,
Services/MeetingDetection/Detectors/TeamsMeetingDetector.swift,
Services/MeetingDetection/Detectors/ZoomMeetingDetector.swift,
Services/Processing/Models/ProcessingError.swift,
Services/Processing/Models/ProcessingResult.swift,
Services/Processing/Models/ProcessingState.swift,
Expand All @@ -65,10 +78,16 @@
Services/Summarization/Models/SummarizationResult.swift,
Services/Summarization/SummarizationServiceType.swift,
Services/Transcription/TranscriptionServiceType.swift,
Views/Summary/Components/ProcessingProgressBar.swift,
Views/Summary/Components/ProcessingStatesCard.swift,
Views/Summary/ViewModel/SummaryViewModel.swift,
Views/Summary/ViewModel/SummaryViewModelType.swift,
Services/Warnings/WarningManagerType.swift,
UseCases/Settings/Components/MeetingDetection/MeetingDetectionView.swift,
UseCases/Settings/Components/Reusable/CustomToggle.swift,
UseCases/Settings/Components/SettingsCard.swift,
UseCases/Settings/ViewModels/MeetingDetection/MeetingDetectionSettingsViewModel.swift,
UseCases/Settings/ViewModels/MeetingDetection/MeetingDetectionSettingsViewModelType.swift,
UseCases/Summary/Components/ProcessingProgressBar.swift,
UseCases/Summary/Components/ProcessingStatesCard.swift,
UseCases/Summary/ViewModel/SummaryViewModel.swift,
UseCases/Summary/ViewModel/SummaryViewModelType.swift,
);
target = A721065F2E30165B0073C515 /* RecapTests */;
};
Expand Down
Binary file removed Recap/Assets.xcassets/AppIcon.appiconset/1024.png
Binary file not shown.
Binary file removed Recap/Assets.xcassets/AppIcon.appiconset/128.png
Binary file not shown.
Binary file removed Recap/Assets.xcassets/AppIcon.appiconset/16.png
Binary file not shown.
Binary file removed Recap/Assets.xcassets/AppIcon.appiconset/256.png
Binary file not shown.
Binary file removed Recap/Assets.xcassets/AppIcon.appiconset/32.png
Binary file not shown.
Binary file removed Recap/Assets.xcassets/AppIcon.appiconset/512.png
Binary file not shown.
Binary file removed Recap/Assets.xcassets/AppIcon.appiconset/64.png
Binary file not shown.
26 changes: 16 additions & 10 deletions Recap/Assets.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
@@ -1,61 +1,67 @@
{
"images" : [
{
"filename" : "16.png",
"filename" : "appstore1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "mac16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "32.png",
"filename" : "mac32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "32.png",
"filename" : "mac32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "64.png",
"filename" : "mac64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "128.png",
"filename" : "mac128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "256.png",
"filename" : "mac256.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "256.png",
"filename" : "mac256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "512.png",
"filename" : "mac512.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "512.png",
"filename" : "mac512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "1024.png",
"filename" : "mac1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Recap/Assets.xcassets/AppIcon.appiconset/mac16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Recap/Audio/Capture/MicrophoneCapture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import Combine
import OSLog

final class MicrophoneCapture: MicrophoneCaptureType {
let logger = Logger(subsystem: "com.recap.audio", category: String(describing: MicrophoneCapture.self))
let logger = Logger(subsystem: AppConstants.Logging.subsystem, category: String(describing: MicrophoneCapture.self))

var audioEngine: AVAudioEngine?
var audioFile: AVAudioFile?
Expand Down
4 changes: 2 additions & 2 deletions Recap/Audio/Capture/Tap/ProcessTap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class ProcessTap: ObservableObject {
init(process: AudioProcess, muteWhenRunning: Bool = false) {
self.process = process
self.muteWhenRunning = muteWhenRunning
self.logger = Logger(subsystem: "com.recap.audio", category: "\(String(describing: ProcessTap.self))(\(process.name))")
self.logger = Logger(subsystem: AppConstants.Logging.subsystem, category: "\(String(describing: ProcessTap.self))(\(process.name))")
}

@ObservationIgnored
Expand Down Expand Up @@ -184,7 +184,7 @@ final class ProcessTapRecorder: ObservableObject {
self.process = tap.process
self.fileURL = fileURL
self._tap = tap
self.logger = Logger(subsystem: "com.recap.audio", category: "\(String(describing: ProcessTapRecorder.self))(\(fileURL.lastPathComponent))")
self.logger = Logger(subsystem: AppConstants.Logging.subsystem, category: "\(String(describing: ProcessTapRecorder.self))(\(fileURL.lastPathComponent))")
}

private var tap: ProcessTap {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import AudioToolbox
import OSLog

final class AudioRecordingCoordinator: AudioRecordingCoordinatorType {
private let logger = Logger(subsystem: "com.recap.audio", category: String(describing: AudioRecordingCoordinator.self))
private let logger = Logger(subsystem: AppConstants.Logging.subsystem, category: String(describing: AudioRecordingCoordinator.self))

private let configuration: RecordingConfiguration
private let microphoneCapture: MicrophoneCapture?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Combine

@MainActor
final class AudioProcessController: AudioProcessControllerType {
private let logger = Logger(subsystem: "com.recap.audio", category: String(describing: AudioProcessController.self))
private let logger = Logger(subsystem: AppConstants.Logging.subsystem, category: String(describing: AudioProcessController.self))

private let detectionService: AudioProcessDetectionServiceType
private var cancellables = Set<AnyCancellable>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import Foundation
import Combine
#if MOCKING
import Mockable
#endif

#if MOCKING
@Mockable
#endif
protocol AudioProcessControllerType: ObservableObject {
var processes: [AudioProcess] { get }
var processGroups: [AudioProcessGroup] { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ protocol AudioProcessDetectionServiceType {
}

final class AudioProcessDetectionService: AudioProcessDetectionServiceType {
private let logger = Logger(subsystem: "com.recap.audio", category: String(describing: AudioProcessDetectionService.self))
private let logger = Logger(subsystem: AppConstants.Logging.subsystem, category: String(describing: AudioProcessDetectionService.self))

func detectActiveProcesses(from apps: [NSRunningApplication]) throws -> [AudioProcess] {
let objectIdentifiers = try AudioObjectID.readProcessList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ protocol MeetingAppDetecting {
}

final class MeetingAppDetectionService: MeetingAppDetecting {
private var processController: AudioProcessControllerType?
private var processController: (any AudioProcessControllerType)?

init(processController: AudioProcessControllerType?) {
init(processController: (any AudioProcessControllerType)?) {
self.processController = processController
}

func setProcessController(_ controller: AudioProcessControllerType) {
func setProcessController(_ controller: any AudioProcessControllerType) {
self.processController = controller
}

Expand All @@ -25,4 +25,4 @@ final class MeetingAppDetectionService: MeetingAppDetecting {
guard let processController = processController else { return [] }
return await MainActor.run { processController.processes }
}
}
}
2 changes: 1 addition & 1 deletion Recap/Audio/Processing/RecordingCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import AVFoundation
import OSLog

final class RecordingCoordinator: ObservableObject {
private let logger = Logger(subsystem: "com.recap.audio", category: String(describing: RecordingCoordinator.self))
private let logger = Logger(subsystem: AppConstants.Logging.subsystem, category: String(describing: RecordingCoordinator.self))

private(set) var state: RecordingState = .idle
private(set) var detectedMeetingApps: [AudioProcess] = []
Expand Down
15 changes: 0 additions & 15 deletions Recap/Audio/Processing/RecordingCoordinatorFactory.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ protocol RecordingSessionManaging {
}

final class RecordingSessionManager: RecordingSessionManaging {
private let logger = Logger(subsystem: "com.recap.audio", category: String(describing: RecordingSessionManager.self))
private let logger = Logger(subsystem: AppConstants.Logging.subsystem, category: String(describing: RecordingSessionManager.self))
private let microphoneCapture: MicrophoneCapture

init(microphoneCapture: MicrophoneCapture) {
Expand Down
133 changes: 133 additions & 0 deletions Recap/Components/Cards/ActionableWarningCard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import SwiftUI

struct ActionableWarningCard: View {
let warning: WarningItem
let containerWidth: CGFloat
let buttonText: String?
let buttonAction: (() -> Void)?
let footerText: String?

init(
warning: WarningItem,
containerWidth: CGFloat,
buttonText: String? = nil,
buttonAction: (() -> Void)? = nil,
footerText: String? = nil
) {
self.warning = warning
self.containerWidth = containerWidth
self.buttonText = buttonText
self.buttonAction = buttonAction
self.footerText = footerText
}

var body: some View {
let severityColor = Color(hex: warning.severity.color)

let cardBackground = LinearGradient(
gradient: Gradient(stops: [
.init(color: severityColor.opacity(0.1), location: 0),
.init(color: severityColor.opacity(0.05), location: 1)
]),
startPoint: .top,
endPoint: .bottom
)

let cardBorder = LinearGradient(
gradient: Gradient(stops: [
.init(color: severityColor.opacity(0.3), location: 0),
.init(color: severityColor.opacity(0.2), location: 1)
]),
startPoint: .top,
endPoint: .bottom
)

VStack(alignment: .leading, spacing: 12) {
HStack(spacing: 12) {
Image(systemName: warning.icon)
.font(.system(size: 16, weight: .bold))
.foregroundColor(severityColor)

Text(warning.title)
.font(UIConstants.Typography.cardTitle)
.foregroundColor(UIConstants.Colors.textPrimary)

Spacer()
}

VStack(alignment: .leading, spacing: 8) {
Text(warning.message)
.font(.system(size: 10, weight: .regular))
.foregroundColor(UIConstants.Colors.textSecondary)
.multilineTextAlignment(.leading)

if let footerText = footerText {
Text(footerText)
.font(.system(size: 9))
.foregroundColor(UIConstants.Colors.textSecondary)
.multilineTextAlignment(.leading)
.fixedSize(horizontal: false, vertical: true)
}
}

if let buttonText = buttonText, let buttonAction = buttonAction {
HStack {
PillButton(
text: buttonText,
icon: "gear"
) {
buttonAction()
}
Spacer()
}
}
}
.padding(.horizontal, UIConstants.Spacing.cardPadding + 4)
.padding(.vertical, UIConstants.Spacing.cardPadding)
.frame(width: UIConstants.Layout.fullCardWidth(containerWidth: containerWidth))
.background(
RoundedRectangle(cornerRadius: UIConstants.Sizing.cornerRadius)
.fill(cardBackground)
.overlay(
RoundedRectangle(cornerRadius: UIConstants.Sizing.cornerRadius)
.stroke(cardBorder, lineWidth: UIConstants.Sizing.borderWidth)
)
)
}
}

#Preview {
GeometryReader { geometry in
VStack(spacing: 16) {
ActionableWarningCard(
warning: WarningItem(
id: "screen-recording",
title: "Permission Required",
message: "Screen Recording permission needed to detect meeting windows",
icon: "exclamationmark.shield",
severity: .warning
),
containerWidth: geometry.size.width,
buttonText: "Open System Settings",
buttonAction: {
print("Button tapped")
},
footerText: "This permission allows Recap to read window titles only. No screen content is captured or recorded."
)

ActionableWarningCard(
warning: WarningItem(
id: "network",
title: "Connection Issue",
message: "Unable to connect to the service. Check your network connection and try again.",
icon: "network.slash",
severity: .error
),
containerWidth: geometry.size.width
)
}
.padding(20)
}
.frame(width: 500, height: 400)
.background(UIConstants.Gradients.backgroundGradient)
}
7 changes: 7 additions & 0 deletions Recap/Helpers/Constants/AppConstants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

struct AppConstants {
struct Logging {
static let subsystem = "com.recap.audio"
}
}
Loading