mirror of
https://github.com/NinjaCheetah/RIT-Dining.git
synced 2025-10-19 06:36:18 -04:00
Reworked the detail view for locations to use the screen space more effectively and show the day's visiting chefs and daily specials, as well as the hours for the entire week. The visiting chef screen has also been massively reworked to show all visiting chefs under what location they're in, with their times for the day and an indicator marking their status (one of: "Here Now", "Arriving Later", "Ariving Soon", "Leaving Soon", and "Left For Today"). These markers are also used in location's detail view. There's also an arrow in the top right that switches the visiting chef screen from today's visiting chefs to tomorrow's, so you can scout out what you might want to get tomorrow. Locations are also now sorted alphabetically on the main menu, to make finding the location you're looking for easier.
201 lines
8.3 KiB
Swift
201 lines
8.3 KiB
Swift
//
|
|
// VisitingChefs.swift
|
|
// RIT Dining
|
|
//
|
|
// Created by Campbell on 9/8/25.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct IdentifiableURL: Identifiable {
|
|
let id = UUID()
|
|
let url: URL
|
|
}
|
|
|
|
struct VisitingChefs: View {
|
|
@State private var diningLocations: [DiningLocation] = []
|
|
@State private var isLoading: Bool = true
|
|
@State private var rotationDegrees: Double = 0
|
|
@State private var daySwitcherRotation: Double = 0
|
|
@State private var safariUrl: IdentifiableURL?
|
|
@State private var isTomorrow: Bool = false
|
|
|
|
private var animation: Animation {
|
|
.linear
|
|
.speed(0.1)
|
|
.repeatForever(autoreverses: false)
|
|
}
|
|
|
|
private let display: DateFormatter = {
|
|
let display = DateFormatter()
|
|
display.timeZone = TimeZone(identifier: "America/New_York")
|
|
display.dateStyle = .none
|
|
display.timeStyle = .short
|
|
return display
|
|
}()
|
|
|
|
// Asynchronously fetch the data for all of the locations on the given date (only ever today or tomorrow) to get the visiting chef
|
|
// information.
|
|
private func getDiningDataForDate(date: String) {
|
|
var newDiningLocations: [DiningLocation] = []
|
|
getAllDiningInfo(date: date) { result in
|
|
DispatchQueue.global().async {
|
|
switch result {
|
|
case .success(let locations):
|
|
for i in 0..<locations.locations.count {
|
|
let diningInfo = parseLocationInfo(location: locations.locations[i])
|
|
print(diningInfo.name)
|
|
DispatchQueue.global().sync {
|
|
newDiningLocations.append(diningInfo)
|
|
}
|
|
}
|
|
DispatchQueue.global().sync {
|
|
diningLocations = newDiningLocations
|
|
isLoading = false
|
|
}
|
|
case .failure(let error): print(error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func getDiningData() {
|
|
isLoading = true
|
|
let dateString: String
|
|
if !isTomorrow {
|
|
dateString = getAPIFriendlyDateString(date: Date())
|
|
print("default really really really long string to make this line more obvious for debugging: \(dateString)")
|
|
} else {
|
|
let calendar = Calendar.current
|
|
let tomorrow = calendar.date(byAdding: .day, value: 1, to: Date())!
|
|
dateString = getAPIFriendlyDateString(date: tomorrow)
|
|
print("really really really long string to make this line more obvious for debugging: \(dateString)")
|
|
}
|
|
getDiningDataForDate(date: dateString)
|
|
}
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading) {
|
|
HStack(alignment: .center) {
|
|
if !isTomorrow {
|
|
Text("Today's Visiting Chefs")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
} else {
|
|
Text("Tomorrow's Visiting Chefs")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
}
|
|
Spacer()
|
|
Button(action: {
|
|
withAnimation(Animation.linear.speed(1.5)) {
|
|
if isTomorrow {
|
|
daySwitcherRotation = 0.0
|
|
} else {
|
|
daySwitcherRotation = 180.0
|
|
}
|
|
}
|
|
isTomorrow.toggle()
|
|
getDiningData()
|
|
}) {
|
|
Image(systemName: "chevron.right.circle")
|
|
.rotationEffect(.degrees(daySwitcherRotation))
|
|
.font(.title)
|
|
}
|
|
}
|
|
if isLoading {
|
|
VStack {
|
|
Image(systemName: "fork.knife.circle")
|
|
.resizable()
|
|
.frame(width: 75, height: 75)
|
|
.foregroundStyle(.accent)
|
|
.rotationEffect(.degrees(rotationDegrees))
|
|
.onAppear {
|
|
rotationDegrees = 0.0
|
|
withAnimation(animation) {
|
|
rotationDegrees = 360.0
|
|
}
|
|
}
|
|
Text("Loading...")
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 25)
|
|
} else {
|
|
ForEach(diningLocations, id: \.self) { location in
|
|
if let visitingChefs = location.visitingChefs, !visitingChefs.isEmpty {
|
|
VStack(alignment: .leading) {
|
|
Divider()
|
|
HStack(alignment: .center) {
|
|
Text(location.name)
|
|
.font(.title2)
|
|
.fontWeight(.semibold)
|
|
Spacer()
|
|
Button(action: {
|
|
safariUrl = IdentifiableURL(url: URL(string: location.mapsUrl)!)
|
|
}) {
|
|
Image(systemName: "map")
|
|
.foregroundStyle(.accent)
|
|
}
|
|
}
|
|
ForEach(visitingChefs, id: \.self) { chef in
|
|
Spacer()
|
|
Text(chef.name)
|
|
.fontWeight(.semibold)
|
|
HStack(spacing: 3) {
|
|
if !isTomorrow {
|
|
switch chef.status {
|
|
case .hereNow:
|
|
Text("Here Now")
|
|
.foregroundStyle(.green)
|
|
case .gone:
|
|
Text("Left For Today")
|
|
.foregroundStyle(.red)
|
|
case .arrivingLater:
|
|
Text("Arriving Later")
|
|
.foregroundStyle(.red)
|
|
case .arrivingSoon:
|
|
Text("Arriving Soon")
|
|
.foregroundStyle(.orange)
|
|
case .leavingSoon:
|
|
Text("Leaving Soon")
|
|
.foregroundStyle(.orange)
|
|
}
|
|
} else {
|
|
Text("Arriving Tomorrow")
|
|
.foregroundStyle(.red)
|
|
}
|
|
Text("•")
|
|
.foregroundStyle(.secondary)
|
|
Text("\(display.string(from: chef.openTime)) - \(display.string(from: chef.closeTime))")
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
Text(chef.description)
|
|
}
|
|
}
|
|
.padding(.bottom, 15)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 8)
|
|
}
|
|
.navigationTitle("Visiting Chefs")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.sheet(item: $safariUrl) { url in
|
|
SafariView(url: url.url)
|
|
}
|
|
.onAppear {
|
|
getDiningData()
|
|
}
|
|
.refreshable {
|
|
getDiningData()
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
VisitingChefs()
|
|
}
|