mirror of
https://github.com/NinjaCheetah/RIT-Dining.git
synced 2025-10-19 06:36:18 -04:00
Various minor fixes and improvements
- Visiting chef screen now shows "No visiting chefs today" instead of nothing when there are, get this, no visiting chefs today - 24-hour locations (Bytes) will no longer show as "Closing Soon" for the last 30 minutes of the day only to reset back to "Open" at midnight - The app will now display a network error instead of loading indefinitely if an error occurs while fetching dining data - Other minor spacing and layout changes
This commit is contained in:
parent
24b71b7b3f
commit
9d16be646a
@ -255,7 +255,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -286,7 +286,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
29
RIT Dining/AboutView.swift
Normal file
29
RIT Dining/AboutView.swift
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// AboutView.swift
|
||||
// RIT Dining
|
||||
//
|
||||
// Created by Campbell on 9/12/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AboutView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image("Icon")
|
||||
.resizable()
|
||||
.frame(width: 128, height: 128)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 20))
|
||||
Text("RIT Dining App")
|
||||
.font(.title)
|
||||
Text("because the RIT dining website is slow!")
|
||||
}
|
||||
.padding()
|
||||
.navigationTitle("About")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AboutView()
|
||||
}
|
12
RIT Dining/Assets.xcassets/Icon.imageset/Contents.json
vendored
Normal file
12
RIT Dining/Assets.xcassets/Icon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "RIT Dining Temp Logo.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
RIT Dining/Assets.xcassets/Icon.imageset/RIT Dining Temp Logo.png
vendored
Normal file
BIN
RIT Dining/Assets.xcassets/Icon.imageset/RIT Dining Temp Logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 140 KiB |
@ -54,7 +54,8 @@ struct LocationList: View {
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var isLoading = true
|
||||
@State private var isLoading: Bool = true
|
||||
@State private var loadFailed: Bool = false
|
||||
@State private var rotationDegrees: Double = 0
|
||||
@State private var diningLocations: [DiningLocation] = []
|
||||
@State private var lastRefreshed: Date?
|
||||
@ -82,11 +83,25 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
DispatchQueue.global().sync {
|
||||
diningLocations = newDiningLocations.sorted { $0.name < $1.name }
|
||||
// Need to sort the locations alphabetically because they get returned in a completely arbitrary order. Also
|
||||
// need to do so while ignoring the word "the" because a bunch of locations have it and it's not helpful to put
|
||||
// those all down in "T".
|
||||
diningLocations = newDiningLocations.sorted { firstLoc, secondLoc in
|
||||
func removeThe(_ name: String) -> String {
|
||||
let lowercased = name.lowercased()
|
||||
if lowercased.hasPrefix("the ") {
|
||||
return String(name.dropFirst(4))
|
||||
}
|
||||
return name
|
||||
}
|
||||
return removeThe(firstLoc.name).localizedCaseInsensitiveCompare(removeThe(secondLoc.name)) == .orderedAscending
|
||||
}
|
||||
lastRefreshed = Date()
|
||||
isLoading = false
|
||||
}
|
||||
case .failure(let error): print(error)
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
loadFailed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,18 +121,35 @@ struct ContentView: View {
|
||||
NavigationStack() {
|
||||
if isLoading {
|
||||
VStack {
|
||||
Image(systemName: "fork.knife.circle")
|
||||
.resizable()
|
||||
.frame(width: 75, height: 75)
|
||||
.foregroundStyle(.accent)
|
||||
.rotationEffect(.degrees(rotationDegrees))
|
||||
.onAppear {
|
||||
withAnimation(animation) {
|
||||
rotationDegrees = 360.0
|
||||
}
|
||||
if loadFailed {
|
||||
Image(systemName: "wifi.exclamationmark.circle")
|
||||
.resizable()
|
||||
.frame(width: 75, height: 75)
|
||||
.foregroundStyle(.accent)
|
||||
Text("An error occurred while fetching dining data. Please check your network connection and try again.")
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
Button(action: {
|
||||
loadFailed = false
|
||||
getDiningData()
|
||||
}) {
|
||||
Label("Refresh", systemImage: "arrow.clockwise")
|
||||
}
|
||||
Text("Loading...")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.top, 10)
|
||||
} else {
|
||||
Image(systemName: "fork.knife.circle")
|
||||
.resizable()
|
||||
.frame(width: 75, height: 75)
|
||||
.foregroundStyle(.accent)
|
||||
.rotationEffect(.degrees(rotationDegrees))
|
||||
.onAppear {
|
||||
withAnimation(animation) {
|
||||
rotationDegrees = 360.0
|
||||
}
|
||||
}
|
||||
Text("Loading...")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
} else {
|
||||
@ -159,6 +191,11 @@ struct ContentView: View {
|
||||
Toggle(isOn: $openLocationsOnly) {
|
||||
Label("Hide Closed Locations", systemImage: "eye.slash")
|
||||
}
|
||||
NavigationLink(destination: AboutView()) {
|
||||
Image(systemName: "info.circle")
|
||||
.foregroundColor(.accentColor)
|
||||
Text("About")
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "slider.horizontal.3")
|
||||
}
|
||||
|
@ -27,10 +27,13 @@ func getAllDiningInfo(date: String?, completionHandler: @escaping (Result<Dining
|
||||
let request = URLRequest(url: url)
|
||||
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
guard case .none = error else { return }
|
||||
if let error = error {
|
||||
completionHandler(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
print("Data error.")
|
||||
completionHandler(.failure(URLError(.badServerResponse)))
|
||||
return
|
||||
}
|
||||
|
||||
@ -82,7 +85,11 @@ func parseOpenStatus(openTime: Date, closeTime: Date) -> OpenStatus {
|
||||
let now = Date()
|
||||
var openStatus: OpenStatus = .closed
|
||||
if now >= openTime && now <= closeTime {
|
||||
if closeTime < calendar.date(byAdding: .minute, value: 30, to: now)! {
|
||||
// This is basically just for Bytes, it checks the case where the open and close times are exactly 24 hours apart, which is
|
||||
// only true for 24-hour locations.
|
||||
if closeTime == calendar.date(byAdding: .day, value: 1, to: openTime)! {
|
||||
openStatus = .open
|
||||
} else if closeTime < calendar.date(byAdding: .minute, value: 30, to: now)! {
|
||||
openStatus = .closingSoon
|
||||
} else {
|
||||
openStatus = .open
|
||||
|
@ -13,7 +13,7 @@ struct IdentifiableURL: Identifiable {
|
||||
}
|
||||
|
||||
struct VisitingChefs: View {
|
||||
@State private var diningLocations: [DiningLocation] = []
|
||||
@State private var locationsWithChefs: [DiningLocation] = []
|
||||
@State private var isLoading: Bool = true
|
||||
@State private var rotationDegrees: Double = 0
|
||||
@State private var daySwitcherRotation: Double = 0
|
||||
@ -46,11 +46,14 @@ struct VisitingChefs: View {
|
||||
let diningInfo = parseLocationInfo(location: locations.locations[i])
|
||||
print(diningInfo.name)
|
||||
DispatchQueue.global().sync {
|
||||
newDiningLocations.append(diningInfo)
|
||||
// Only save the locations that actually have visiting chefs to avoid extra iterations later.
|
||||
if let visitingChefs = diningInfo.visitingChefs, !visitingChefs.isEmpty {
|
||||
newDiningLocations.append(diningInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
DispatchQueue.global().sync {
|
||||
diningLocations = newDiningLocations
|
||||
locationsWithChefs = newDiningLocations
|
||||
isLoading = false
|
||||
}
|
||||
case .failure(let error): print(error)
|
||||
@ -64,12 +67,12 @@ struct VisitingChefs: View {
|
||||
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)")
|
||||
print("fetching visiting chefs for date \(dateString) (today)")
|
||||
} 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)")
|
||||
print("fetching visiting chefs for date \(dateString) (tomorrow)")
|
||||
}
|
||||
getDiningDataForDate(date: dateString)
|
||||
}
|
||||
@ -123,7 +126,12 @@ struct VisitingChefs: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 25)
|
||||
} else {
|
||||
ForEach(diningLocations, id: \.self) { location in
|
||||
if locationsWithChefs.isEmpty {
|
||||
Text("No visiting chefs today")
|
||||
.font(.title2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
ForEach(locationsWithChefs, id: \.self) { location in
|
||||
if let visitingChefs = location.visitingChefs, !visitingChefs.isEmpty {
|
||||
VStack(alignment: .leading) {
|
||||
Divider()
|
||||
@ -174,15 +182,13 @@ struct VisitingChefs: View {
|
||||
Text(chef.description)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 15)
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
.navigationTitle("Visiting Chefs")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.sheet(item: $safariUrl) { url in
|
||||
SafariView(url: url.url)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user