mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Final Fix: Working with Animation
This commit is contained in:
parent
357335d35e
commit
2ece36fe09
6 changed files with 158 additions and 74 deletions
|
|
@ -422,7 +422,7 @@ struct ContentView: View {
|
||||||
TimerLiveActivity()
|
TimerLiveActivity()
|
||||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .recording) && vm.notchState == .closed && (recordingManager.isRecording || !recordingManager.isRecorderIdle) && Defaults[.enableScreenRecordingDetection] && !vm.hideOnClosed {
|
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .recording) && vm.notchState == .closed && (recordingManager.isRecording || !recordingManager.isRecorderIdle) && Defaults[.enableScreenRecordingDetection] && !vm.hideOnClosed {
|
||||||
RecordingLiveActivity()
|
RecordingLiveActivity()
|
||||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .privacy) && vm.notchState == .closed && privacyManager.hasAnyIndicator && !vm.hideOnClosed {
|
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .privacy) && vm.notchState == .closed && privacyManager.hasAnyIndicator && (Defaults[.enableCameraDetection] || Defaults[.enableMicrophoneDetection]) && !vm.hideOnClosed {
|
||||||
PrivacyLiveActivity()
|
PrivacyLiveActivity()
|
||||||
} else if !coordinator.expandingView.show && vm.notchState == .closed && (!musicManager.isPlaying && musicManager.isPlayerIdle) && Defaults[.showNotHumanFace] && !vm.hideOnClosed {
|
} else if !coordinator.expandingView.show && vm.notchState == .closed && (!musicManager.isPlaying && musicManager.isPlayerIdle) && Defaults[.showNotHumanFace] && !vm.hideOnClosed {
|
||||||
DynamicIslandFaceAnimation().animation(.interactiveSpring, value: musicManager.isPlayerIdle)
|
DynamicIslandFaceAnimation().animation(.interactiveSpring, value: musicManager.isPlayerIdle)
|
||||||
|
|
|
||||||
|
|
@ -3792,6 +3792,12 @@
|
||||||
"Camera" : {
|
"Camera" : {
|
||||||
"comment" : "A label for the camera privacy indicator.",
|
"comment" : "A label for the camera privacy indicator.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
|
"Camera Active" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Camera Status" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Cancel" : {
|
"Cancel" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -8847,6 +8853,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Inactive" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Inline" : {
|
"Inline" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -10799,6 +10808,12 @@
|
||||||
"Microphone" : {
|
"Microphone" : {
|
||||||
"comment" : "A label displayed next to the microphone icon in the privacy indicator detail view.",
|
"comment" : "A label displayed next to the microphone icon in the privacy indicator detail view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
|
"Microphone Active" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Microphone Status" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"min" : {
|
"min" : {
|
||||||
|
|
||||||
|
|
@ -13097,10 +13112,6 @@
|
||||||
},
|
},
|
||||||
"Preview Zoom:" : {
|
"Preview Zoom:" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Privacy & Security" : {
|
|
||||||
"comment" : "The title of the privacy and security settings screen.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
},
|
||||||
"Privacy Indicators" : {
|
"Privacy Indicators" : {
|
||||||
"comment" : "A heading for the section that lists privacy indicators.",
|
"comment" : "A heading for the section that lists privacy indicators.",
|
||||||
|
|
@ -13908,9 +13919,8 @@
|
||||||
"Screen Assistant:" : {
|
"Screen Assistant:" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Screen recording detection shows a red indicator when your screen is being captured. Detection uses minimal system resources and can be disabled at any time." : {
|
"Screen Recording Detection" : {
|
||||||
"comment" : "A footer description for the \"Privacy & Security\" section of the settings.",
|
|
||||||
"isCommentAutoGenerated" : true
|
|
||||||
},
|
},
|
||||||
"Screenshot options" : {
|
"Screenshot options" : {
|
||||||
|
|
||||||
|
|
@ -15096,6 +15106,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Shows green camera icon and yellow microphone icon when in use. Uses event-driven CoreAudio and CoreMediaIO APIs." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Shows the model's reasoning process before the final answer" : {
|
"Shows the model's reasoning process before the final answer" : {
|
||||||
|
|
||||||
|
|
@ -17031,6 +17044,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Uses event-driven private API for real-time screen recording detection" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Version" : {
|
"Version" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
|
||||||
|
|
@ -16,56 +16,76 @@ struct PrivacyLiveActivity: View {
|
||||||
@State private var gestureProgress: CGFloat = 0
|
@State private var gestureProgress: CGFloat = 0
|
||||||
@State private var isExpanded: Bool = false
|
@State private var isExpanded: Bool = false
|
||||||
|
|
||||||
|
// Calculate if both camera and mic are active
|
||||||
|
private var bothIndicatorsActive: Bool {
|
||||||
|
privacyManager.indicatorLayout.showsCameraIndicator &&
|
||||||
|
privacyManager.indicatorLayout.showsMicrophoneIndicator
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
// Left side - Recording pulsator (if recording is active)
|
// Left side - Recording pulsator OR microphone (if both indicators active without recording)
|
||||||
Color.clear
|
Color.clear
|
||||||
.background {
|
.background {
|
||||||
if recordingManager.isRecording && isExpanded {
|
if isExpanded {
|
||||||
HStack {
|
HStack {
|
||||||
ZStack {
|
if recordingManager.isRecording {
|
||||||
RoundedRectangle(cornerRadius: 6)
|
// Recording pulsator
|
||||||
.fill(Color.red.opacity(0.15))
|
ZStack {
|
||||||
|
RoundedRectangle(cornerRadius: 6)
|
||||||
Circle()
|
.fill(Color.red.opacity(0.15))
|
||||||
.fill(Color.red)
|
|
||||||
.frame(width: 10, height: 10)
|
Circle()
|
||||||
.modifier(PulsingModifier())
|
.fill(Color.red)
|
||||||
|
.frame(width: 10, height: 10)
|
||||||
|
.modifier(PulsingModifier())
|
||||||
|
}
|
||||||
|
.frame(width: vm.effectiveClosedNotchHeight - 12, height: vm.effectiveClosedNotchHeight - 12)
|
||||||
|
} else if bothIndicatorsActive {
|
||||||
|
// Microphone on left when both active and no recording
|
||||||
|
PrivacyIcon(type: .microphone)
|
||||||
}
|
}
|
||||||
.frame(width: vm.effectiveClosedNotchHeight - 12, height: vm.effectiveClosedNotchHeight - 12)
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: recordingManager.isRecording && isExpanded ? max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2) : 0, height: vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))
|
.frame(width: isExpanded && (recordingManager.isRecording || bothIndicatorsActive) ? max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2 + 20) : 0, height: vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))
|
||||||
|
|
||||||
// Center - Black fill
|
// Center - Black fill
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(.black)
|
.fill(.black)
|
||||||
.frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0))
|
.frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0))
|
||||||
|
|
||||||
// Right side - Privacy indicators (camera and/or microphone)
|
// Right side - Privacy indicators
|
||||||
Color.clear
|
Color.clear
|
||||||
.background {
|
.background {
|
||||||
if isExpanded {
|
if isExpanded {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 4) {
|
||||||
// Microphone indicator (shows first if both active)
|
// When both active without recording: only camera on right (mic is on left)
|
||||||
if privacyManager.indicatorLayout.showsMicrophoneIndicator {
|
// When recording + both: show both on right
|
||||||
PrivacyIcon(type: .microphone)
|
// When only one active: show that one
|
||||||
.transition(.scale.combined(with: .opacity))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Camera indicator
|
if bothIndicatorsActive && recordingManager.isRecording {
|
||||||
if privacyManager.indicatorLayout.showsCameraIndicator {
|
// Both indicators on right when recording
|
||||||
|
PrivacyIcon(type: .microphone)
|
||||||
PrivacyIcon(type: .camera)
|
PrivacyIcon(type: .camera)
|
||||||
.transition(.scale.combined(with: .opacity))
|
} else if bothIndicatorsActive {
|
||||||
|
// Only camera on right when both active (mic is on left)
|
||||||
|
PrivacyIcon(type: .camera)
|
||||||
|
} else {
|
||||||
|
// Single indicator
|
||||||
|
if privacyManager.indicatorLayout.showsMicrophoneIndicator {
|
||||||
|
PrivacyIcon(type: .microphone)
|
||||||
|
}
|
||||||
|
if privacyManager.indicatorLayout.showsCameraIndicator {
|
||||||
|
PrivacyIcon(type: .camera)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.trailing, 8)
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: isExpanded ? max(0, vm.effectiveClosedNotchHeight + 30 + gestureProgress / 2) : 0, height: vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))
|
.frame(width: isExpanded ? max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2 + 20) : 0, height: vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))
|
||||||
}
|
}
|
||||||
.frame(height: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0))
|
.frame(height: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
@ -92,40 +112,23 @@ struct PrivacyIcon: View {
|
||||||
let type: PrivacyIndicatorType
|
let type: PrivacyIndicatorType
|
||||||
@State private var isPulsing = false
|
@State private var isPulsing = false
|
||||||
|
|
||||||
|
// Color based on type: camera = #26CC41, mic = #FF9402
|
||||||
|
private var iconColor: Color {
|
||||||
|
type == .camera ? Color(red: 0.152, green: 0.804, blue: 0.256) : Color(red: 1.000, green: 0.584, blue: 0.010)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
// Simple icon without blur/glow effects
|
||||||
// Glow effect
|
Image(systemName: type.icon)
|
||||||
Circle()
|
.font(.system(size: 13, weight: .semibold))
|
||||||
.fill(
|
.foregroundColor(iconColor)
|
||||||
RadialGradient(
|
.frame(width: 24, height: 24)
|
||||||
colors: [
|
.scaleEffect(isPulsing ? 1.1 : 1.0)
|
||||||
Color.orange.opacity(0.4),
|
.opacity(isPulsing ? 1.0 : 0.85)
|
||||||
Color.orange.opacity(0.2),
|
.onAppear {
|
||||||
Color.clear
|
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) {
|
||||||
],
|
isPulsing = true
|
||||||
center: .center,
|
}
|
||||||
startRadius: 0,
|
|
||||||
endRadius: 10
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.opacity(isPulsing ? 0.8 : 0.5)
|
|
||||||
|
|
||||||
// Icon background
|
|
||||||
Circle()
|
|
||||||
.fill(Color.orange.opacity(0.2))
|
|
||||||
.frame(width: 16, height: 16)
|
|
||||||
|
|
||||||
// Icon
|
|
||||||
Image(systemName: type.icon)
|
|
||||||
.font(.system(size: 8, weight: .semibold))
|
|
||||||
.foregroundColor(.orange)
|
|
||||||
}
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.onAppear {
|
|
||||||
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) {
|
|
||||||
isPulsing = true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,7 @@ struct GeneralSettings: View {
|
||||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||||
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||||
@ObservedObject var recordingManager = ScreenRecordingManager.shared
|
@ObservedObject var recordingManager = ScreenRecordingManager.shared
|
||||||
|
@ObservedObject var privacyManager = PrivacyIndicatorManager.shared
|
||||||
|
|
||||||
@Default(.mirrorShape) var mirrorShape
|
@Default(.mirrorShape) var mirrorShape
|
||||||
@Default(.showEmojis) var showEmojis
|
@Default(.showEmojis) var showEmojis
|
||||||
|
|
@ -245,9 +246,54 @@ struct GeneralSettings: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Privacy & Security")
|
Text("Screen Recording Detection")
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Screen recording detection shows a red indicator when your screen is being captured. Detection uses minimal system resources and can be disabled at any time.")
|
Text("Uses event-driven private API for real-time screen recording detection")
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Defaults.Toggle("Enable Camera Detection", key: .enableCameraDetection)
|
||||||
|
Defaults.Toggle("Enable Microphone Detection", key: .enableMicrophoneDetection)
|
||||||
|
|
||||||
|
if privacyManager.isMonitoring {
|
||||||
|
HStack {
|
||||||
|
Text("Camera Status")
|
||||||
|
Spacer()
|
||||||
|
if privacyManager.cameraActive {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.green)
|
||||||
|
.frame(width: 8, height: 8)
|
||||||
|
Text("Camera Active")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("Inactive")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Text("Microphone Status")
|
||||||
|
Spacer()
|
||||||
|
if privacyManager.microphoneActive {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
Circle()
|
||||||
|
.fill(Color.yellow)
|
||||||
|
.frame(width: 8, height: 8)
|
||||||
|
Text("Microphone Active")
|
||||||
|
.foregroundColor(.yellow)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("Inactive")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Privacy Indicators")
|
||||||
|
} footer: {
|
||||||
|
Text("Shows green camera icon and yellow microphone icon when in use. Uses event-driven CoreAudio and CoreMediaIO APIs.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Combine
|
import Combine
|
||||||
|
import Defaults
|
||||||
|
|
||||||
// MARK: - Indicator Layout Enum
|
// MARK: - Indicator Layout Enum
|
||||||
enum IndicatorLayout {
|
enum IndicatorLayout {
|
||||||
|
|
@ -74,6 +75,7 @@ class PrivacyIndicatorManager: ObservableObject {
|
||||||
@Published var cameraActive: Bool = false
|
@Published var cameraActive: Bool = false
|
||||||
@Published var microphoneActive: Bool = false
|
@Published var microphoneActive: Bool = false
|
||||||
@Published var screenRecordingActive: Bool = false
|
@Published var screenRecordingActive: Bool = false
|
||||||
|
@Published var isMonitoring: Bool = false
|
||||||
|
|
||||||
// MARK: - Child Monitors
|
// MARK: - Child Monitors
|
||||||
private let cameraMonitor = CameraMonitor()
|
private let cameraMonitor = CameraMonitor()
|
||||||
|
|
@ -87,8 +89,9 @@ class PrivacyIndicatorManager: ObservableObject {
|
||||||
|
|
||||||
/// Current indicator layout based on active states
|
/// Current indicator layout based on active states
|
||||||
var indicatorLayout: IndicatorLayout {
|
var indicatorLayout: IndicatorLayout {
|
||||||
let camera = cameraActive
|
// Respect user settings
|
||||||
let mic = microphoneActive
|
let camera = cameraActive && Defaults[.enableCameraDetection]
|
||||||
|
let mic = microphoneActive && Defaults[.enableMicrophoneDetection]
|
||||||
let recording = screenRecordingActive
|
let recording = screenRecordingActive
|
||||||
|
|
||||||
// 8 possible combinations
|
// 8 possible combinations
|
||||||
|
|
@ -112,9 +115,11 @@ class PrivacyIndicatorManager: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if any indicator is active
|
/// Check if any indicator is active (respecting user settings)
|
||||||
var hasAnyIndicator: Bool {
|
var hasAnyIndicator: Bool {
|
||||||
return cameraActive || microphoneActive || screenRecordingActive
|
let showCamera = cameraActive && Defaults[.enableCameraDetection]
|
||||||
|
let showMic = microphoneActive && Defaults[.enableMicrophoneDetection]
|
||||||
|
return showCamera || showMic || screenRecordingActive
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
@ -134,7 +139,9 @@ class PrivacyIndicatorManager: ObservableObject {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if self.cameraActive != isActive {
|
if self.cameraActive != isActive {
|
||||||
print("PrivacyIndicatorManager: 📷 Camera state: \(isActive)")
|
print("PrivacyIndicatorManager: 📷 Camera state: \(isActive)")
|
||||||
self.cameraActive = isActive
|
withAnimation(.smooth) {
|
||||||
|
self.cameraActive = isActive
|
||||||
|
}
|
||||||
self.logLayoutChange()
|
self.logLayoutChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -147,7 +154,9 @@ class PrivacyIndicatorManager: ObservableObject {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if self.microphoneActive != isActive {
|
if self.microphoneActive != isActive {
|
||||||
print("PrivacyIndicatorManager: 🎤 Microphone state: \(isActive)")
|
print("PrivacyIndicatorManager: 🎤 Microphone state: \(isActive)")
|
||||||
self.microphoneActive = isActive
|
withAnimation(.smooth) {
|
||||||
|
self.microphoneActive = isActive
|
||||||
|
}
|
||||||
self.logLayoutChange()
|
self.logLayoutChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +172,9 @@ class PrivacyIndicatorManager: ObservableObject {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
if self.screenRecordingActive != isRecording {
|
if self.screenRecordingActive != isRecording {
|
||||||
print("PrivacyIndicatorManager: 📹 Screen recording state: \(isRecording)")
|
print("PrivacyIndicatorManager: 📹 Screen recording state: \(isRecording)")
|
||||||
self.screenRecordingActive = isRecording
|
withAnimation(.smooth) {
|
||||||
|
self.screenRecordingActive = isRecording
|
||||||
|
}
|
||||||
self.logLayoutChange()
|
self.logLayoutChange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,6 +193,8 @@ class PrivacyIndicatorManager: ObservableObject {
|
||||||
func startMonitoring() {
|
func startMonitoring() {
|
||||||
print("PrivacyIndicatorManager: 🟢 Starting all monitors...")
|
print("PrivacyIndicatorManager: 🟢 Starting all monitors...")
|
||||||
|
|
||||||
|
isMonitoring = true
|
||||||
|
|
||||||
// Start camera monitoring
|
// Start camera monitoring
|
||||||
if cameraMonitor.isMonitoringAvailable {
|
if cameraMonitor.isMonitoringAvailable {
|
||||||
cameraMonitor.startMonitoring()
|
cameraMonitor.startMonitoring()
|
||||||
|
|
@ -204,6 +217,8 @@ class PrivacyIndicatorManager: ObservableObject {
|
||||||
func stopMonitoring() {
|
func stopMonitoring() {
|
||||||
print("PrivacyIndicatorManager: 🛑 Stopping all monitors...")
|
print("PrivacyIndicatorManager: 🛑 Stopping all monitors...")
|
||||||
|
|
||||||
|
isMonitoring = false
|
||||||
|
|
||||||
cameraMonitor.stopMonitoring()
|
cameraMonitor.stopMonitoring()
|
||||||
microphoneMonitor.stopMonitoring()
|
microphoneMonitor.stopMonitoring()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -432,6 +432,10 @@ extension Defaults.Keys {
|
||||||
// Polling removed - now uses event-driven private API detection (CGSIsScreenWatcherPresent)
|
// Polling removed - now uses event-driven private API detection (CGSIsScreenWatcherPresent)
|
||||||
// static let enableScreenRecordingPolling = Key<Bool>("enableScreenRecordingPolling", default: false)
|
// static let enableScreenRecordingPolling = Key<Bool>("enableScreenRecordingPolling", default: false)
|
||||||
|
|
||||||
|
// MARK: Privacy Indicators (Camera & Microphone Detection)
|
||||||
|
static let enableCameraDetection = Key<Bool>("enableCameraDetection", default: true)
|
||||||
|
static let enableMicrophoneDetection = Key<Bool>("enableMicrophoneDetection", default: true)
|
||||||
|
|
||||||
// MARK: ImageService
|
// MARK: ImageService
|
||||||
static let didClearLegacyURLCacheV1 = Key<Bool>("didClearLegacyURLCacheV1", default: false)
|
static let didClearLegacyURLCacheV1 = Key<Bool>("didClearLegacyURLCacheV1", default: false)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue