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
|
|
@ -16,56 +16,76 @@ struct PrivacyLiveActivity: View {
|
|||
@State private var gestureProgress: CGFloat = 0
|
||||
@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 {
|
||||
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
|
||||
.background {
|
||||
if recordingManager.isRecording && isExpanded {
|
||||
if isExpanded {
|
||||
HStack {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color.red.opacity(0.15))
|
||||
|
||||
Circle()
|
||||
.fill(Color.red)
|
||||
.frame(width: 10, height: 10)
|
||||
.modifier(PulsingModifier())
|
||||
if recordingManager.isRecording {
|
||||
// Recording pulsator
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 6)
|
||||
.fill(Color.red.opacity(0.15))
|
||||
|
||||
Circle()
|
||||
.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(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
|
||||
Rectangle()
|
||||
.fill(.black)
|
||||
.frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0))
|
||||
|
||||
// Right side - Privacy indicators (camera and/or microphone)
|
||||
// Right side - Privacy indicators
|
||||
Color.clear
|
||||
.background {
|
||||
if isExpanded {
|
||||
HStack(spacing: 8) {
|
||||
// Microphone indicator (shows first if both active)
|
||||
if privacyManager.indicatorLayout.showsMicrophoneIndicator {
|
||||
PrivacyIcon(type: .microphone)
|
||||
.transition(.scale.combined(with: .opacity))
|
||||
}
|
||||
HStack(spacing: 4) {
|
||||
// When both active without recording: only camera on right (mic is on left)
|
||||
// When recording + both: show both on right
|
||||
// When only one active: show that one
|
||||
|
||||
// Camera indicator
|
||||
if privacyManager.indicatorLayout.showsCameraIndicator {
|
||||
if bothIndicatorsActive && recordingManager.isRecording {
|
||||
// Both indicators on right when recording
|
||||
PrivacyIcon(type: .microphone)
|
||||
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(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))
|
||||
.onAppear {
|
||||
|
|
@ -92,40 +112,23 @@ struct PrivacyIcon: View {
|
|||
let type: PrivacyIndicatorType
|
||||
@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 {
|
||||
ZStack {
|
||||
// Glow effect
|
||||
Circle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
colors: [
|
||||
Color.orange.opacity(0.4),
|
||||
Color.orange.opacity(0.2),
|
||||
Color.clear
|
||||
],
|
||||
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
|
||||
// Simple icon without blur/glow effects
|
||||
Image(systemName: type.icon)
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
.foregroundColor(iconColor)
|
||||
.frame(width: 24, height: 24)
|
||||
.scaleEffect(isPulsing ? 1.1 : 1.0)
|
||||
.opacity(isPulsing ? 1.0 : 0.85)
|
||||
.onAppear {
|
||||
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) {
|
||||
isPulsing = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ struct GeneralSettings: View {
|
|||
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||
@ObservedObject var recordingManager = ScreenRecordingManager.shared
|
||||
@ObservedObject var privacyManager = PrivacyIndicatorManager.shared
|
||||
|
||||
@Default(.mirrorShape) var mirrorShape
|
||||
@Default(.showEmojis) var showEmojis
|
||||
|
|
@ -245,9 +246,54 @@ struct GeneralSettings: View {
|
|||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Privacy & Security")
|
||||
Text("Screen Recording Detection")
|
||||
} 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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue