mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-13 12:35:54 +00:00
Make permission monitoring dependent on views
This commit is contained in:
parent
a121d09fee
commit
41d602d6d9
3 changed files with 103 additions and 15 deletions
|
|
@ -5,6 +5,10 @@ import Foundation
|
|||
import Observation
|
||||
import OSLog
|
||||
|
||||
extension Notification.Name {
|
||||
static let permissionsUpdated = Notification.Name("sh.vibetunnel.permissionsUpdated")
|
||||
}
|
||||
|
||||
/// Types of system permissions that VibeTunnel requires
|
||||
enum SystemPermission {
|
||||
case appleScript
|
||||
|
|
@ -63,8 +67,14 @@ final class SystemPermissionManager {
|
|||
category: "SystemPermissions"
|
||||
)
|
||||
|
||||
/// Timer for monitoring permission changes
|
||||
private var monitorTimer: Timer?
|
||||
|
||||
/// Count of views that have registered for monitoring
|
||||
private var monitorRegistrationCount = 0
|
||||
|
||||
private init() {
|
||||
// No automatic monitoring - UI components will check when visible
|
||||
// No automatic monitoring - UI components will register when visible
|
||||
}
|
||||
|
||||
// MARK: - Public API
|
||||
|
|
@ -127,13 +137,67 @@ final class SystemPermissionManager {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Permission Monitoring
|
||||
|
||||
/// Register for permission monitoring (call when a view appears)
|
||||
func registerForMonitoring() {
|
||||
monitorRegistrationCount += 1
|
||||
logger.debug("Registered for monitoring, count: \(self.monitorRegistrationCount)")
|
||||
|
||||
if monitorRegistrationCount == 1 {
|
||||
// First registration, start monitoring
|
||||
startMonitoring()
|
||||
}
|
||||
}
|
||||
|
||||
/// Unregister from permission monitoring (call when a view disappears)
|
||||
func unregisterFromMonitoring() {
|
||||
monitorRegistrationCount = max(0, monitorRegistrationCount - 1)
|
||||
logger.debug("Unregistered from monitoring, count: \(self.monitorRegistrationCount)")
|
||||
|
||||
if monitorRegistrationCount == 0 {
|
||||
// No more registrations, stop monitoring
|
||||
stopMonitoring()
|
||||
}
|
||||
}
|
||||
|
||||
private func startMonitoring() {
|
||||
logger.info("Starting permission monitoring")
|
||||
|
||||
// Initial check
|
||||
Task {
|
||||
await checkAllPermissions()
|
||||
}
|
||||
|
||||
// Start timer for periodic checks
|
||||
monitorTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
Task { @MainActor in
|
||||
await self.checkAllPermissions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stopMonitoring() {
|
||||
logger.info("Stopping permission monitoring")
|
||||
monitorTimer?.invalidate()
|
||||
monitorTimer = nil
|
||||
}
|
||||
|
||||
// MARK: - Permission Checking
|
||||
|
||||
func checkAllPermissions() async {
|
||||
let oldPermissions = permissions
|
||||
|
||||
// Check each permission type
|
||||
permissions[.appleScript] = await checkAppleScriptPermission()
|
||||
permissions[.screenRecording] = checkScreenRecordingPermission()
|
||||
permissions[.accessibility] = checkAccessibilityPermission()
|
||||
|
||||
// Post notification if any permissions changed
|
||||
if oldPermissions != permissions {
|
||||
NotificationCenter.default.post(name: .permissionsUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AppleScript Permission
|
||||
|
|
|
|||
|
|
@ -130,14 +130,20 @@ private struct PermissionsSection: View {
|
|||
@State private var permissionManager = SystemPermissionManager.shared
|
||||
@State private var permissionUpdateTrigger = 0
|
||||
|
||||
// IMPORTANT: These computed properties ensure the UI always shows current permission state.
|
||||
// The permissionUpdateTrigger dependency forces SwiftUI to re-evaluate these properties
|
||||
// when permissions change. Without this, the UI would not update when permissions are
|
||||
// granted in System Settings while this view is visible.
|
||||
//
|
||||
// We use computed properties instead of @State to avoid UI flashing - the initial
|
||||
// permission check in .task happens before the first render, ensuring correct state
|
||||
// from the start.
|
||||
private var hasAppleScriptPermission: Bool {
|
||||
// This will cause a re-read whenever permissionUpdateTrigger changes
|
||||
_ = permissionUpdateTrigger
|
||||
return permissionManager.hasPermission(.appleScript)
|
||||
}
|
||||
|
||||
private var hasAccessibilityPermission: Bool {
|
||||
// This will cause a re-read whenever permissionUpdateTrigger changes
|
||||
_ = permissionUpdateTrigger
|
||||
return permissionManager.hasPermission(.accessibility)
|
||||
}
|
||||
|
|
@ -232,13 +238,19 @@ private struct PermissionsSection: View {
|
|||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
|
||||
// Force a re-render to check permissions
|
||||
permissionUpdateTrigger += 1
|
||||
}
|
||||
.task {
|
||||
// Check all permissions
|
||||
// Check permissions before first render to avoid UI flashing
|
||||
await permissionManager.checkAllPermissions()
|
||||
|
||||
// Register for continuous monitoring
|
||||
permissionManager.registerForMonitoring()
|
||||
}
|
||||
.onDisappear {
|
||||
permissionManager.unregisterFromMonitoring()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .permissionsUpdated)) { _ in
|
||||
// Increment trigger to force computed property re-evaluation
|
||||
permissionUpdateTrigger += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,20 @@ struct RequestPermissionsPageView: View {
|
|||
@State private var permissionManager = SystemPermissionManager.shared
|
||||
@State private var permissionUpdateTrigger = 0
|
||||
|
||||
// IMPORTANT: These computed properties ensure the UI always shows current permission state.
|
||||
// The permissionUpdateTrigger dependency forces SwiftUI to re-evaluate these properties
|
||||
// when permissions change. Without this, the UI would not update when permissions are
|
||||
// granted in System Settings while this view is visible.
|
||||
//
|
||||
// We use computed properties instead of @State to avoid UI flashing - the initial
|
||||
// permission check in .task happens before the first render, ensuring correct state
|
||||
// from the start.
|
||||
private var hasAppleScriptPermission: Bool {
|
||||
// This will cause a re-read whenever permissionUpdateTrigger changes
|
||||
_ = permissionUpdateTrigger
|
||||
return permissionManager.hasPermission(.appleScript)
|
||||
}
|
||||
|
||||
private var hasAccessibilityPermission: Bool {
|
||||
// This will cause a re-read whenever permissionUpdateTrigger changes
|
||||
_ = permissionUpdateTrigger
|
||||
return permissionManager.hasPermission(.accessibility)
|
||||
}
|
||||
|
|
@ -102,13 +108,19 @@ struct RequestPermissionsPageView: View {
|
|||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding()
|
||||
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
|
||||
// Force a re-render to check permissions
|
||||
permissionUpdateTrigger += 1
|
||||
}
|
||||
.task {
|
||||
// Check all permissions
|
||||
// Check permissions before first render to avoid UI flashing
|
||||
await permissionManager.checkAllPermissions()
|
||||
|
||||
// Register for continuous monitoring
|
||||
permissionManager.registerForMonitoring()
|
||||
}
|
||||
.onDisappear {
|
||||
permissionManager.unregisterFromMonitoring()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .permissionsUpdated)) { _ in
|
||||
// Increment trigger to force computed property re-evaluation
|
||||
permissionUpdateTrigger += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue