feat: use ConfettiSwiftUI package instead of custom confetti
- 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:
13
ios/Platform/.stignore
Normal file
13
ios/Platform/.stignore
Normal 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
|
||||
@@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import ConfettiSwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(AuthManager.self) private var auth
|
||||
@@ -24,7 +25,7 @@ struct ContentView: View {
|
||||
struct MainTabView: View {
|
||||
@State private var selectedTab = 0
|
||||
@State private var showAssistant = false
|
||||
@State private var showConfetti = false
|
||||
@State private var confettiTrigger = 0
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -57,14 +58,18 @@ struct MainTabView: View {
|
||||
}
|
||||
.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) {
|
||||
AssistantSheetView(onFoodAdded: foodAdded)
|
||||
}
|
||||
@@ -73,117 +78,8 @@ struct MainTabView: View {
|
||||
private func foodAdded() {
|
||||
showAssistant = false
|
||||
selectedTab = 1
|
||||
triggerConfetti()
|
||||
}
|
||||
|
||||
private func triggerConfetti() {
|
||||
showConfetti = true
|
||||
confettiTrigger += 1
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user