import SwiftUI // MARK: - Date Match (Swipe) struct DateMatchView: View { @State private var currentIndex = 0 @State private var offset: CGSize = .zero @State private var showMatch = false @State private var matched: DateIdea? let dateIdeas: [DateIdea] = [ DateIdea(id: "1", title: "Sunset Picnic", description: "Pack a basket and watch the sunset together at your favorite spot.", category: "romance", cost: "low", duration: "medium", location: "outdoor"), DateIdea(id: "2", title: "Cooking Challenge", description: "Pick a cuisine you've never tried and cook it together.", category: "fun", cost: "medium", duration: "long", location: "indoor"), DateIdea(id: "3", title: "Board Game Night", description: "Pull out your favorite board games and make it a tournament.", category: "fun", cost: "free", duration: "medium", location: "indoor"), DateIdea(id: "4", title: "Stargazing", description: "Find a dark spot, bring blankets, and watch the stars.", category: "romance", cost: "free", duration: "medium", location: "outdoor"), DateIdea(id: "5", title: "Art Class Together", description: "Take a pottery or painting class as a couple.", category: "creative", cost: "medium", duration: "long", location: "indoor"), ] var body: some View { VStack(spacing: CloserSpacing.xl) { if showMatch, let idea = matched { VStack(spacing: CloserSpacing.lg) { Image(systemName: "heart.fill") .font(.system(size: 72)) .foregroundColor(.closerDanger) Text("It's a Match!") .font(CloserFont.title1) .foregroundColor(.closerText) Text(idea.title) .font(CloserFont.title2) .foregroundColor(.closerPrimary) Text(idea.description ?? "") .font(CloserFont.callout) .foregroundColor(.closerTextSecondary) .multilineTextAlignment(.center) Button("Plan This Date") { // Navigate to date builder } .buttonStyle(PrimaryButtonStyle()) Button("Keep Swiping") { withAnimation { showMatch = false } } .buttonStyle(SecondaryButtonStyle()) } } else { // Card stack ZStack { ForEach(dateIdeas.indices, id: \.self) { index in if index >= currentIndex && index < currentIndex + 3 { DateSwipeCard( idea: dateIdeas[index], offset: index == currentIndex ? $offset : .constant(.zero), isTop: index == currentIndex ) .scaleEffect(index == currentIndex ? 1 : 1 - CGFloat(index - currentIndex) * 0.05) .offset(y: index == currentIndex ? 0 : CGFloat(index - currentIndex) * 10) } } } .frame(height: 400) // Action buttons HStack(spacing: CloserSpacing.xxl) { Button(action: { swipe(.left) }) { Image(systemName: "xmark.circle.fill") .font(.system(size: 56)) .foregroundColor(.closerDanger) } Button(action: { swipe(.right) }) { Image(systemName: "heart.circle.fill") .font(.system(size: 56)) .foregroundColor(.closerSuccess) } } Text("Swipe right to match, left to pass") .font(CloserFont.caption) .foregroundColor(.closerTextSecondary) } } .closerPadding() .background(Color.closerBackground) .navigationTitle("Date Ideas") .navigationBarTitleDisplayMode(.inline) } private func swipe(_ direction: SwipeDirection) { let generator = UIImpactFeedbackGenerator(style: .medium) generator.impactOccurred() withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) { offset = direction == .right ? CGSize(width: 500, height: 0) : CGSize(width: -500, height: 0) } if direction == .right { matched = dateIdeas[currentIndex] showMatch = true } DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { offset = .zero currentIndex += 1 if currentIndex >= dateIdeas.count { currentIndex = 0 } } } enum SwipeDirection { case left, right } } // MARK: - Date Swipe Card struct DateSwipeCard: View { let idea: DateIdea @Binding var offset: CGSize let isTop: Bool var body: some View { VStack(alignment: .leading, spacing: CloserSpacing.md) { // Icon area ZStack { RoundedRectangle(cornerRadius: CloserRadius.large) .fill(Color.closerPrimary.opacity(0.1)) .frame(height: 200) Image(systemName: iconForDate(idea)) .font(.system(size: 60)) .foregroundColor(.closerPrimary) } VStack(alignment: .leading, spacing: CloserSpacing.sm) { Text(idea.title) .font(CloserFont.title2) .foregroundColor(.closerText) Text(idea.description ?? "") .font(CloserFont.callout) .foregroundColor(.closerTextSecondary) .lineLimit(3) HStack(spacing: CloserSpacing.md) { TagView(text: idea.cost ?? "") TagView(text: idea.location ?? "") TagView(text: idea.duration ?? "") } } .padding(CloserSpacing.md) } .closerCard() .offset(isTop ? offset : .zero) .rotationEffect(isTop ? .degrees(Double(offset.width / 20)) : .zero) .gesture(isTop ? DragGesture() .onChanged { value in offset = value.translation } .onEnded { _ in if abs(offset.width) > 120 { // Let swipe handler in parent manage this } else { withAnimation(.spring()) { offset = .zero } } } : nil) } private func iconForDate(_ idea: DateIdea) -> String { switch idea.category { case "romance": return "heart.fill" case "fun": return "gamecontroller.fill" case "creative": return "paintbrush.fill" case "adventure": return "paperplane.fill" default: return "star.fill" } } } // MARK: - Date Matches struct DateMatchesView: View { @State private var matches: [DateIdea] = [] @State private var isLoading = true var body: some View { List { if isLoading { ProgressView() .frame(maxWidth: .infinity) } else if matches.isEmpty { EmptyStateView( icon: "heart.slash", title: "No Matches Yet", message: "Swipe on date ideas to find mutual matches with your partner!", action: (title: "Browse Ideas", handler: {}) ) .listRowBackground(Color.clear) } else { ForEach(matches, id: \.id) { match in HStack(spacing: CloserSpacing.md) { Image(systemName: "heart.fill") .foregroundColor(.closerDanger) VStack(alignment: .leading, spacing: 4) { Text(match.title) .font(CloserFont.body) Text(match.description ?? "") .font(CloserFont.caption) .foregroundColor(.closerTextSecondary) .lineLimit(2) } } .padding(.vertical, 4) } } } .listStyle(.insetGrouped) .background(Color.closerBackground) .navigationTitle("Date Matches") .navigationBarTitleDisplayMode(.inline) .task { try? await Task.sleep(nanoseconds: 500_000_000) isLoading = false } } } // MARK: - Date Builder struct DateBuilderView: View { @State private var dateTitle = "" @State private var dateDescription = "" @State private var dateLocation = "" @State private var selectedDate = Date() @State private var isLoading = false var body: some View { Form { Section("Date Details") { TextField("Title", text: $dateTitle) TextField("Description (optional)", text: $dateDescription, axis: .vertical) .lineLimit(3) TextField("Location (optional)", text: $dateLocation) } Section("Date & Time") { DatePicker("Date", selection: $selectedDate, displayedComponents: [.date, .hourAndMinute]) } Section { Button(action: savePlan) { if isLoading { ProgressView() } else { Text("Save Date Plan") } } .disabled(dateTitle.isEmpty || isLoading) .frame(maxWidth: .infinity) } } .background(Color.closerBackground) .navigationTitle("Plan a Date") .navigationBarTitleDisplayMode(.inline) } private func savePlan() { isLoading = true Task { try? await Task.sleep(nanoseconds: 500_000_000) isLoading = false } } } // MARK: - Bucket List struct BucketListView: View { @State private var items: [BucketListItem] = [] @State private var isLoading = true @State private var showAdd = false @State private var newItemTitle = "" @State private var newItemDescription = "" var body: some View { List { if isLoading { ProgressView() .frame(maxWidth: .infinity) } else if items.isEmpty { EmptyStateView( icon: "list.bullet", title: "Your Bucket List is Empty", message: "Add things you want to do together as a couple!", action: (title: "Add Item", handler: { showAdd = true }) ) .listRowBackground(Color.clear) } else { ForEach(items) { item in HStack { Image(systemName: item.completed ? "checkmark.circle.fill" : "circle") .foregroundColor(item.completed ? .closerSuccess : .closerDivider) .onTapGesture { toggleItem(item) } VStack(alignment: .leading, spacing: 4) { Text(item.title) .font(CloserFont.body) .strikethrough(item.completed) .foregroundColor(item.completed ? .closerTextSecondary : .closerText) if let desc = item.description { Text(desc) .font(CloserFont.caption) .foregroundColor(.closerTextSecondary) } } } .padding(.vertical, 4) } } } .listStyle(.insetGrouped) .background(Color.closerBackground) .navigationTitle("Our Bucket List") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: { showAdd = true }) { Image(systemName: "plus") } } } .sheet(isPresented: $showAdd) { NavigationStack { Form { TextField("Title", text: $newItemTitle) TextField("Description (optional)", text: $newItemDescription, axis: .vertical) .lineLimit(3) } .navigationTitle("Add Item") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { showAdd = false } } ToolbarItem(placement: .confirmationAction) { Button("Add") { addItem() } .disabled(newItemTitle.isEmpty) } } } } .task { try? await Task.sleep(nanoseconds: 500_000_000) isLoading = false } } private func addItem() { let item = BucketListItem( id: UUID().uuidString, title: newItemTitle, description: newItemDescription.isEmpty ? nil : newItemDescription, createdBy: AuthService.shared.currentUserId ?? "", completed: false, completedAt: nil, createdAt: Date() ) items.append(item) newItemTitle = "" newItemDescription = "" showAdd = false } private func toggleItem(_ item: BucketListItem) { if let index = items.firstIndex(where: { $0.id == item.id }) { items[index].completed.toggle() items[index].completedAt = items[index].completed ? Date() : nil } } } // MARK: - Helper Views struct TagView: View { let text: String var body: some View { Text(text.capitalized) .font(CloserFont.caption) .foregroundColor(.closerTextSecondary) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.closerDivider.opacity(0.5)) .cornerRadius(CloserRadius.full) } }