Janky animation

This commit is contained in:
Hariharan Mudaliar 2025-10-03 11:52:18 +05:30
commit 6eba9eff0b
5 changed files with 66 additions and 60 deletions

View file

@ -76,7 +76,6 @@ struct ContentView: View {
@State private var lastHapticTime: Date = Date() @State private var lastHapticTime: Date = Date()
@State private var gestureProgress: CGFloat = .zero @State private var gestureProgress: CGFloat = .zero
@State private var isPulsing: Bool = false
@State private var haptics: Bool = false @State private var haptics: Bool = false
@ -562,7 +561,7 @@ struct ContentView: View {
} }
} }
) )
.frame(width: (coordinator.expandingView.show && (coordinator.expandingView.type == .music || coordinator.expandingView.type == .timer || coordinator.expandingView.type == .recording) && Defaults[.enableSneakPeek] && Defaults[.sneakPeekStyles] == .inline) ? 380 : vm.closedNotchSize.width + (isHovering ? 8 : 0)) .frame(width: (coordinator.expandingView.show && (coordinator.expandingView.type == .music || coordinator.expandingView.type == .timer) && Defaults[.enableSneakPeek] && Defaults[.sneakPeekStyles] == .inline) ? 380 : vm.closedNotchSize.width + (isHovering ? 8 : 0))
HStack { HStack {
@ -590,47 +589,52 @@ struct ContentView: View {
func RecordingLiveActivity() -> some View { func RecordingLiveActivity() -> some View {
HStack { HStack {
// Left side - Recording icon with record.circle (same pattern as album art) // Left side - Recording icon (EXACT same structure as music album art)
HStack { HStack {
ZStack { Color.clear
// Background circle matching notch style .aspectRatio(1, contentMode: .fit)
RoundedRectangle(cornerRadius: MusicPlayerImageSizes.cornerRadiusInset.closed) .background(
.fill(Color.red.opacity(0.1)) ZStack {
RoundedRectangle(cornerRadius: MusicPlayerImageSizes.cornerRadiusInset.closed)
// Circle.fill icon with pulsing animation .fill(Color.red.opacity(0.1))
Image(systemName: "circle.fill")
.font(.system(size: 12, weight: .medium)) Circle()
.foregroundColor(.red) .fill(Color.red)
.scaleEffect(isPulsing ? 1.2 : 1.0) .frame(width: 12, height: 12)
.opacity(isPulsing ? 0.7 : 1.0) .modifier(PulsingModifier())
} }
.onAppear { )
withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) { .frame(width: max(0, vm.effectiveClosedNotchHeight - 12), height: max(0, vm.effectiveClosedNotchHeight - 12))
isPulsing = true
}
}
.onDisappear {
isPulsing = false
}
.frame(width: max(0, vm.effectiveClosedNotchHeight - 12), height: max(0, vm.effectiveClosedNotchHeight - 12))
} }
.frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2), .frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2),
height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))) height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)))
// Center - Recording indicator (no expansion needed since no text) // Center - Fixed width (SAME as music)
Rectangle() Rectangle()
.fill(.black) .fill(.black)
.frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0)) .frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0))
// Right side - Empty (as requested) // Right side - Empty space to balance (SAME structure as music visualizer)
HStack { HStack {
// Empty space // Empty - no content
} }
.frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2), .frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2),
height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)), alignment: .center) height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)), alignment: .center)
} }
.frame(height: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0), alignment: .center) .frame(height: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0), alignment: .center)
.animation(.easeInOut(duration: 0.3), value: isPulsing) }
// Helper function to format recording duration
private func formatDuration(_ duration: TimeInterval) -> String {
let hours = Int(duration) / 3600
let minutes = Int(duration) / 60 % 60
let seconds = Int(duration) % 60
if hours > 0 {
return String(format: "%d:%02d:%02d", hours, minutes, seconds)
} else {
return String(format: "%d:%02d", minutes, seconds)
}
} }
@ViewBuilder @ViewBuilder
@ -903,3 +907,19 @@ struct FullScreenDropDelegate: DropDelegate {
return true return true
} }
} }
// Pulsing animation modifier for recording indicator
struct PulsingModifier: ViewModifier {
@State private var isPulsing = false
func body(content: Content) -> some View {
content
.scaleEffect(isPulsing ? 1.2 : 1.0)
.opacity(isPulsing ? 0.7 : 1.0)
.onAppear {
withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) {
isPulsing = true
}
}
}
}

