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 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user