Untitled

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

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 randomAngles: [Double] = []
    @State private var swipedIndices: Set<Int> = []
    @State private var searchRadius: Int = 1500
    @State private var showNoMoreRestaurants: Bool = false


    var body: some View {
        ZStack {
            VStack {
                if !restaurants.isEmpty {
                    GeometryReader { geometry in
                        ZStack {
                            ForEach(restaurants.indices, id: \.self) { index in
                                if !swipedIndices.contains(index) {
                                    let components = restaurants[index].components(separatedBy: "|")
                                    let distance = Double(components[2]) ?? 0.0
                                    let rating = Float(components[3]) ?? 0.0

                                    RestaurantCard(restaurantInfo: restaurants[index], distance: distance, cardWidth: geometry.size.width * 0.9, cardHeight: geometry.size.height * 0.6, favoritesManager: favoritesManager)
                                        .offset(x: index == currentIndex ? dragState.translation.width : 0, y: index == currentIndex ? dragState.translation.height : 0)
                                        .rotationEffect(Angle(degrees: index == currentIndex ? Double(dragState.translation.width) / 10 : randomAngles[index]), anchor: UnitPoint(x: dragState.translation.width > 0 ? 0 : 1, y: dragState.translation.height > 0 ? 0 : 1))
                                        .animation(.spring(response: 0.6, dampingFraction: 0.6))
                                        .zIndex(index == currentIndex ? 1 : 0)
                                        .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
                                        .gesture(
                                            DragGesture()
                                                .updating($dragState) { value, state, transaction in
                                                    state = .active(translation: value.translation)
                                                }
                                                .onEnded { value in
                                                    if value.translation.width < -50 || value.translation.width > 50 {
                                                        if value.translation.width > 50 {
                                                            favoritesManager.addFavorite(restaurant: restaurants[currentIndex])
                                                        }
                                                        swipedIndices.insert(currentIndex)
                                                        currentIndex += 1
                                                        if currentIndex == restaurants.count {
                                                            showNoMoreRestaurants = true
                                                        }
                                                    }
                                                }
                                        )
                                }
                            }
                        }
                    }
                } 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()
           
            randomAngles = Array(repeating: 0.0, count: restaurants.count).map { _ in randomAngle()
            }
        }
        
        .onChange(of: locationManager.lastLocation) { location in
            if let location = location {
                fetchNearbyRestaurants(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
            }
        }
    }
    
    func randomAngle() -> Double {
        return Double.random(in: -3...3)
    }
    
    // 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)"
                                }
                                
                            }
                            randomAngles = Array(repeating: 0.0, count: restaurants.count).map { _ in randomAngle() }
                        }
                    }
                } 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...