diff --git a/ios/Platform/Platform/ContentView.swift b/ios/Platform/Platform/ContentView.swift index a92a126..454c8e1 100644 --- a/ios/Platform/Platform/ContentView.swift +++ b/ios/Platform/Platform/ContentView.swift @@ -24,11 +24,12 @@ struct ContentView: View { struct MainTabView: View { @State private var selectedTab = 0 @State private var showAssistant = false + @State private var showSuccess = false var body: some View { - ZStack(alignment: .bottomTrailing) { + ZStack { TabView(selection: $selectedTab) { - HomeView() + HomeView(selectedTab: $selectedTab) .tabItem { Label("Home", systemImage: "house.fill") } @@ -42,35 +43,146 @@ struct MainTabView: View { } .tint(Color.accentWarm) - Button { - showAssistant = true - } label: { - Image(systemName: "plus") - .font(.title2.weight(.semibold)) - .foregroundStyle(.white) - .frame(width: 56, height: 56) - .background(Color.accentWarm) - .clipShape(Circle()) - .shadow(color: .black.opacity(0.2), radius: 8, y: 4) + // Floating + button + VStack { + Spacer() + HStack { + Spacer() + Button { + showAssistant = true + } label: { + Image(systemName: "plus") + .font(.title2.weight(.semibold)) + .foregroundStyle(.white) + .frame(width: 56, height: 56) + .background(Color.accentWarm) + .clipShape(Circle()) + .shadow(color: .black.opacity(0.2), radius: 8, y: 4) + } + .padding(.trailing, 20) + } + .padding(.bottom, 70) + } + + // Success overlay + if showSuccess { + SuccessConfettiView() + .ignoresSafeArea() + .allowsHitTesting(false) } - .padding(.trailing, 20) - .padding(.bottom, 70) } .sheet(isPresented: $showAssistant) { - AssistantSheetView() + AssistantSheetView(onFoodAdded: { + showAssistant = false + selectedTab = 1 + // Show confetti + withAnimation { showSuccess = true } + DispatchQueue.main.asyncAfter(deadline: .now() + 1.8) { + withAnimation { showSuccess = false } + } + }) } } } +// MARK: - Success Confetti Animation + +struct SuccessConfettiView: View { + @State private var particles: [ConfettiParticle] = [] + @State private var checkScale: CGFloat = 0 + + struct ConfettiParticle: Identifiable { + let id = UUID() + let emoji: String + let x: CGFloat + let delay: Double + let duration: Double + let rotation: Double + } + + var body: some View { + ZStack { + // Semi-transparent backdrop + Color.black.opacity(0.1) + + // Confetti particles + ForEach(particles) { p in + Text(p.emoji) + .font(.title) + .offset(x: p.x) + .modifier(FallingModifier(delay: p.delay, duration: p.duration, rotation: p.rotation)) + } + + // Checkmark + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 60)) + .foregroundStyle(Color.emerald) + .scaleEffect(checkScale) + .shadow(color: Color.emerald.opacity(0.3), radius: 20) + } + .onAppear { + // Generate particles + let emojis = ["🎉", "✨", "🍎", "🥑", "💚", "⭐️", "🎊"] + particles = (0..<15).map { i in + ConfettiParticle( + emoji: emojis[i % emojis.length], + x: CGFloat.random(in: -160...160), + delay: Double.random(in: 0...0.3), + duration: Double.random(in: 0.8...1.4), + rotation: Double.random(in: -180...180) + ) + } + // Animate checkmark + withAnimation(.spring(response: 0.4, dampingFraction: 0.6)) { + checkScale = 1.0 + } + // Haptic + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.success) + } + } +} + +struct FallingModifier: ViewModifier { + let delay: Double + let duration: Double + let rotation: Double + + @State private var yOffset: CGFloat = -400 + @State private var opacity: Double = 1 + @State private var angle: Double = 0 + + func body(content: Content) -> some View { + content + .offset(y: yOffset) + .opacity(opacity) + .rotationEffect(.degrees(angle)) + .onAppear { + withAnimation(.easeIn(duration: duration).delay(delay)) { + yOffset = 600 + opacity = 0 + angle = rotation + } + } + } +} + +// Fix: Array doesn't have .length in Swift +extension Array { + var length: Int { count } +} + +// MARK: - Assistant Sheet + struct AssistantSheetView: View { @Environment(\.dismiss) private var dismiss @State private var selectedMode = 0 + var onFoodAdded: () -> Void = {} var body: some View { VStack(spacing: 0) { - // Custom header — warm background + // Custom header VStack(spacing: 12) { - // Drag handle Capsule() .fill(Color.textTertiary.opacity(0.3)) .frame(width: 36, height: 5) @@ -80,7 +192,6 @@ struct AssistantSheetView: View { .font(.headline) .foregroundStyle(Color.textPrimary) - // Tab picker — warm styled HStack(spacing: 4) { tabButton("AI Chat", icon: "sparkles", index: 0) tabButton("Quick Add", icon: "magnifyingglass", index: 1) @@ -93,9 +204,8 @@ struct AssistantSheetView: View { .padding(.bottom, 12) .background(Color.canvas) - // Content if selectedMode == 0 { - AssistantChatView() + AssistantChatView(onFoodAdded: onFoodAdded) } else { FoodSearchView(isSheet: true) } diff --git a/ios/Platform/Platform/Features/Assistant/AssistantChatView.swift b/ios/Platform/Platform/Features/Assistant/AssistantChatView.swift index 4212643..f403d13 100644 --- a/ios/Platform/Platform/Features/Assistant/AssistantChatView.swift +++ b/ios/Platform/Platform/Features/Assistant/AssistantChatView.swift @@ -3,6 +3,7 @@ import PhotosUI struct AssistantChatView: View { @State private var vm = AssistantViewModel() + var onFoodAdded: (() -> Void)? var body: some View { VStack(spacing: 0) { @@ -113,6 +114,13 @@ struct AssistantChatView: View { .onChange(of: vm.selectedPhoto) { Task { await vm.handlePhotoSelection() } } + .onChange(of: vm.applied) { + if vm.applied { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + onFoodAdded?() + } + } + } } // MARK: - Chat Bubble diff --git a/ios/Platform/Platform/Features/Home/HomeView.swift b/ios/Platform/Platform/Features/Home/HomeView.swift index da52a45..4709165 100644 --- a/ios/Platform/Platform/Features/Home/HomeView.swift +++ b/ios/Platform/Platform/Features/Home/HomeView.swift @@ -4,8 +4,8 @@ import PhotosUI struct HomeView: View { @Environment(AuthManager.self) private var auth @State private var vm = HomeViewModel() - @State private var showAssistant = false @State private var ringAnimated = false + @Binding var selectedTab: Int var body: some View { ZStack { @@ -54,12 +54,15 @@ struct HomeView: View { } } .padding(.horizontal) - .padding(.top, 60) + .padding(.top, 16) - // Widget grid — half width + // Widget grid — half width, tap to go to fitness HStack(spacing: 12) { - calorieWidget - // Future widget placeholder — invisible spacer + Button { selectedTab = 1 } label: { + calorieWidget + } + .buttonStyle(.plain) + Color.clear } .padding(.horizontal)