Untitled
unknown
plain_text
3 years ago
18 kB
8
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 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 {
if !swipedIndices.contains(currentIndex) {
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: dragState.translation.width, y: dragState.translation.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)
}
.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()
}
.onChange(of: locationManager.lastLocation) { location in
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...