feat: use ConfettiSwiftUI package instead of custom confetti
All checks were successful
Security Checks / dependency-audit (push) Successful in 12s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 3s

- import ConfettiSwiftUI
- .confettiCannon modifier on MainTabView
- 80 particles, circles/crosses/rectangles, 7 colors
- Trigger: confettiTrigger += 1
- Removed all custom confetti code (ConfettiView, PhysicsModifier, etc.)

Requires: Add package in Xcode → https://github.com/simibac/ConfettiSwiftUI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-03 11:05:41 -05:00
parent 2476c31d8c
commit 9fb0133c45
2 changed files with 27 additions and 118 deletions

13
ios/Platform/.stignore Normal file
View File

@@ -0,0 +1,13 @@
// Xcode user data — changes every build, ignore
Platform.xcodeproj/xcuserdata
Platform.xcodeproj/project.xcworkspace/xcuserdata
Platform.xcodeproj/project.xcworkspace/xcshareddata
// Build artifacts
DerivedData
build
.build
// macOS files
.DS_Store
*.xcuserstate

View File

@@ -1,4 +1,5 @@
import SwiftUI import SwiftUI
import ConfettiSwiftUI
struct ContentView: View { struct ContentView: View {
@Environment(AuthManager.self) private var auth @Environment(AuthManager.self) private var auth
@@ -24,7 +25,7 @@ struct ContentView: View {
struct MainTabView: View { struct MainTabView: View {
@State private var selectedTab = 0 @State private var selectedTab = 0
@State private var showAssistant = false @State private var showAssistant = false
@State private var showConfetti = false @State private var confettiTrigger = 0
var body: some View { var body: some View {
ZStack { ZStack {
@@ -57,14 +58,18 @@ struct MainTabView: View {
} }
.padding(.bottom, 70) .padding(.bottom, 70)
} }
// Confetti overlay
if showConfetti {
ConfettiView()
.ignoresSafeArea()
.allowsHitTesting(false)
}
} }
.confettiCannon(
counter: $confettiTrigger,
num: 80,
confettis: [.shape(.circle), .shape(.roundedCross), .shape(.slimRectangle)],
colors: [.red, .orange, .yellow, .green, .blue, .purple, .pink],
confettiSize: 8,
rainHeight: 600,
openingAngle: Angle.degrees(40),
closingAngle: Angle.degrees(140),
radius: 300
)
.sheet(isPresented: $showAssistant) { .sheet(isPresented: $showAssistant) {
AssistantSheetView(onFoodAdded: foodAdded) AssistantSheetView(onFoodAdded: foodAdded)
} }
@@ -73,117 +78,8 @@ struct MainTabView: View {
private func foodAdded() { private func foodAdded() {
showAssistant = false showAssistant = false
selectedTab = 1 selectedTab = 1
triggerConfetti() confettiTrigger += 1
}
private func triggerConfetti() {
showConfetti = true
UINotificationFeedbackGenerator().notificationOccurred(.success) UINotificationFeedbackGenerator().notificationOccurred(.success)
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
showConfetti = false
}
}
}
// MARK: - Confetti (ReactFlux style: ceiling drop, full screen scatter)
struct ConfettiView: View {
@State private var particles: [ConfettiPiece] = []
static let colors: [Color] = [
Color(red: 1, green: 0.22, blue: 0.36), // hot pink
Color(red: 0.26, green: 0.63, blue: 1), // sky blue
Color(red: 1, green: 0.84, blue: 0), // gold
Color(red: 0.18, green: 0.8, blue: 0.44), // green
Color(red: 0.61, green: 0.35, blue: 0.96), // purple
Color(red: 1, green: 0.55, blue: 0), // orange
Color(red: 0, green: 0.82, blue: 0.77), // teal
Color(red: 1, green: 0.41, blue: 0.71), // pink
Color(red: 0.39, green: 0.4, blue: 1), // indigo
Color(red: 1, green: 0.92, blue: 0.23), // yellow
]
struct ConfettiPiece: Identifiable {
let id = UUID()
let color: Color
let width: CGFloat
let height: CGFloat
let x: CGFloat
let startY: CGFloat
let drift: CGFloat
let spin: Double
let delay: Double
let fallDuration: Double
}
var body: some View {
GeometryReader { geo in
ZStack {
ForEach(particles) { p in
RoundedRectangle(cornerRadius: 1)
.fill(p.color)
.frame(width: p.width, height: p.height)
.modifier(CeilingDropModifier(
x: p.x, startY: p.startY,
endY: geo.size.height + 40,
drift: p.drift, spin: p.spin,
delay: p.delay, duration: p.fallDuration
))
}
}
.onAppear { scatter(in: geo.size) }
}
}
private func scatter(in size: CGSize) {
particles = (0..<200).map { _ in
ConfettiPiece(
color: Self.colors.randomElement()!,
width: CGFloat.random(in: 4...8),
height: CGFloat.random(in: 6...14),
x: CGFloat.random(in: 0...size.width),
startY: CGFloat.random(in: (-200)...(-20)),
drift: CGFloat.random(in: -30...30),
spin: Double.random(in: -540...540),
delay: Double.random(in: 0...0.4),
fallDuration: Double.random(in: 1.5...2.5)
)
}
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
}
struct CeilingDropModifier: ViewModifier {
let x: CGFloat
let startY: CGFloat
let endY: CGFloat
let drift: CGFloat
let spin: Double
let delay: Double
let duration: Double
@State private var progress: CGFloat = 0
@State private var opacity: Double = 1
func body(content: Content) -> some View {
content
.position(
x: x + drift * progress,
y: startY + (endY - startY) * progress
)
.opacity(opacity)
.rotation3DEffect(
Angle.degrees(spin * Double(progress)),
axis: (x: Double.random(in: 0...1), y: Double.random(in: 0...1), z: 0.5)
)
.onAppear {
withAnimation(.easeIn(duration: duration).delay(delay)) {
progress = 1
}
withAnimation(.easeIn(duration: 0.3).delay(delay + duration * 0.8)) {
opacity = 0
}
}
} }
} }