Simplify task completion animations to 4 celebration types
Keep only implode, firework, starburst, and ripple animations. Remove slide, fade, scale, flip, bounce, spring, rotation, morph, confetti, and cascade animations for a more focused experience. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ struct AnimationTestingView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
// Animation selection
|
||||
@State private var selectedAnimation: TaskAnimationType = .slide
|
||||
@State private var selectedAnimation: TaskAnimationType = .implode
|
||||
|
||||
// Fake task data
|
||||
@State private var columns: [TestColumn] = TestColumn.defaultColumns
|
||||
|
||||
@@ -3,16 +3,6 @@ import SwiftUI
|
||||
// MARK: - Animation Type Enum
|
||||
|
||||
enum TaskAnimationType: String, CaseIterable, Identifiable {
|
||||
case slide = "Slide"
|
||||
case fade = "Fade"
|
||||
case scale = "Scale"
|
||||
case flip = "Flip"
|
||||
case bounce = "Bounce"
|
||||
case spring = "Spring"
|
||||
case rotation = "Rotation"
|
||||
case morph = "Morph"
|
||||
case confetti = "Confetti"
|
||||
case cascade = "Cascade"
|
||||
case implode = "Implode"
|
||||
case firework = "Firework"
|
||||
case starburst = "Starburst"
|
||||
@@ -22,16 +12,6 @@ enum TaskAnimationType: String, CaseIterable, Identifiable {
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .slide: return "arrow.right"
|
||||
case .fade: return "circle.lefthalf.filled"
|
||||
case .scale: return "arrow.up.left.and.arrow.down.right"
|
||||
case .flip: return "arrow.triangle.2.circlepath"
|
||||
case .bounce: return "arrow.up.arrow.down"
|
||||
case .spring: return "waveform.path"
|
||||
case .rotation: return "rotate.right"
|
||||
case .morph: return "circle.hexagongrid.fill"
|
||||
case .confetti: return "sparkles"
|
||||
case .cascade: return "stairs"
|
||||
case .implode: return "checkmark.circle"
|
||||
case .firework: return "sparkle"
|
||||
case .starburst: return "sun.max.fill"
|
||||
@@ -41,16 +21,6 @@ enum TaskAnimationType: String, CaseIterable, Identifiable {
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .slide: return "Slides horizontally to next column"
|
||||
case .fade: return "Fades out and in at destination"
|
||||
case .scale: return "Shrinks, moves, then grows"
|
||||
case .flip: return "3D flip rotation during move"
|
||||
case .bounce: return "Bouncy overshoot animation"
|
||||
case .spring: return "Physics-based spring motion"
|
||||
case .rotation: return "Spins while moving"
|
||||
case .morph: return "Shape morphs during transition"
|
||||
case .confetti: return "Celebration particles on complete"
|
||||
case .cascade: return "Multi-stage staggered animation"
|
||||
case .implode: return "Sucks into center, becomes checkmark"
|
||||
case .firework: return "Explodes into colorful sparks"
|
||||
case .starburst: return "Radiating rays from checkmark"
|
||||
@@ -58,15 +28,8 @@ enum TaskAnimationType: String, CaseIterable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this animation needs special timing (longer hold for checkmark display)
|
||||
var needsExtendedTiming: Bool {
|
||||
switch self {
|
||||
case .implode, .firework, .starburst, .ripple:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
/// All celebration animations need extended timing for checkmark display
|
||||
var needsExtendedTiming: Bool { true }
|
||||
}
|
||||
|
||||
// MARK: - Animation Phase
|
||||
@@ -190,91 +153,12 @@ struct AnimationChip: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Confetti Particle System
|
||||
|
||||
struct ConfettiParticle: Identifiable {
|
||||
let id: Int
|
||||
let color: Color
|
||||
let size: CGFloat
|
||||
var x: CGFloat
|
||||
var y: CGFloat
|
||||
var rotation: Double
|
||||
var opacity: Double
|
||||
}
|
||||
|
||||
struct ConfettiView: View {
|
||||
@State private var particles: [ConfettiParticle] = []
|
||||
|
||||
private let colors: [Color] = [
|
||||
.appPrimary, .appAccent, .appSecondary, .green, .orange, .pink, .purple
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
ForEach(particles) { particle in
|
||||
RoundedRectangle(cornerRadius: 2)
|
||||
.fill(particle.color)
|
||||
.frame(width: particle.size, height: particle.size * 0.6)
|
||||
.rotationEffect(.degrees(particle.rotation))
|
||||
.offset(x: particle.x, y: particle.y)
|
||||
.opacity(particle.opacity)
|
||||
}
|
||||
}
|
||||
.onAppear { generateParticles() }
|
||||
}
|
||||
|
||||
private func generateParticles() {
|
||||
particles = (0..<25).map { i in
|
||||
ConfettiParticle(
|
||||
id: i,
|
||||
color: colors.randomElement()!,
|
||||
size: CGFloat.random(in: 6...10),
|
||||
x: CGFloat.random(in: -60...60),
|
||||
y: CGFloat.random(in: -20...20),
|
||||
rotation: Double.random(in: 0...360),
|
||||
opacity: 1.0
|
||||
)
|
||||
}
|
||||
|
||||
withAnimation(.easeOut(duration: 1.2)) {
|
||||
particles = particles.map { p in
|
||||
var particle = p
|
||||
particle.y += CGFloat.random(in: 80...150)
|
||||
particle.x += CGFloat.random(in: -30...30)
|
||||
particle.rotation += Double.random(in: 180...540)
|
||||
particle.opacity = 0
|
||||
return particle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Animation View Modifiers
|
||||
|
||||
extension View {
|
||||
@ViewBuilder
|
||||
func taskAnimation(type: TaskAnimationType, phase: AnimationPhase) -> some View {
|
||||
switch type {
|
||||
case .slide:
|
||||
self.slideAnimation(phase: phase)
|
||||
case .fade:
|
||||
self.fadeAnimation(phase: phase)
|
||||
case .scale:
|
||||
self.scaleAnimation(phase: phase)
|
||||
case .flip:
|
||||
self.flipAnimation(phase: phase)
|
||||
case .bounce:
|
||||
self.bounceAnimation(phase: phase)
|
||||
case .spring:
|
||||
self.springAnimation(phase: phase)
|
||||
case .rotation:
|
||||
self.rotationAnimation(phase: phase)
|
||||
case .morph:
|
||||
self.morphAnimation(phase: phase)
|
||||
case .confetti:
|
||||
self.confettiAnimation(phase: phase)
|
||||
case .cascade:
|
||||
self.cascadeAnimation(phase: phase)
|
||||
case .implode:
|
||||
self.implodeAnimation(phase: phase)
|
||||
case .firework:
|
||||
@@ -287,112 +171,11 @@ extension View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Individual Animation Implementations
|
||||
// MARK: - Animation Implementations
|
||||
|
||||
private extension View {
|
||||
|
||||
// 1. Slide - Horizontal slide with opacity
|
||||
func slideAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.offset(x: phase == .exiting ? 80 : (phase == .entering ? -80 : 0))
|
||||
.opacity(phase == .exiting || phase == .entering ? 0.3 : 1.0)
|
||||
.animation(.easeInOut(duration: 0.4), value: phase)
|
||||
}
|
||||
|
||||
// 2. Fade - Simple fade out/in
|
||||
func fadeAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.opacity(phase == .idle || phase == .complete ? 1.0 : 0.0)
|
||||
.animation(.easeInOut(duration: 0.35), value: phase)
|
||||
}
|
||||
|
||||
// 3. Scale - Shrink to point and expand
|
||||
func scaleAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.scaleEffect(phase == .moving ? 0.1 : 1.0)
|
||||
.opacity(phase == .moving ? 0.3 : 1.0)
|
||||
.animation(.easeInOut(duration: 0.4), value: phase)
|
||||
}
|
||||
|
||||
// 4. Flip - 3D horizontal flip
|
||||
func flipAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.rotation3DEffect(
|
||||
.degrees(phase == .exiting ? 90 : (phase == .entering ? -90 : 0)),
|
||||
axis: (x: 0, y: 1, z: 0),
|
||||
perspective: 0.5
|
||||
)
|
||||
.opacity(phase == .moving ? 0 : 1.0)
|
||||
.animation(.easeInOut(duration: 0.45), value: phase)
|
||||
}
|
||||
|
||||
// 5. Bounce - Overshoot with vertical movement
|
||||
func bounceAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.offset(y: phase == .exiting ? -25 : (phase == .entering ? 15 : 0))
|
||||
.scaleEffect(phase == .entering ? 1.15 : (phase == .exiting ? 0.9 : 1.0))
|
||||
.animation(.interpolatingSpring(stiffness: 120, damping: 8), value: phase)
|
||||
}
|
||||
|
||||
// 6. Spring - Physics-based spring
|
||||
func springAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.offset(x: phase == .exiting ? 40 : 0, y: phase == .moving ? -20 : 0)
|
||||
.scaleEffect(phase == .entering ? 0.92 : 1.0)
|
||||
.animation(.spring(response: 0.55, dampingFraction: 0.6, blendDuration: 0), value: phase)
|
||||
}
|
||||
|
||||
// 7. Rotation - 360 degree spin
|
||||
func rotationAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.rotationEffect(.degrees(phase == .moving || phase == .exiting ? 360 : 0))
|
||||
.scaleEffect(phase == .moving ? 0.75 : 1.0)
|
||||
.animation(.easeInOut(duration: 0.5), value: phase)
|
||||
}
|
||||
|
||||
// 8. Morph - Shape change during transition
|
||||
func morphAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.clipShape(RoundedRectangle(
|
||||
cornerRadius: phase == .moving ? 60 : AppRadius.lg,
|
||||
style: .continuous
|
||||
))
|
||||
.scaleEffect(
|
||||
x: phase == .moving ? 0.6 : 1.0,
|
||||
y: phase == .moving ? 1.3 : 1.0
|
||||
)
|
||||
.opacity(phase == .moving ? 0.7 : 1.0)
|
||||
.animation(.easeInOut(duration: 0.45), value: phase)
|
||||
}
|
||||
|
||||
// 9. Confetti - Celebration burst
|
||||
func confettiAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.overlay {
|
||||
if phase == .complete {
|
||||
ConfettiView()
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
.scaleEffect(phase == .exiting ? 1.1 : (phase == .complete ? 1.05 : 1.0))
|
||||
.animation(.spring(response: 0.35, dampingFraction: 0.5), value: phase)
|
||||
}
|
||||
|
||||
// 10. Cascade - Multi-stage staggered animation
|
||||
func cascadeAnimation(phase: AnimationPhase) -> some View {
|
||||
self
|
||||
.scaleEffect(phase == .exiting ? 0.85 : 1.0)
|
||||
.rotationEffect(.degrees(phase == .exiting ? -8 : (phase == .entering ? 8 : 0)))
|
||||
.offset(y: phase == .moving ? -35 : 0)
|
||||
.opacity(phase == .moving ? 0.6 : 1.0)
|
||||
.animation(
|
||||
.easeInOut(duration: 0.25)
|
||||
.delay(phase == .entering ? 0.15 : 0),
|
||||
value: phase
|
||||
)
|
||||
}
|
||||
|
||||
// 11. Implode - Sucks into center, becomes checkmark, disappears
|
||||
// Implode - Sucks into center, becomes checkmark, disappears
|
||||
func implodeAnimation(phase: AnimationPhase) -> some View {
|
||||
ZStack {
|
||||
// Card content - shrinks and hides
|
||||
@@ -409,7 +192,7 @@ private extension View {
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Firework - Explodes into colorful sparks with checkmark
|
||||
// Firework - Explodes into colorful sparks with checkmark
|
||||
func fireworkAnimation(phase: AnimationPhase) -> some View {
|
||||
ZStack {
|
||||
// Card content - shrinks and hides
|
||||
@@ -426,7 +209,7 @@ private extension View {
|
||||
}
|
||||
}
|
||||
|
||||
// 13. Starburst - Radiating rays from checkmark
|
||||
// Starburst - Radiating rays from checkmark
|
||||
func starburstAnimation(phase: AnimationPhase) -> some View {
|
||||
ZStack {
|
||||
// Card content - shrinks and hides
|
||||
@@ -443,7 +226,7 @@ private extension View {
|
||||
}
|
||||
}
|
||||
|
||||
// 14. Ripple - Checkmark with expanding rings
|
||||
// Ripple - Checkmark with expanding rings
|
||||
func rippleAnimation(phase: AnimationPhase) -> some View {
|
||||
ZStack {
|
||||
// Card content - shrinks and hides
|
||||
|
||||
Reference in New Issue
Block a user