cleanup
This commit is contained in:
@@ -431,13 +431,14 @@
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Gaze/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Gaze;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 0.4.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.Gaze;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -467,13 +468,14 @@
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
INFOPLIST_FILE = Gaze/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Gaze;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 0.4.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.mikefreno.Gaze;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
import Foundation
|
||||
|
||||
/// Thread-safe configuration holder for eye tracking thresholds.
|
||||
enum EyeTrackingConstants {
|
||||
/// All properties are Sendable constants, safe for use in any concurrency context.
|
||||
enum EyeTrackingConstants: Sendable {
|
||||
// MARK: - Logging
|
||||
/// Interval between log messages in seconds
|
||||
static let logInterval: TimeInterval = 0.5
|
||||
|
||||
@@ -31,8 +31,7 @@ struct GazeApp: App {
|
||||
}
|
||||
} else {
|
||||
OnboardingContainerView(settingsManager: settingsManager)
|
||||
.onChange(of: settingsManager.settings.hasCompletedOnboarding) {
|
||||
completed in
|
||||
.onChange(of: settingsManager.settings.hasCompletedOnboarding) { _, completed in
|
||||
if completed {
|
||||
closeAllWindows()
|
||||
appDelegate.onboardingCompleted()
|
||||
|
||||
@@ -68,6 +68,24 @@ class EyeTrackingService: NSObject, ObservableObject {
|
||||
var debugRightPupilRatio: Double?
|
||||
var debugYaw: Double?
|
||||
var debugPitch: Double?
|
||||
|
||||
nonisolated init(
|
||||
faceDetected: Bool = false,
|
||||
isEyesClosed: Bool = false,
|
||||
userLookingAtScreen: Bool = true,
|
||||
debugLeftPupilRatio: Double? = nil,
|
||||
debugRightPupilRatio: Double? = nil,
|
||||
debugYaw: Double? = nil,
|
||||
debugPitch: Double? = nil
|
||||
) {
|
||||
self.faceDetected = faceDetected
|
||||
self.isEyesClosed = isEyesClosed
|
||||
self.userLookingAtScreen = userLookingAtScreen
|
||||
self.debugLeftPupilRatio = debugLeftPupilRatio
|
||||
self.debugRightPupilRatio = debugRightPupilRatio
|
||||
self.debugYaw = debugYaw
|
||||
self.debugPitch = debugPitch
|
||||
}
|
||||
}
|
||||
|
||||
func startEyeTracking() async throws {
|
||||
@@ -277,6 +295,20 @@ class EyeTrackingService: NSObject, ObservableObject {
|
||||
var rightPupilRatio: Double?
|
||||
var yaw: Double?
|
||||
var pitch: Double?
|
||||
|
||||
nonisolated init(
|
||||
lookingAway: Bool = false,
|
||||
leftPupilRatio: Double? = nil,
|
||||
rightPupilRatio: Double? = nil,
|
||||
yaw: Double? = nil,
|
||||
pitch: Double? = nil
|
||||
) {
|
||||
self.lookingAway = lookingAway
|
||||
self.leftPupilRatio = leftPupilRatio
|
||||
self.rightPupilRatio = rightPupilRatio
|
||||
self.yaw = yaw
|
||||
self.pitch = pitch
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-isolated gaze direction detection
|
||||
|
||||
@@ -71,16 +71,23 @@ final class FullscreenDetectionService: ObservableObject {
|
||||
private let permissionManager: ScreenCapturePermissionManaging
|
||||
private let environmentProvider: FullscreenEnvironmentProviding
|
||||
|
||||
// This initializer is only for use within main actor contexts
|
||||
init(
|
||||
permissionManager: ScreenCapturePermissionManaging = ScreenCapturePermissionManager.shared,
|
||||
environmentProvider: FullscreenEnvironmentProviding = SystemFullscreenEnvironmentProvider()
|
||||
permissionManager: ScreenCapturePermissionManaging,
|
||||
environmentProvider: FullscreenEnvironmentProviding
|
||||
) {
|
||||
self.permissionManager = permissionManager
|
||||
self.environmentProvider = environmentProvider
|
||||
setupObservers()
|
||||
}
|
||||
|
||||
/// Convenience initializer using default services
|
||||
convenience init() {
|
||||
self.init(
|
||||
permissionManager: ScreenCapturePermissionManager.shared,
|
||||
environmentProvider: SystemFullscreenEnvironmentProvider()
|
||||
)
|
||||
}
|
||||
|
||||
// Factory method to safely create instances from non-main actor contexts
|
||||
static func create(
|
||||
permissionManager: ScreenCapturePermissionManaging? = nil,
|
||||
@@ -109,8 +116,10 @@ final class FullscreenDetectionService: ObservableObject {
|
||||
object: workspace,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.checkFullscreenState()
|
||||
}
|
||||
}
|
||||
observers.append(spaceObserver)
|
||||
|
||||
let transitionObserver = notificationCenter.addObserver(
|
||||
@@ -118,8 +127,10 @@ final class FullscreenDetectionService: ObservableObject {
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.checkFullscreenState()
|
||||
}
|
||||
}
|
||||
observers.append(transitionObserver)
|
||||
|
||||
let fullscreenObserver = notificationCenter.addObserver(
|
||||
@@ -127,8 +138,10 @@ final class FullscreenDetectionService: ObservableObject {
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.checkFullscreenState()
|
||||
}
|
||||
}
|
||||
observers.append(fullscreenObserver)
|
||||
|
||||
let exitFullscreenObserver = notificationCenter.addObserver(
|
||||
@@ -136,8 +149,10 @@ final class FullscreenDetectionService: ObservableObject {
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.checkFullscreenState()
|
||||
}
|
||||
}
|
||||
observers.append(exitFullscreenObserver)
|
||||
|
||||
frontmostAppObserver = NotificationCenter.default.publisher(
|
||||
|
||||
@@ -33,8 +33,9 @@ class IdleMonitoringService: ObservableObject {
|
||||
|
||||
private func startMonitoring() {
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
Task { @MainActor in
|
||||
self?.checkIdleState()
|
||||
self.checkIdleState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,13 +38,13 @@ final class PupilCalibration: @unchecked Sendable {
|
||||
private var thresholdsLeft: [Int] = []
|
||||
private var thresholdsRight: [Int] = []
|
||||
|
||||
var isComplete: Bool {
|
||||
nonisolated var isComplete: Bool {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return thresholdsLeft.count >= targetFrames && thresholdsRight.count >= targetFrames
|
||||
}
|
||||
|
||||
func threshold(forSide side: Int) -> Int {
|
||||
nonisolated func threshold(forSide side: Int) -> Int {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
let thresholds = side == 0 ? thresholdsLeft : thresholdsRight
|
||||
@@ -52,7 +52,7 @@ final class PupilCalibration: @unchecked Sendable {
|
||||
return thresholds.reduce(0, +) / thresholds.count
|
||||
}
|
||||
|
||||
func evaluate(eyeData: UnsafePointer<UInt8>, width: Int, height: Int, side: Int) {
|
||||
nonisolated func evaluate(eyeData: UnsafePointer<UInt8>, width: Int, height: Int, side: Int) {
|
||||
let bestThreshold = findBestThreshold(eyeData: eyeData, width: width, height: height)
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
@@ -63,7 +63,7 @@ final class PupilCalibration: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
private func findBestThreshold(eyeData: UnsafePointer<UInt8>, width: Int, height: Int) -> Int {
|
||||
private nonisolated func findBestThreshold(eyeData: UnsafePointer<UInt8>, width: Int, height: Int) -> Int {
|
||||
let averageIrisSize = 0.48
|
||||
var bestThreshold = 50
|
||||
var bestDiff = Double.greatestFiniteMagnitude
|
||||
@@ -91,7 +91,7 @@ final class PupilCalibration: @unchecked Sendable {
|
||||
return bestThreshold
|
||||
}
|
||||
|
||||
private static func irisSize(data: UnsafePointer<UInt8>, width: Int, height: Int) -> Double {
|
||||
private nonisolated static func irisSize(data: UnsafePointer<UInt8>, width: Int, height: Int) -> Double {
|
||||
let margin = 5
|
||||
guard width > margin * 2, height > margin * 2 else { return 0 }
|
||||
|
||||
@@ -112,7 +112,7 @@ final class PupilCalibration: @unchecked Sendable {
|
||||
return totalCount > 0 ? Double(blackCount) / Double(totalCount) : 0
|
||||
}
|
||||
|
||||
func reset() {
|
||||
nonisolated func reset() {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
thresholdsLeft.removeAll()
|
||||
@@ -143,46 +143,46 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
// MARK: - Configuration
|
||||
|
||||
static var enableDebugImageSaving = false
|
||||
static var enablePerformanceLogging = false
|
||||
static var frameSkipCount = 10 // Process every Nth frame
|
||||
nonisolated(unsafe) static var enableDebugImageSaving = false
|
||||
nonisolated(unsafe) static var enablePerformanceLogging = false
|
||||
nonisolated(unsafe) static var frameSkipCount = 10 // Process every Nth frame
|
||||
|
||||
// MARK: - State (protected by lock)
|
||||
|
||||
private static var _debugImageCounter = 0
|
||||
private static var _frameCounter = 0
|
||||
private static var _lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) = (
|
||||
private nonisolated(unsafe) static var _debugImageCounter = 0
|
||||
private nonisolated(unsafe) static var _frameCounter = 0
|
||||
private nonisolated(unsafe) static var _lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) = (
|
||||
nil, nil
|
||||
)
|
||||
private static var _metrics = PupilDetectorMetrics()
|
||||
private nonisolated(unsafe) static var _metrics = PupilDetectorMetrics()
|
||||
|
||||
static let calibration = PupilCalibration()
|
||||
nonisolated(unsafe) static let calibration = PupilCalibration()
|
||||
|
||||
// MARK: - Convenience Properties
|
||||
|
||||
private static var debugImageCounter: Int {
|
||||
private nonisolated static var debugImageCounter: Int {
|
||||
get { _debugImageCounter }
|
||||
set { _debugImageCounter = newValue }
|
||||
}
|
||||
|
||||
private static var frameCounter: Int {
|
||||
private nonisolated static var frameCounter: Int {
|
||||
get { _frameCounter }
|
||||
set { _frameCounter = newValue }
|
||||
}
|
||||
|
||||
private static var lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) {
|
||||
private nonisolated static var lastPupilPositions: (left: PupilPosition?, right: PupilPosition?) {
|
||||
get { _lastPupilPositions }
|
||||
set { _lastPupilPositions = newValue }
|
||||
}
|
||||
|
||||
private static var metrics: PupilDetectorMetrics {
|
||||
private nonisolated static var metrics: PupilDetectorMetrics {
|
||||
get { _metrics }
|
||||
set { _metrics = newValue }
|
||||
}
|
||||
|
||||
// MARK: - Precomputed Tables
|
||||
|
||||
private static let spatialWeightsLUT: [[Float]] = {
|
||||
private nonisolated(unsafe) static let spatialWeightsLUT: [[Float]] = {
|
||||
let d = 10
|
||||
let radius = d / 2
|
||||
let sigmaSpace: Float = 15.0
|
||||
@@ -197,7 +197,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
return weights
|
||||
}()
|
||||
|
||||
private static let colorWeightsLUT: [Float] = {
|
||||
private nonisolated(unsafe) static let colorWeightsLUT: [Float] = {
|
||||
let sigmaColor: Float = 15.0
|
||||
var lut = [Float](repeating: 0, count: 256)
|
||||
for diff in 0..<256 {
|
||||
@@ -209,12 +209,12 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
// MARK: - Reusable Buffers
|
||||
|
||||
private static var grayscaleBuffer: UnsafeMutablePointer<UInt8>?
|
||||
private static var grayscaleBufferSize = 0
|
||||
private static var eyeBuffer: UnsafeMutablePointer<UInt8>?
|
||||
private static var eyeBufferSize = 0
|
||||
private static var tempBuffer: UnsafeMutablePointer<UInt8>?
|
||||
private static var tempBufferSize = 0
|
||||
private nonisolated(unsafe) static var grayscaleBuffer: UnsafeMutablePointer<UInt8>?
|
||||
private nonisolated(unsafe) static var grayscaleBufferSize = 0
|
||||
private nonisolated(unsafe) static var eyeBuffer: UnsafeMutablePointer<UInt8>?
|
||||
private nonisolated(unsafe) static var eyeBufferSize = 0
|
||||
private nonisolated(unsafe) static var tempBuffer: UnsafeMutablePointer<UInt8>?
|
||||
private nonisolated(unsafe) static var tempBufferSize = 0
|
||||
|
||||
// MARK: - Public API
|
||||
|
||||
@@ -369,7 +369,9 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
// MARK: - Buffer Management
|
||||
|
||||
private static func ensureBufferCapacity(frameSize: Int, eyeSize: Int) {
|
||||
// MARK: - Buffer Management
|
||||
|
||||
private nonisolated static func ensureBufferCapacity(frameSize: Int, eyeSize: Int) {
|
||||
if grayscaleBufferSize < frameSize {
|
||||
grayscaleBuffer?.deallocate()
|
||||
grayscaleBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: frameSize)
|
||||
@@ -388,7 +390,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
// MARK: - Optimized Grayscale Conversion (vImage)
|
||||
|
||||
private static func extractGrayscaleDataOptimized(
|
||||
private nonisolated static func extractGrayscaleDataOptimized(
|
||||
from pixelBuffer: CVPixelBuffer,
|
||||
to output: UnsafeMutablePointer<UInt8>,
|
||||
width: Int,
|
||||
@@ -476,7 +478,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
// MARK: - Optimized Eye Isolation
|
||||
|
||||
private static func isolateEyeWithMaskOptimized(
|
||||
private nonisolated static func isolateEyeWithMaskOptimized(
|
||||
frameData: UnsafePointer<UInt8>,
|
||||
frameWidth: Int,
|
||||
frameHeight: Int,
|
||||
@@ -526,7 +528,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
private static func pointInPolygonFast(
|
||||
private nonisolated static func pointInPolygonFast(
|
||||
px: Float, py: Float, edges: [(x1: Float, y1: Float, x2: Float, y2: Float)]
|
||||
) -> Bool {
|
||||
var inside = false
|
||||
@@ -542,7 +544,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
// MARK: - Optimized Image Processing
|
||||
|
||||
static func imageProcessingOptimized(
|
||||
nonisolated static func imageProcessingOptimized(
|
||||
input: UnsafePointer<UInt8>,
|
||||
output: UnsafeMutablePointer<UInt8>,
|
||||
width: Int,
|
||||
@@ -569,7 +571,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
private static func gaussianBlurOptimized(
|
||||
private nonisolated static func gaussianBlurOptimized(
|
||||
input: UnsafePointer<UInt8>,
|
||||
output: UnsafeMutablePointer<UInt8>,
|
||||
width: Int,
|
||||
@@ -607,7 +609,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
)
|
||||
}
|
||||
|
||||
private static func erodeOptimized(
|
||||
private nonisolated static func erodeOptimized(
|
||||
input: UnsafePointer<UInt8>,
|
||||
output: UnsafeMutablePointer<UInt8>,
|
||||
width: Int,
|
||||
@@ -668,7 +670,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
/// Optimized centroid-of-dark-pixels approach - much faster than union-find
|
||||
/// Returns the centroid of the largest dark region
|
||||
private static func findPupilFromContoursOptimized(
|
||||
private nonisolated static func findPupilFromContoursOptimized(
|
||||
data: UnsafePointer<UInt8>,
|
||||
width: Int,
|
||||
height: Int
|
||||
@@ -722,7 +724,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
private static func landmarksToPixelCoordinates(
|
||||
private nonisolated static func landmarksToPixelCoordinates(
|
||||
landmarks: VNFaceLandmarkRegion2D,
|
||||
faceBoundingBox: CGRect,
|
||||
imageSize: CGSize
|
||||
@@ -736,7 +738,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
private static func createEyeRegion(from points: [CGPoint], imageSize: CGSize) -> EyeRegion? {
|
||||
private nonisolated static func createEyeRegion(from points: [CGPoint], imageSize: CGSize) -> EyeRegion? {
|
||||
guard !points.isEmpty else { return nil }
|
||||
|
||||
let margin: CGFloat = 5
|
||||
@@ -777,7 +779,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
|
||||
// MARK: - Debug Helpers
|
||||
|
||||
private static func saveDebugImage(
|
||||
private nonisolated static func saveDebugImage(
|
||||
data: UnsafePointer<UInt8>, width: Int, height: Int, name: String
|
||||
) {
|
||||
guard let cgImage = createCGImage(from: data, width: width, height: height) else { return }
|
||||
@@ -793,7 +795,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
print("💾 Saved debug image: \(url.path)")
|
||||
}
|
||||
|
||||
private static func createCGImage(from data: UnsafePointer<UInt8>, width: Int, height: Int)
|
||||
private nonisolated static func createCGImage(from data: UnsafePointer<UInt8>, width: Int, height: Int)
|
||||
-> CGImage?
|
||||
{
|
||||
let mutableData = UnsafeMutablePointer<UInt8>.allocate(capacity: width * height)
|
||||
@@ -817,7 +819,7 @@ final class PupilDetector: @unchecked Sendable {
|
||||
}
|
||||
|
||||
/// Clean up allocated buffers (call on app termination if needed)
|
||||
static func cleanup() {
|
||||
nonisolated static func cleanup() {
|
||||
grayscaleBuffer?.deallocate()
|
||||
grayscaleBuffer = nil
|
||||
grayscaleBufferSize = 0
|
||||
|
||||
@@ -111,7 +111,12 @@ final class ServiceContainer {
|
||||
}
|
||||
|
||||
/// Creates a new container configured for testing with default mock settings
|
||||
static func forTesting(settings: AppSettings = .defaults) -> ServiceContainer {
|
||||
static func forTesting() -> ServiceContainer {
|
||||
forTesting(settings: AppSettings())
|
||||
}
|
||||
|
||||
/// Creates a new container configured for testing with custom settings
|
||||
static func forTesting(settings: AppSettings) -> ServiceContainer {
|
||||
let mockSettings = MockSettingsManager(settings: settings)
|
||||
return ServiceContainer(settingsManager: mockSettings)
|
||||
}
|
||||
@@ -138,7 +143,11 @@ final class MockSettingsManager: SettingsProviding {
|
||||
.posture: \.postureTimer,
|
||||
]
|
||||
|
||||
init(settings: AppSettings = .defaults) {
|
||||
convenience init() {
|
||||
self.init(settings: AppSettings())
|
||||
}
|
||||
|
||||
init(settings: AppSettings) {
|
||||
self.settings = settings
|
||||
self._settingsSubject = CurrentValueSubject(settings)
|
||||
}
|
||||
|
||||
@@ -32,10 +32,21 @@ class TimerEngine: ObservableObject {
|
||||
// Logging manager
|
||||
private let logger = LoggingManager.shared.timerLogger
|
||||
|
||||
convenience init(
|
||||
settingsManager: any SettingsProviding,
|
||||
enforceModeService: EnforceModeService? = nil
|
||||
) {
|
||||
self.init(
|
||||
settingsManager: settingsManager,
|
||||
enforceModeService: enforceModeService,
|
||||
timeProvider: SystemTimeProvider()
|
||||
)
|
||||
}
|
||||
|
||||
init(
|
||||
settingsManager: any SettingsProviding,
|
||||
enforceModeService: EnforceModeService? = nil,
|
||||
timeProvider: TimeProviding = SystemTimeProvider()
|
||||
enforceModeService: EnforceModeService?,
|
||||
timeProvider: TimeProviding
|
||||
) {
|
||||
self.settingsProvider = settingsManager
|
||||
self.enforceModeService = enforceModeService ?? EnforceModeService.shared
|
||||
|
||||
@@ -75,8 +75,9 @@ class UsageTrackingService: ObservableObject {
|
||||
|
||||
private func startTracking() {
|
||||
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
Task { @MainActor in
|
||||
self?.tick()
|
||||
self.tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,10 @@ final class OnboardingWindowPresenter {
|
||||
object: window,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.windowController = nil
|
||||
self?.removeCloseObserver()
|
||||
}
|
||||
NotificationCenter.default.post(name: Notification.Name("OnboardingWindowDidClose"), object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ struct UserTimerEditSheet: View {
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.onChange(of: type) { newType in
|
||||
.onChange(of: type) { _, newType in
|
||||
if newType == .subtle {
|
||||
timeOnScreen = 3
|
||||
} else if timeOnScreen == 3 {
|
||||
|
||||
@@ -42,6 +42,7 @@ final class ServiceContainerTests: XCTestCase {
|
||||
let mockSettings = EnhancedMockSettingsManager(settings: .shortIntervals)
|
||||
let customEngine = TimerEngine(
|
||||
settingsManager: mockSettings,
|
||||
enforceModeService: nil,
|
||||
timeProvider: MockTimeProvider()
|
||||
)
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ final class TimerEngineTests: XCTestCase {
|
||||
let timeProvider = MockTimeProvider()
|
||||
let engine = TimerEngine(
|
||||
settingsManager: testEnv.settingsManager,
|
||||
enforceModeService: nil,
|
||||
timeProvider: timeProvider
|
||||
)
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ final class TimerEngineTestabilityTests: XCTestCase {
|
||||
let timeProvider = MockTimeProvider()
|
||||
let timerEngine = TimerEngine(
|
||||
settingsManager: testEnv.settingsManager,
|
||||
enforceModeService: nil,
|
||||
timeProvider: timeProvider
|
||||
)
|
||||
|
||||
@@ -59,6 +60,7 @@ final class TimerEngineTestabilityTests: XCTestCase {
|
||||
let timeProvider = MockTimeProvider(startTime: Date())
|
||||
let timerEngine = TimerEngine(
|
||||
settingsManager: testEnv.settingsManager,
|
||||
enforceModeService: nil,
|
||||
timeProvider: timeProvider
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user