// // HapticFeedbackManager.swift // Reflect // // Haptic feedback patterns that match celebration animations. // import UIKit import CoreHaptics @MainActor final class HapticFeedbackManager { static let shared = HapticFeedbackManager() private var engine: CHHapticEngine? private init() { prepareEngine() } private func prepareEngine() { guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return } do { engine = try CHHapticEngine() engine?.resetHandler = { [weak self] in try? self?.engine?.start() } try engine?.start() } catch { engine = nil } } /// Play a haptic pattern that matches the given celebration animation type. func play(for animationType: CelebrationAnimationType) { guard CHHapticEngine.capabilitiesForHardware().supportsHaptics, let engine else { // Fall back to basic haptic on devices without CoreHaptics let generator = UIImpactFeedbackGenerator(style: .medium) generator.impactOccurred() return } do { let pattern = try hapticPattern(for: animationType) let player = try engine.makePlayer(with: pattern) try engine.start() try player.start(atTime: CHHapticTimeImmediate) } catch { // Fallback to simple impact let generator = UIImpactFeedbackGenerator(style: .medium) generator.impactOccurred() } } // MARK: - Pattern Definitions private func hapticPattern(for animationType: CelebrationAnimationType) throws -> CHHapticPattern { switch animationType { case .confettiCannon: return try confettiPattern() case .vortexCheckmark: return try vortexPattern() case .explosionReveal: return try explosionPattern() case .flipReveal: return try flipPattern() case .shatterReform: return try shatterPattern() case .pulseWave: return try pulseWavePattern() case .fireworks: return try fireworksPattern() case .morphBlob: return try morphPattern() case .zoomTunnel: return try tunnelPattern() case .gravityDrop: return try gravityPattern() } } // Confetti: burst upward, then scattered taps falling down private func confettiPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Initial burst events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8) ], relativeTime: 0.0 )) // Glow swell events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3) ], relativeTime: 0.1, duration: 0.3 )) // Scattered confetti taps falling let confettiTimes: [Double] = [0.5, 0.6, 0.65, 0.75, 0.85, 0.95, 1.1, 1.2] for (i, time) in confettiTimes.enumerated() { let intensity = Float(0.6 - Double(i) * 0.05) events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: max(intensity, 0.15)), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4) ], relativeTime: time )) } // Final checkmark thud events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: 1.4 )) return try CHHapticPattern(events: events, parameters: []) } // Vortex: spinning wind-up then a satisfying snap private func vortexPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Spiral wind-up (continuous, rising intensity) events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.2), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3) ], relativeTime: 0.0, duration: 0.5 )) // Ring snap events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7) ], relativeTime: 0.5 )) // Checkmark bounce events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.6) ], relativeTime: 0.7 )) // Settle events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4) ], relativeTime: 0.9 )) let curve = CHHapticParameterCurve( parameterID: .hapticIntensityControl, controlPoints: [ CHHapticParameterCurve.ControlPoint(relativeTime: 0.0, value: 0.2), CHHapticParameterCurve.ControlPoint(relativeTime: 0.4, value: 0.7), CHHapticParameterCurve.ControlPoint(relativeTime: 0.5, value: 1.0) ], relativeTime: 0.0 ) return try CHHapticPattern(events: events, parameterCurves: [curve]) } // Explosion: big bang then particles scatter private func explosionPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Big explosion impact events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) ], relativeTime: 0.0 )) // Debris rumble events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8) ], relativeTime: 0.05, duration: 0.4 )) // Glow pulse events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2) ], relativeTime: 0.3, duration: 0.3 )) // Checkmark landing events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: 0.6 )) return try CHHapticPattern(events: events, parameters: []) } // Flip: a quick flip-over thud private func flipPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Card lift events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3) ], relativeTime: 0.0 )) // Mid-flip whoosh events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4) ], relativeTime: 0.2, duration: 0.2 )) // Card lands events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.6) ], relativeTime: 0.4 )) // Shimmer glide events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.2), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7) ], relativeTime: 0.5, duration: 0.4 )) return try CHHapticPattern(events: events, parameters: []) } // Shatter: crack apart then reform private func shatterPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Shatter crack events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0) ], relativeTime: 0.0 )) // Flying shards let shardTimes: [Double] = [0.05, 0.1, 0.15, 0.2, 0.3] for (i, time) in shardTimes.enumerated() { events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: Float(0.7 - Double(i) * 0.1)), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8) ], relativeTime: time )) } // Reform convergence events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3) ], relativeTime: 0.6, duration: 0.4 )) // Checkmark snap events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: 1.1 )) return try CHHapticPattern(events: events, parameters: []) } // Pulse Wave: expanding rings private func pulseWavePattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Three expanding wave pulses let waveTimes: [Double] = [0.0, 0.1, 0.2] for (i, time) in waveTimes.enumerated() { events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: Float(0.8 - Double(i) * 0.15)), CHHapticEventParameter(parameterID: .hapticSharpness, value: Float(0.6 - Double(i) * 0.1)) ], relativeTime: time )) } // Stamp down (checkmark) events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7) ], relativeTime: 0.3 )) // Stamp settle events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4) ], relativeTime: 0.45 )) return try CHHapticPattern(events: events, parameters: []) } // Fireworks: multiple bursts at staggered intervals private func fireworksPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // 4 firework bursts, each staggered let burstTimes: [Double] = [0.0, 0.15, 0.3, 0.45] for time in burstTimes { // Launch whoosh events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: time, duration: 0.1 )) // Burst pop events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8) ], relativeTime: time + 0.1 )) } // Final checkmark events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: 0.7 )) return try CHHapticPattern(events: events, parameters: []) } // Morph: flowing organic movement private func morphPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Expand outward events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.2) ], relativeTime: 0.0, duration: 0.4 )) // Morph shift events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3) ], relativeTime: 0.4, duration: 0.5 )) // Converge events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4) ], relativeTime: 0.9 )) // Checkmark reveal events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: 1.2 )) return try CHHapticPattern(events: events, parameters: []) } // Tunnel: rushing zoom then arrival private func tunnelPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Rings rushing inward (6 quick taps, increasing intensity) for i in 0..<6 { let time = Double(i) * 0.08 events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: Float(0.2 + Double(i) * 0.12)), CHHapticEventParameter(parameterID: .hapticSharpness, value: Float(0.3 + Double(i) * 0.1)) ], relativeTime: time )) } // Warp speed continuous events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.6) ], relativeTime: 0.3, duration: 0.2 )) // Arrival impact events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7) ], relativeTime: 0.5 )) // Settle bounce events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.4), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.4) ], relativeTime: 0.7 )) return try CHHapticPattern(events: events, parameters: []) } // Gravity: fall, bounce, bounce, settle private func gravityPattern() throws -> CHHapticPattern { var events: [CHHapticEvent] = [] // Falling whoosh events.append(CHHapticEvent( eventType: .hapticContinuous, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.3), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3) ], relativeTime: 0.0, duration: 0.35 )) // First impact (heaviest) events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8) ], relativeTime: 0.4 )) // First bounce up & down events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.6) ], relativeTime: 0.6 )) // Second bounce (smaller) events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.35), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) ], relativeTime: 0.75 )) // Final settle events.append(CHHapticEvent( eventType: .hapticTransient, parameters: [ CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.2), CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3) ], relativeTime: 0.85 )) let curve = CHHapticParameterCurve( parameterID: .hapticIntensityControl, controlPoints: [ CHHapticParameterCurve.ControlPoint(relativeTime: 0.0, value: 0.1), CHHapticParameterCurve.ControlPoint(relativeTime: 0.35, value: 0.8) ], relativeTime: 0.0 ) return try CHHapticPattern(events: events, parameterCurves: [curve]) } }