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 gestureProgress: CGFloat = .zero
@State private var isPulsing: 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 {
@ -590,47 +589,52 @@ struct ContentView: View {
func RecordingLiveActivity() -> some View {
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 {
ZStack {
// Background circle matching notch style
RoundedRectangle(cornerRadius: MusicPlayerImageSizes.cornerRadiusInset.closed)
.fill(Color.red.opacity(0.1))
// Circle.fill icon with pulsing animation
Image(systemName: "circle.fill")
.font(.system(size: 12, weight: .medium))
.foregroundColor(.red)
.scaleEffect(isPulsing ? 1.2 : 1.0)
.opacity(isPulsing ? 0.7 : 1.0)
}
.onAppear {
withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) {
isPulsing = true
}
}
.onDisappear {
isPulsing = false
}
.frame(width: max(0, vm.effectiveClosedNotchHeight - 12), height: max(0, vm.effectiveClosedNotchHeight - 12))
Color.clear
.aspectRatio(1, contentMode: .fit)
.background(
ZStack {
RoundedRectangle(cornerRadius: MusicPlayerImageSizes.cornerRadiusInset.closed)
.fill(Color.red.opacity(0.1))
Circle()
.fill(Color.red)
.frame(width: 12, height: 12)
.modifier(PulsingModifier())
}
)
.frame(width: max(0, vm.effectiveClosedNotchHeight - 12), height: max(0, vm.effectiveClosedNotchHeight - 12))
}
.frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2),
height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)))
// Center - Recording indicator (no expansion needed since no text)
// Center - Fixed width (SAME as music)
Rectangle()
.fill(.black)
.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 {
// Empty space
// Empty - no content
}
.frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2),
height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)), 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
@ -903,3 +907,19 @@ struct FullScreenDropDelegate: DropDelegate {
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.",
"isCommentAutoGenerated" : true
},
"Privacy Alert" : {
"comment" : "A label displayed below the recording icon, indicating privacy settings.",
"isCommentAutoGenerated" : true
},
"Progress bar" : {
"localizations" : {
"en" : {

View file

@ -46,30 +46,7 @@ struct TimerLiveActivity: View {
private var timerInfoSection: some View {
Rectangle()
.fill(.black)
.overlay(
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))
.frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0))
}
private var timerCountdownSection: some View {

View file

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

View file

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