Final Fix: Working with Animation

This commit is contained in:
Hariharan Mudaliar 2025-10-15 23:55:50 +05:30
commit 2ece36fe09
6 changed files with 158 additions and 74 deletions

View file

@ -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
}
}
}
}
}