Untitled

 avatar
unknown
plain_text
2 years ago
20 kB
4
Indexable
import SwiftUI
import CoreLocation
import Combine

struct ContentView: View {
    @StateObject private var locationManager = LocationManager()
    @State private var restaurants: [String] = []
    @State private var currentIndex: Int = 0
    @State private var isMenuOpen: Bool = false
    @ObservedObject private var favoritesManager = FavoritesManager()
    @State private var showFavorites: Bool = false
    @GestureState private var dragState: DragState = .inactive
    @State private var swipedRightIndices: Set<Int> = []
    @State private var swipedIndices: Set<Int> = []
    @State private var searchRadius: Int = 5000
    @State private var showNoMoreRestaurants: Bool = false
    @State private var cardOffset: CGSize = .zero
    
    var body: some View {
        ZStack {
            VStack {
                if !restaurants.isEmpty {
                    GeometryReader { geometry in
                        ZStack {
                            if !swipedIndices.contains(currentIndex) {
                                let components = restaurants[currentIndex].components(separatedBy: "|")
                                let distance = Double(components[2]) ?? 0.0
                                let rating = Float(components[3]) ?? 0.0
                                let cardSwipedPublisher = PassthroughSubject<Void, Never>()
                                
                                GeometryReader { geometry in
                                    if currentIndex < restaurants.count {
                                        let components = restaurants[currentIndex].components(separatedBy: "|")
                                        let distance = Double(components[2]) ?? 0.0
                                        let rating = Float(components[3]) ?? 0.0
                                        
                                        RestaurantCard(restaurantInfo: restaurants[currentIndex], distance: distance, cardWidth: geometry.size.width * 0.9, cardHeight: geometry.size.height * 0.6, favoritesManager: favoritesManager)
                                            .offset(x: cardOffset.width, y: cardOffset.height)
                                            .animation(.spring(response: 0.6, dampingFraction: 0.6))
                                            .zIndex(1)
                                            .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
                                            .gesture(
                                                DragGesture()
                                                    .updating($dragState) { value, state, transaction in
                                                        state = .active(translation: value.translation)
                                                        cardOffset = value.translation
                                                    }
                                                    .onEnded { value in
                                                        if value.translation.width < -50 || value.translation.width > 50 {
                                                            if value.translation.width > 50 {
                                                                favoritesManager.addFavorite(restaurant: restaurants[currentIndex])
                                                            }
                                                            
                                                            let screenWidth = UIScreen.main.bounds.size.width
                                                            let xTranslation = value.translation.width > 0 ? screenWidth : -screenWidth
                                                            
                                                            withAnimation {
                                                                cardOffset = CGSize(width: xTranslation, height: 0)
                                                            }
                                                            
                                                            cardSwipedPublisher.send()
                                                            
                                                        } else {
                                                            cardOffset = .zero
                                                        }
                                                    }                                            )
                                            .onReceive(cardSwipedPublisher) {
                                                swipedIndices.insert(currentIndex)
                                                currentIndex += 1
                                                if currentIndex == restaurants.count {
                                                    showNoMoreRestaurants = true
                                                }
                                                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                                                    cardOffset = .zero
                                                }
                                            }
                                    }
                                }
                                
                                
                                
                            }
                            
                            
                        }
                        
                    }
                } else {
                    if showNoMoreRestaurants {
                        VStack {
                            Text("No more restaurants nearby, increase radius?")
                                .font(.title2)
                                .multilineTextAlignment(.center)
                                .padding(.bottom, 10)
                            
                            Button(action: {
                                searchRadius += 1500
                                fetchNearbyRestaurants(latitude: locationManager.lastLocation?.coordinate.latitude ?? 0,
                                                       longitude: locationManager.lastLocation?.coordinate.longitude ?? 0)
                            }) {
                                Text("Find More")
                                    .padding(.horizontal, 24)
                                    .padding(.vertical, 12)
                                    .foregroundColor(.white)
                                    .background(Color.blue)
                                    .cornerRadius(8)
                            }
                        }
                    } else {
                        Text("Loading restaurants...")
                    }
                }
            }
            VStack {
                Spacer()
                HamburgerMenu(isOpen: $isMenuOpen, showFavorites: $showFavorites)
            }
            if showFavorites {
                NavigationView {
                    List(favoritesManager.favorites, id: \.self) { favorite in
                        let components = favorite.components(separatedBy: "|")
                        let restaurantName = components[0]
                        Text(restaurantName)
                    }
                    .navigationBarTitle("Favorites", displayMode: .inline)
                    .toolbar {
                        Button("Done") {
                            showFavorites = false
                        }
                    }
                }
            }
        }
        .onAppear {
            locationManager.requestAuthorization()
        }
        
