diff --git a/ios/Platform/.stignore b/ios/Platform/.stignore new file mode 100644 index 0000000..52fb9b9 --- /dev/null +++ b/ios/Platform/.stignore @@ -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 diff --git a/ios/Platform/Platform/ContentView.swift b/ios/Platform/Platform/ContentView.swift index aa6a590..56bfaee 100644 --- a/ios/Platform/Platform/ContentView.swift +++ b/ios/Platform/Platform/ContentView.swift @@ -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 - } - } } }