View file

@ -1107,10 +1107,6 @@
"comment" : "The title of the privacy and security settings screen.", "comment" : "The title of the privacy and security settings screen.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"Privacy Alert" : {
"comment" : "A label displayed below the recording icon, indicating privacy settings.",
"isCommentAutoGenerated" : true
},
"Progress bar" : { "Progress bar" : {
"localizations" : { "localizations" : {
"en" : { "en" : {

View file

@ -46,30 +46,7 @@ struct TimerLiveActivity: View {
private var timerInfoSection: some View { private var timerInfoSection: some View {
Rectangle() Rectangle()
.fill(.black) .fill(.black)
.overlay( .frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0))
HStack(alignment: .center) {
if coordinator.expandingView.show && coordinator.expandingView.type == .timer {
// Timer name
MarqueeText(
.constant(timerManager.timerName),
textColor: timerManager.timerColor,
minDuration: 0.4,
frameWidth: 100
)
.opacity((coordinator.expandingView.show && Defaults[.enableSneakPeek] && Defaults[.sneakPeekStyles] == .inline) ? 1 : 0)
Spacer(minLength: vm.closedNotchSize.width)
// Timer status
Text(timerManager.isPaused ? "Paused" : "Active")
.lineLimit(1)
.truncationMode(.tail)
.foregroundStyle(timerManager.isOvertime ? .red : timerManager.timerColor)
.opacity((coordinator.expandingView.show && coordinator.expandingView.type == .timer && Defaults[.enableSneakPeek] && Defaults[.sneakPeekStyles] == .inline) ? 1 : 0)
}
}
)
.frame(width: (coordinator.expandingView.show && coordinator.expandingView.type == .timer && Defaults[.enableSneakPeek] && Defaults[.sneakPeekStyles] == .inline) ? 380 : vm.closedNotchSize.width + (isHovering ? 8 : 0))
} }
private var timerCountdownSection: some View { private var timerCountdownSection: some View {

View file

@ -141,14 +141,21 @@ class ScreenRecordingManager: ObservableObject {
updateIdleState(recording: true) updateIdleState(recording: true)
// Trigger expanding view like music activity // Trigger expanding view like music activity
coordinator.toggleExpandingView(status: true, type: .recording) coordinator.toggleExpandingView(status: true, type: .recording)
withAnimation(.smooth) {
isRecording = confirmedState
}
} else if !confirmedState && isRecording { } else if !confirmedState && isRecording {
// Stopped recording - let expanding view auto-collapse naturally (like music) // Stopped recording - let expanding view auto-collapse naturally (like music)
lastUpdated = Date() lastUpdated = Date()
stopDurationTracking() stopDurationTracking()
updateIdleState(recording: false) updateIdleState(recording: false)
withAnimation(.smooth) {
isRecording = confirmedState
}
} else {
isRecording = confirmedState
} }
isRecording = confirmedState
print("ScreenRecordingManager: ✅ Recording state changed to \(confirmedState)") print("ScreenRecordingManager: ✅ Recording state changed to \(confirmedState)")
} else { } else {
print("ScreenRecordingManager: ❌ State not confirmed, keeping \(isRecording)") print("ScreenRecordingManager: ❌ State not confirmed, keeping \(isRecording)")

View file

@ -129,7 +129,9 @@ class TimerManager: ObservableObject {
timerInstance?.invalidate() timerInstance?.invalidate()
// Start new timer // Start new timer
isTimerActive = true withAnimation(.smooth) {
isTimerActive = true
}
isFinished = false isFinished = false
isOvertime = false isOvertime = false
timerName = name timerName = name
@ -191,7 +193,9 @@ class TimerManager: ObservableObject {
timerInstance?.invalidate() timerInstance?.invalidate()
timerInstance = nil timerInstance = nil
soundPlayer?.stop() soundPlayer?.stop()
isTimerActive = false withAnimation(.smooth) {
isTimerActive = false
}
resetTimer() resetTimer()
} }
@ -236,7 +240,9 @@ class TimerManager: ObservableObject {
} }
private func resetTimer() { private func resetTimer() {
isTimerActive = false withAnimation(.smooth) {
isTimerActive = false
}
timerName = "Timer" timerName = "Timer"
totalDuration = 0 totalDuration = 0
remainingTime = 0 remainingTime = 0