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:
Trey t
2025-12-26 21:35:42 -06:00
parent 3274924937
commit 49f4cf168f
2 changed files with 8 additions and 225 deletions

View File

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

View File

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