        .onChange(of: locationManager.lastLocation) { location in
            DispatchQueue.main.async {
                if let location = location {
                    fetchNearbyRestaurants(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
                }
            }
        }
        
        
        // MARK: - Fetch Nearby Restaurants
        
        func fetchNearbyRestaurants(latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
            let apiKey = "AIzaSyCWUFGPrVuiFqwqWhwvrw9Glf9HNlkuvM0"
            let urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(latitude),\(longitude)&radius=\(searchRadius)&type=restaurant&key=\(apiKey)"
            
            guard let url = URL(string: urlString) else {
                print("Invalid URL")
                return
            }
            
            URLSession.shared.dataTask(with: url) { data, response, error in
                if let data = data {
                    do {
                        let jsonDecoder = JSONDecoder()
                        let response = try jsonDecoder.decode(GooglePlacesResponse.self, from: data)
                        DispatchQueue.main.async {
                            if response.results.count < 10 && searchRadius < 10000 {
                                fetchNearbyRestaurants(latitude: latitude, longitude: longitude)
                            } else {
                                let userLocation = CLLocation(latitude: latitude, longitude: longitude)
                                restaurants = response.results.map { result in
                                    let restaurantLocation = CLLocation(latitude: result.geometry.location.lat, longitude: result.geometry.location.lng)
                                    let distanceInMiles = restaurantLocation.distance(from: userLocation) * 0.000621371
                                    let rating = result.rating ?? 0.0
                                    if let photoReference = result.photos?.first?.photo_reference {
                                        let photoUrl = "https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=\(photoReference)&key=\(apiKey)"
                                        return "\(result.name)|\(photoUrl)|\(distanceInMiles)|\(rating)"
                                    } else {
                                        return "\(result.name)||\(distanceInMiles)|\(rating)"
                                    }
                                    
                                }
                            }
                        }
                    } catch {
                        print("Error decoding JSON: \(error)")
                    }
                } else {
                    print("Error fetching nearby restaurants: \(error?.localizedDescription ?? "Unknown error")")
                }
            }.resume()
        }
    }
    
    
    struct NoMoreRestaurantsView: View {
        @Binding var searchRadius: Int
        let fetchNearbyRestaurants: (CLLocationDegrees, CLLocationDegrees) -> Void
        let locationManager: LocationManager
        
        var body: some View {
            VStack {
                Text("No more restaurants nearby, increase radius?")
                    .font(.title2)
                    .multilineTextAlignment(.center)
                    .padding(.bottom, 10)
                
                Button(action: {
                    searchRadius += 1500
                    if let location = locationManager.lastLocation {
                        fetchNearbyRestaurants(location.coordinate.latitude, location.coordinate.longitude)
                    }
                }) {
                    Text("Find More")
                        .padding(.horizontal, 24)
                        .padding(.vertical, 12)
                        .foregroundColor(.white)
                        .background(Color.blue)
                        .cornerRadius(8)
                }
            }
        }
    }
    //....RestaurantCard...........................................................................
    
    struct RestaurantCard: View {
        let restaurantInfo: String
        let distance: Double
        let cardWidth: CGFloat
        let cardHeight: CGFloat
        @ObservedObject var favoritesManager: FavoritesManager
        
