From 6eba9eff0b1348df918746c5f645ebbab24e71d0 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Fri, 3 Oct 2025 11:52:18 +0530 Subject: [PATCH] Janky animation --- DynamicIsland/ContentView.swift | 76 ++++++++++++------- DynamicIsland/Localizable.xcstrings | 4 - .../components/Timer/TimerLiveActivity.swift | 25 +----- .../managers/ScreenRecordingManager.swift | 9 ++- DynamicIsland/managers/TimerManager.swift | 12 ++- 5 files changed, 66 insertions(+), 60 deletions(-) diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift index bc4ec20..bbbcb2e 100644 --- a/DynamicIsland/ContentView.swift +++ b/DynamicIsland/ContentView.swift @@ -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 + } + } + } +} diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index caed19b..89f9ac4 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -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" : { diff --git a/DynamicIsland/components/Timer/TimerLiveActivity.swift b/DynamicIsland/components/Timer/TimerLiveActivity.swift index 3f7a559..9e5aff2 100644 --- a/DynamicIsland/components/Timer/TimerLiveActivity.swift +++ b/DynamicIsland/components/Timer/TimerLiveActivity.swift @@ -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 { diff --git a/DynamicIsland/managers/ScreenRecordingManager.swift b/DynamicIsland/managers/ScreenRecordingManager.swift index 1082f45..bac90ba 100644 --- a/DynamicIsland/managers/ScreenRecordingManager.swift +++ b/DynamicIsland/managers/ScreenRecordingManager.swift @@ -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)") diff --git a/DynamicIsland/managers/TimerManager.swift b/DynamicIsland/managers/TimerManager.swift index 0921a9d..06adfd9 100644 --- a/DynamicIsland/managers/TimerManager.swift +++ b/DynamicIsland/managers/TimerManager.swift @@ -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