general: working through bugs and maintainability
This commit is contained in:
@@ -10,20 +10,24 @@ import Combine
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
struct FullscreenWindowDescriptor: Equatable {
|
||||
let ownerPID: pid_t
|
||||
let layer: Int
|
||||
let bounds: CGRect
|
||||
public struct FullscreenWindowDescriptor: Equatable {
|
||||
public let ownerPID: pid_t
|
||||
public let layer: Int
|
||||
public let bounds: CGRect
|
||||
|
||||
public init(ownerPID: pid_t, layer: Int, bounds: CGRect) {
|
||||
self.ownerPID = ownerPID
|
||||
self.layer = layer
|
||||
self.bounds = bounds
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
protocol FullscreenEnvironmentProviding {
|
||||
func frontmostProcessIdentifier() -> pid_t?
|
||||
func windowDescriptors() -> [FullscreenWindowDescriptor]
|
||||
func screenFrames() -> [CGRect]
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct SystemFullscreenEnvironmentProvider: FullscreenEnvironmentProviding {
|
||||
func frontmostProcessIdentifier() -> pid_t? {
|
||||
NSWorkspace.shared.frontmostApplication?.processIdentifier
|
||||
@@ -53,13 +57,13 @@ struct SystemFullscreenEnvironmentProvider: FullscreenEnvironmentProviding {
|
||||
}
|
||||
}
|
||||
|
||||
func screenFrames() -> [CGRect] {
|
||||
public func screenFrames() -> [CGRect] {
|
||||
NSScreen.screens.map(\.frame)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class FullscreenDetectionService: ObservableObject {
|
||||
final class FullscreenDetectionService: ObservableObject {
|
||||
@Published private(set) var isFullscreenActive = false
|
||||
|
||||
private var observers: [NSObjectProtocol] = []
|
||||
@@ -68,11 +72,11 @@ class FullscreenDetectionService: ObservableObject {
|
||||
private let environmentProvider: FullscreenEnvironmentProviding
|
||||
|
||||
init(
|
||||
permissionManager: ScreenCapturePermissionManaging? = nil,
|
||||
environmentProvider: FullscreenEnvironmentProviding? = nil
|
||||
permissionManager: ScreenCapturePermissionManaging = ScreenCapturePermissionManager.shared,
|
||||
environmentProvider: FullscreenEnvironmentProviding = SystemFullscreenEnvironmentProvider()
|
||||
) {
|
||||
self.permissionManager = permissionManager ?? ScreenCapturePermissionManager.shared
|
||||
self.environmentProvider = environmentProvider ?? SystemFullscreenEnvironmentProvider()
|
||||
self.permissionManager = permissionManager
|
||||
self.environmentProvider = environmentProvider
|
||||
setupObservers()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public enum ScreenCaptureAuthorizationStatus: Equatable {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public protocol ScreenCapturePermissionManaging: AnyObject {
|
||||
protocol ScreenCapturePermissionManaging: AnyObject {
|
||||
var authorizationStatus: ScreenCaptureAuthorizationStatus { get }
|
||||
var authorizationStatusPublisher: AnyPublisher<ScreenCaptureAuthorizationStatus, Never> { get }
|
||||
|
||||
|
||||
@@ -19,6 +19,85 @@ struct VisualEffectView: NSViewRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class OnboardingWindowPresenter {
|
||||
static let shared = OnboardingWindowPresenter()
|
||||
|
||||
private weak var windowController: NSWindowController?
|
||||
private var closeObserver: NSObjectProtocol?
|
||||
|
||||
func show(settingsManager: SettingsManager) {
|
||||
if activateIfPresent() {
|
||||
return
|
||||
}
|
||||
createWindow(settingsManager: settingsManager)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func activateIfPresent() -> Bool {
|
||||
guard let window = windowController?.window else {
|
||||
windowController = nil
|
||||
return false
|
||||
}
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
return true
|
||||
}
|
||||
|
||||
func close() {
|
||||
windowController?.close()
|
||||
windowController = nil
|
||||
if let closeObserver {
|
||||
NotificationCenter.default.removeObserver(closeObserver)
|
||||
self.closeObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func createWindow(settingsManager: SettingsManager) {
|
||||
let window = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 700, height: 700),
|
||||
styleMask: [.titled, .closable, .miniaturizable, .fullSizeContentView],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
|
||||
window.identifier = WindowIdentifiers.onboarding
|
||||
window.titleVisibility = .hidden
|
||||
window.titlebarAppearsTransparent = true
|
||||
window.center()
|
||||
window.isReleasedWhenClosed = true
|
||||
window.contentView = NSHostingView(
|
||||
rootView: OnboardingContainerView(settingsManager: settingsManager)
|
||||
)
|
||||
|
||||
let controller = NSWindowController(window: window)
|
||||
controller.showWindow(nil)
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
|
||||
windowController = controller
|
||||
|
||||
closeObserver.map(NotificationCenter.default.removeObserver)
|
||||
closeObserver = NotificationCenter.default.addObserver(
|
||||
forName: NSWindow.willCloseNotification,
|
||||
object: window,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
self?.windowController = nil
|
||||
if let closeObserver = self?.closeObserver {
|
||||
NotificationCenter.default.removeObserver(closeObserver)
|
||||
}
|
||||
self?.closeObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let closeObserver {
|
||||
NotificationCenter.default.removeObserver(closeObserver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OnboardingContainerView: View {
|
||||
@ObservedObject var settingsManager: SettingsManager
|
||||
@State private var currentPage = 0
|
||||
|
||||
@@ -17,37 +17,36 @@ struct SettingsWindowView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
NavigationSplitView {
|
||||
List(SettingsSection.allCases, selection: $selectedSection) { section in
|
||||
NavigationLink(value: section) {
|
||||
Label(section.title, systemImage: section.iconName)
|
||||
ZStack {
|
||||
VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)
|
||||
.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
NavigationSplitView {
|
||||
List(SettingsSection.allCases, selection: $selectedSection) { section in
|
||||
NavigationLink(value: section) {
|
||||
Label(section.title, systemImage: section.iconName)
|
||||
}
|
||||
}
|
||||
.listStyle(.sidebar)
|
||||
} detail: {
|
||||
detailView(for: selectedSection)
|
||||
}
|
||||
.listStyle(.sidebar)
|
||||
} detail: {
|
||||
detailView(for: selectedSection)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
HStack {
|
||||
#if DEBUG
|
||||
Button("Retrigger Onboarding") {
|
||||
retriggerOnboarding()
|
||||
Divider()
|
||||
|
||||
HStack {
|
||||
Button("Retrigger Onboarding") {
|
||||
retriggerOnboarding()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.padding()
|
||||
#endif
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Close") {
|
||||
closeWindow()
|
||||
}
|
||||
.keyboardShortcut(.escape)
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
#if APPSTORE
|
||||
.frame(
|
||||
@@ -99,34 +98,16 @@ struct SettingsWindowView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func closeWindow() {
|
||||
if let window = NSApplication.shared.windows.first(where: { $0.title == "Settings" }) {
|
||||
window.close()
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private func retriggerOnboarding() {
|
||||
// Get AppDelegate reference first
|
||||
guard let appDelegate = NSApplication.shared.delegate as? AppDelegate else { return }
|
||||
OnboardingWindowPresenter.shared.close()
|
||||
SettingsWindowPresenter.shared.close()
|
||||
|
||||
// Step 1: Close any existing onboarding window
|
||||
if let onboardingWindow = NSApplication.shared.windows.first(where: {
|
||||
$0.identifier == WindowIdentifiers.onboarding
|
||||
}) {
|
||||
onboardingWindow.close()
|
||||
}
|
||||
|
||||
// Step 2: Close settings window
|
||||
closeWindow()
|
||||
|
||||
// Step 3: Reset onboarding state with a delay to ensure settings window is closed
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
self.settingsManager.settings.hasCompletedOnboarding = false
|
||||
|
||||
// Step 4: Open onboarding window with another delay to ensure state is saved
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
appDelegate.openOnboarding()
|
||||
OnboardingWindowPresenter.shared.show(settingsManager: self.settingsManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,18 +73,6 @@ struct EnforceModeSetupView: View {
|
||||
.padding()
|
||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
|
||||
|
||||
#if DEBUG
|
||||
HStack {
|
||||
Button("Debug Info") {
|
||||
showDebugView.toggle()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
|
||||
#endif
|
||||
|
||||
cameraStatusView
|
||||
|
||||
if enforceModeService.isEnforceModeEnabled {
|
||||
@@ -97,9 +85,9 @@ struct EnforceModeSetupView: View {
|
||||
if enforceModeService.isCameraActive && !isTestModeActive {
|
||||
eyeTrackingStatusView
|
||||
#if DEBUG
|
||||
if showDebugView {
|
||||
debugEyeTrackingView
|
||||
}
|
||||
if showDebugView {
|
||||
debugEyeTrackingView
|
||||
}
|
||||
#endif
|
||||
} else if enforceModeService.isEnforceModeEnabled {
|
||||
cameraPendingView
|
||||
@@ -378,17 +366,17 @@ struct EnforceModeSetupView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Face Detected: \(eyeTrackingService.faceDetected ? "Yes" : "No")")
|
||||
.font(.caption)
|
||||
|
||||
|
||||
Text("Looking at Screen: \(eyeTrackingService.userLookingAtScreen ? "Yes" : "No")")
|
||||
.font(.caption)
|
||||
|
||||
|
||||
Text("Eyes Closed: \(eyeTrackingService.isEyesClosed ? "Yes" : "No")")
|
||||
.font(.caption)
|
||||
|
||||
|
||||
if eyeTrackingService.faceDetected {
|
||||
Text("Yaw: 0.0")
|
||||
.font(.caption)
|
||||
|
||||
|
||||
Text("Roll: 0.0")
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user