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;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -286,7 +286,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 5;
|
CURRENT_PROJECT_VERSION = 6;
|
||||||
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = 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 {
|
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 rotationDegrees: Double = 0
|
||||||
@State private var diningLocations: [DiningLocation] = []
|
@State private var diningLocations: [DiningLocation] = []
|
||||||
@State private var lastRefreshed: Date?
|
@State private var lastRefreshed: Date?
|
||||||
@ -82,11 +83,25 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DispatchQueue.global().sync {
|
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()
|
lastRefreshed = Date()
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
case .failure(let error): print(error)
|
case .failure(let error):
|
||||||
|
print(error)
|
||||||
|
loadFailed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,18 +121,35 @@ struct ContentView: View {
|
|||||||
NavigationStack() {
|
NavigationStack() {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
VStack {
|
VStack {
|
||||||
Image(systemName: "fork.knife.circle")
|
if loadFailed {
|
||||||
.resizable()
|
Image(systemName: "wifi.exclamationmark.circle")
|
||||||
.frame(width: 75, height: 75)
|
.resizable()
|
||||||
.foregroundStyle(.accent)
|
.frame(width: 75, height: 75)
|
||||||
.rotationEffect(.degrees(rotationDegrees))
|
.foregroundStyle(.accent)
|
||||||
.onAppear {
|
Text("An error occurred while fetching dining data. Please check your network connection and try again.")
|
||||||
withAnimation(animation) {
|
.foregroundStyle(.secondary)
|
||||||
rotationDegrees = 360.0
|
.multilineTextAlignment(.center)
|
||||||
}
|
Button(action: {
|
||||||
|
loadFailed = false
|
||||||
|
getDiningData()
|
||||||
|
}) {
|
||||||
|
Label("Refresh", systemImage: "arrow.clockwise")
|
||||||
}
|
}
|
||||||
Text("Loading...")
|
.padding(.top, 10)
|
||||||
.foregroundStyle(.secondary)
|
} 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()
|
.padding()
|
||||||
} else {
|
} else {
|
||||||
@ -159,6 +191,11 @@ struct ContentView: View {
|
|||||||
Toggle(isOn: $openLocationsOnly) {
|
Toggle(isOn: $openLocationsOnly) {
|
||||||
Label("Hide Closed Locations", systemImage: "eye.slash")
|
Label("Hide Closed Locations", systemImage: "eye.slash")
|
||||||
}
|
}
|
||||||
|
NavigationLink(destination: AboutView()) {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
Text("About")
|
||||||
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "slider.horizontal.3")
|
Image(systemName: "slider.horizontal.3")
|
||||||
}
|
}
|
||||||
|
@ -27,10 +27,13 @@ func getAllDiningInfo(date: String?, completionHandler: @escaping (Result<Dining
|
|||||||
let request = URLRequest(url: url)
|
let request = URLRequest(url: url)
|
||||||
|
|
||||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
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 {
|
guard let data = data else {
|
||||||
print("Data error.")
|
completionHandler(.failure(URLError(.badServerResponse)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +85,11 @@ func parseOpenStatus(openTime: Date, closeTime: Date) -> OpenStatus {
|
|||||||
let now = Date()
|
let now = Date()
|
||||||
var openStatus: OpenStatus = .closed
|
var openStatus: OpenStatus = .closed
|
||||||
if now >= openTime && now <= closeTime {
|
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
|
openStatus = .closingSoon
|
||||||
} else {
|
} else {
|
||||||
openStatus = .open
|
openStatus = .open
|
||||||
|
@ -13,7 +13,7 @@ struct IdentifiableURL: Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct VisitingChefs: View {
|
struct VisitingChefs: View {
|
||||||
@State private var diningLocations: [DiningLocation] = []
|
@State private var locationsWithChefs: [DiningLocation] = []
|
||||||
@State private var isLoading: Bool = true
|
@State private var isLoading: Bool = true
|
||||||
@State private var rotationDegrees: Double = 0
|
@State private var rotationDegrees: Double = 0
|
||||||
@State private var daySwitcherRotation: Double = 0
|
@State private var daySwitcherRotation: Double = 0
|
||||||
@ -46,11 +46,14 @@ struct VisitingChefs: View {
|
|||||||
let diningInfo = parseLocationInfo(location: locations.locations[i])
|
let diningInfo = parseLocationInfo(location: locations.locations[i])
|
||||||
print(diningInfo.name)
|
print(diningInfo.name)
|
||||||
DispatchQueue.global().sync {
|
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 {
|
DispatchQueue.global().sync {
|
||||||
diningLocations = newDiningLocations
|
locationsWithChefs = newDiningLocations
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
case .failure(let error): print(error)
|
case .failure(let error): print(error)
|
||||||
@ -64,12 +67,12 @@ struct VisitingChefs: View {
|
|||||||
let dateString: String
|
let dateString: String
|
||||||
if !isTomorrow {
|
if !isTomorrow {
|
||||||
dateString = getAPIFriendlyDateString(date: Date())
|
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 {
|
} else {
|
||||||
let calendar = Calendar.current
|
let calendar = Calendar.current
|
||||||
let tomorrow = calendar.date(byAdding: .day, value: 1, to: Date())!
|
let tomorrow = calendar.date(byAdding: .day, value: 1, to: Date())!
|
||||||
dateString = getAPIFriendlyDateString(date: tomorrow)
|
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)
|
getDiningDataForDate(date: dateString)
|
||||||
}
|
}
|
||||||
@ -123,7 +126,12 @@ struct VisitingChefs: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.vertical, 25)
|
.padding(.vertical, 25)
|
||||||
} else {
|
} 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 {
|
if let visitingChefs = location.visitingChefs, !visitingChefs.isEmpty {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Divider()
|
Divider()
|
||||||
@ -174,15 +182,13 @@ struct VisitingChefs: View {
|
|||||||
Text(chef.description)
|
Text(chef.description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.bottom, 15)
|
.padding(.bottom, 20)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
}
|
}
|
||||||
.navigationTitle("Visiting Chefs")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.sheet(item: $safariUrl) { url in
|
.sheet(item: $safariUrl) { url in
|
||||||
SafariView(url: url.url)
|
SafariView(url: url.url)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user