        var body: some View {
            let components = restaurantInfo.components(separatedBy: "|")
            let restaurantName = components[0]
            let imageUrl = components.count > 1 ? components[1] : ""
            let rating = Float(components.count > 3 ? components[3] : "0") ?? 0.0
            
            VStack {
                if !imageUrl.isEmpty {
                    AsyncImage(url: URL(string: imageUrl)) { image in
                        image.resizable()
                    } placeholder: {
                        ProgressView()
                    }
                    .frame(maxWidth: .infinity, maxHeight: cardHeight * 2 / 3, alignment: .top)
                    .aspectRatio(contentMode: .fill)
                    .clipped()
                }
                
                VStack(alignment: .leading) {
                    HStack {
                        Text(restaurantName)
                            .font(.custom("Roboto-Thin", size: 24))
                            .foregroundColor(.primary)
                        
                        Spacer()
                        
                        if favoritesManager.isFavorite(restaurant: restaurantInfo) { // Check if the restaurant is a favorite
                            Image(systemName: "heart.fill")
                                .foregroundColor(.red)
                        }
                    }
                    
                    Text(String(format: "%.2f miles", distance))
                        .font(.footnote)
                        .foregroundColor(.gray)
                        .padding(.bottom)
                    
                    StarsView(rating: rating)
                }
                .padding([.leading, .trailing])
                
                Spacer()
                
            }
            .frame(width: cardWidth, height: cardHeight)
            .background(Color(.systemGray5))
            .cornerRadius(10)
            .shadow(radius: 10)
        }
    }
    
    
    
    struct StarsView: View {
        let rating: Float
        
        var body: some View {
            HStack {
                ForEach(0..<5) { index in
                    Image(systemName: rating >= Float(index + 1) ? "star.fill" : (rating >= Float(index) + 0.5 ? "star.leadinghalf.fill" : "star"))
                        .resizable()
                        .scaledToFit()
                        .frame(height: 16)
                        .foregroundColor(.yellow)
                }
            }
        }
    }
    
    //....End of RestaurnatCard...........................................................................
    
    //....HamburgerMenu...........................................................................
    
    struct HamburgerMenu: View {
        @Binding var isOpen: Bool
        @Binding var showFavorites: Bool
        
        var body: some View {
            VStack {
                Spacer()
                
                if isOpen {
                    VStack {
                        HStack {
                            Button(action: {
                                withAnimation {
                                    isOpen.toggle()
                                }
                            }) {
                                Image(systemName: "xmark")
                                    .font(.system(size: 20))
                                    .foregroundColor(.primary)
                                    .padding()
                            }
                            Spacer()
                        }
                        
                        Button("Filters") {
                            print("Option 1 selected")
                        }
                        .frame(maxWidth: .infinity, minHeight: 130)
                        .background(Color(.systemGray5))
                        .foregroundColor(.primary)
                        .font(.system(size: 36))
                        .background(Color.clear) // Add this line
                        
                        Button("Favorites") {
                            isOpen = false
                            showFavorites = true
                        }
                        .frame(maxWidth: .infinity, minHeight: 130)
                        .background(Color(.systemGray5))
                        .foregroundColor(.primary)
                        .font(.system(size: 36))
                        .background(Color.clear) // Add this line
                        
                        Button("About") {
                            print("Option 3 selected")
                        }
                        .frame(maxWidth: .infinity, minHeight: 130)
                        .background(Color(.systemGray5))
                        .foregroundColor(.primary)
                        .font(.system(size: 36))
                        .background(Color.clear) // Add this line
                        
                        Spacer()
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color(.systemBackground)) // Use systemBackground to adapt to dark and light modes
                    .edgesIgnoringSafeArea(.all)
                    .transition(.move(edge: .bottom))
                }
                
                Button(action: {
                    withAnimation {
                        isOpen.toggle()
                    }
                }) {
                    Image(systemName: "line.horizontal.3")
                        .font(.system(size: 20))
                        .foregroundColor(.primary)
                        .padding()
                }
            }
        }
    }
    
    
    
    //....End of HamburgerMenu...........................................................................
    
    
    //....GooglePlacesResponse...........................................................................
    
    struct GooglePlacesResponse: Codable {
        let results: [Place]
        
        struct Place: Codable {
            let name: String
            let photos: [Photo]?
            let geometry: Geometry
            let rating: Float?
            
            struct Geometry: Codable {
                let location: Location
                
                struct Location: Codable {
                    let lat: Double
                    let lng: Double
                }
            }
            
            struct Photo: Codable {
                let photo_reference: String
            }
        }
    }
    
    
    //.... End of GooglePlacesResponse.....................................................................
    
    //.....DragState......................................................................................
    
    enum DragState {
        case inactive
        case active(translation: CGSize)
        
        var translation: CGSize {
            switch self {
            case .inactive:
                return .zero
            case .active(let translation):
                return translation
            }
        }
        
        var isActive: Bool {
            switch self {
            case .inactive:
                return false
            case .active:
                return true
            }
        }
    }
    
    //.....End of DragState.......................................................................................
    
}
Editor is loading...