mirror of
https://github.com/NinjaCheetah/RIT-Dining.git
synced 2026-03-05 05:25:29 -05:00
Major sorting/filtering improvements
This update mostly includes improvements related to sorting and filtering the main dining location list, including:
- Favorites! You can mark locations as your favorites by swiping them on the list or pressing the star button on their detail page. Favorites are sorted to the top.
- "Hide Closed Locations" has been moved to a dedicated sort/filter button in the bottom left corner. This looks best on iOS 26+, where it sits nicely to the left of the search bar.
- Added an "Open Locations First" option to sort open locations above closed locations, if you want to know what's open quicker without entirely hiding closed locations.
Other improvements:
- Made most asynchronous code properly async instead of bouncing between DispatchQueue.main.async{} and .sync{}.
- Added a timer to refresh open statuses every 3 seconds while on the main list, so that when the time changes the open statuses will change appropriately. It seemed silly to force you to fetch the data again just to do a quick "hey is current time in range?".
- Made date formatter shared so there aren't 3 separate copies of it.
This commit is contained in:
@@ -23,6 +23,8 @@ struct AboutView: View {
|
||||
Text("because the RIT dining website is slow!")
|
||||
Text("Version \(appVersionString) (\(buildNumber))")
|
||||
.foregroundStyle(.secondary)
|
||||
Text("The RIT Dining app is powered by the TigerCenter API. Dining location occupancy information is sourced from the RIT maps API.")
|
||||
.multilineTextAlignment(.center)
|
||||
Spacer()
|
||||
Button(action: {
|
||||
openURL(URL(string: "https://github.com/NinjaCheetah/RIT-Dining")!)
|
||||
@@ -32,7 +34,12 @@ struct AboutView: View {
|
||||
Button(action: {
|
||||
openURL(URL(string: "https://tigercenter.rit.edu/")!)
|
||||
}) {
|
||||
Label("TigerCenter API", systemImage: "globe")
|
||||
Label("TigerCenter", systemImage: "globe")
|
||||
}
|
||||
Button(action: {
|
||||
openURL(URL(string: "https://maps.rit.edu/")!)
|
||||
}) {
|
||||
Label("RIT Maps", systemImage: "globe")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -10,6 +10,7 @@ import SafariServices
|
||||
|
||||
struct DetailView: View {
|
||||
@State var location: DiningLocation
|
||||
@Environment(Favorites.self) var favorites
|
||||
@State private var isLoading: Bool = true
|
||||
@State private var rotationDegrees: Double = 0
|
||||
@State private var showingSafari: Bool = false
|
||||
@@ -26,14 +27,6 @@ struct DetailView: View {
|
||||
.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
|
||||
}()
|
||||
|
||||
private func requestDone(result: Result<DiningLocationParser, Error>) -> Void {
|
||||
switch result {
|
||||
case .success(let location):
|
||||
@@ -41,7 +34,7 @@ struct DetailView: View {
|
||||
if let times = diningInfo.diningTimes, !times.isEmpty {
|
||||
var timeStrings: [String] = []
|
||||
for time in times {
|
||||
timeStrings.append("\(display.string(from: time.openTime)) - \(display.string(from: time.closeTime))")
|
||||
timeStrings.append("\(dateDisplay.string(from: time.openTime)) - \(dateDisplay.string(from: time.closeTime))")
|
||||
}
|
||||
weeklyHours.append(timeStrings)
|
||||
} else {
|
||||
@@ -129,6 +122,23 @@ struct DetailView: View {
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
Spacer()
|
||||
Button(action: {
|
||||
if favorites.contains(location) {
|
||||
favorites.remove(location)
|
||||
} else {
|
||||
favorites.add(location)
|
||||
}
|
||||
}) {
|
||||
if favorites.contains(location) {
|
||||
Image(systemName: "star.fill")
|
||||
.foregroundStyle(.yellow)
|
||||
.font(.title3)
|
||||
} else {
|
||||
Image(systemName: "star")
|
||||
.foregroundStyle(.yellow)
|
||||
.font(.title3)
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
showingSafari = true
|
||||
}) {
|
||||
@@ -164,7 +174,7 @@ struct DetailView: View {
|
||||
.onAppear {
|
||||
openString = ""
|
||||
for time in times {
|
||||
openString += "\(display.string(from: time.openTime)) - \(display.string(from: time.closeTime)), "
|
||||
openString += "\(dateDisplay.string(from: time.openTime)) - \(dateDisplay.string(from: time.closeTime)), "
|
||||
}
|
||||
openString = String(openString.prefix(openString.count - 2))
|
||||
}
|
||||
@@ -220,7 +230,7 @@ struct DetailView: View {
|
||||
Text("Leaving Soon")
|
||||
.foregroundStyle(.orange)
|
||||
}
|
||||
Text("\(display.string(from: chef.openTime)) - \(display.string(from: chef.closeTime))")
|
||||
Text("\(dateDisplay.string(from: chef.openTime)) - \(dateDisplay.string(from: chef.closeTime))")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,43 +26,29 @@ struct VisitingChefs: View {
|
||||
.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) {
|
||||
private func getDiningDataForDate(date: String) async {
|
||||
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 {
|
||||
// Only save the locations that actually have visiting chefs to avoid extra iterations later.
|
||||
if let visitingChefs = diningInfo.visitingChefs, !visitingChefs.isEmpty {
|
||||
newDiningLocations.append(diningInfo)
|
||||
}
|
||||
}
|
||||
switch result {
|
||||
case .success(let locations):
|
||||
for i in 0..<locations.locations.count {
|
||||
let diningInfo = parseLocationInfo(location: locations.locations[i])
|
||||
print(diningInfo.name)
|
||||
// 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 {
|
||||
locationsWithChefs = newDiningLocations
|
||||
isLoading = false
|
||||
}
|
||||
case .failure(let error): print(error)
|
||||
}
|
||||
locationsWithChefs = newDiningLocations
|
||||
isLoading = false
|
||||
case .failure(let error): print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getDiningData() {
|
||||
private func getDiningData() async {
|
||||
isLoading = true
|
||||
let dateString: String
|
||||
if !isTomorrow {
|
||||
@@ -74,7 +60,7 @@ struct VisitingChefs: View {
|
||||
dateString = getAPIFriendlyDateString(date: tomorrow)
|
||||
print("fetching visiting chefs for date \(dateString) (tomorrow)")
|
||||
}
|
||||
getDiningDataForDate(date: dateString)
|
||||
await getDiningDataForDate(date: dateString)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -100,7 +86,9 @@ struct VisitingChefs: View {
|
||||
}
|
||||
}
|
||||
isTomorrow.toggle()
|
||||
getDiningData()
|
||||
Task {
|
||||
await getDiningData()
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "chevron.right.circle")
|
||||
.rotationEffect(.degrees(daySwitcherRotation))
|
||||
@@ -176,7 +164,7 @@ struct VisitingChefs: View {
|
||||
}
|
||||
Text("•")
|
||||
.foregroundStyle(.secondary)
|
||||
Text("\(display.string(from: chef.openTime)) - \(display.string(from: chef.closeTime))")
|
||||
Text("\(dateDisplay.string(from: chef.openTime)) - \(dateDisplay.string(from: chef.closeTime))")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Text(chef.description)
|
||||
@@ -192,11 +180,11 @@ struct VisitingChefs: View {
|
||||
.sheet(item: $safariUrl) { url in
|
||||
SafariView(url: url.url)
|
||||
}
|
||||
.onAppear {
|
||||
getDiningData()
|
||||
.task {
|
||||
await getDiningData()
|
||||
}
|
||||
.refreshable {
|
||||
getDiningData()
|
||||
await getDiningData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user