mirror of
https://github.com/NinjaCheetah/RIT-Dining.git
synced 2025-10-19 06:36:18 -04:00
Fixed bug reading weekly schedules
The app was previously not checking if the current day of the week was within the list of weekdays that the regular opening schedule was valid for. This lead to the app frequently claiming a location was open on the weekend when it wasn't, which burned me personally several times. I've gone to like 4 locations this weekend under the assumption they were open because my own app said so, and finally I was like "hey maybe this isn't the data being bad and I've messed something up" and lo and behold, I did. Oops. Also removes the middleman API call to get the MDO ID from the main location ID, as I realized the location info from TigerCenter actually includes the MDO ID already. This simplifies the code for getting the occupancy of a location by a good bit and just makes me happy.
This commit is contained in:
parent
f8e4c37cd4
commit
059209c9e5
@ -257,7 +257,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 = 11;
|
CURRENT_PROJECT_VERSION = 12;
|
||||||
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -291,7 +291,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 = 11;
|
CURRENT_PROJECT_VERSION = 12;
|
||||||
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
@ -33,7 +33,7 @@ struct ContentView: View {
|
|||||||
switch result {
|
switch result {
|
||||||
case .success(let locations):
|
case .success(let locations):
|
||||||
for i in 0..<locations.locations.count {
|
for i in 0..<locations.locations.count {
|
||||||
let diningInfo = parseLocationInfo(location: locations.locations[i])
|
let diningInfo = parseLocationInfo(location: locations.locations[i], forDate: nil)
|
||||||
newDiningLocations.append(diningInfo)
|
newDiningLocations.append(diningInfo)
|
||||||
}
|
}
|
||||||
diningLocations = newDiningLocations
|
diningLocations = newDiningLocations
|
||||||
|
@ -81,22 +81,19 @@ func getSingleDiningInfo(date: String?, locationId: Int, completionHandler: @esc
|
|||||||
}.resume()
|
}.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the occupancy information for a location using its TigerCenter API location ID.
|
// Get the occupancy information for a location using its MDO ID, whatever that stands for. This ID is provided alongside the other main
|
||||||
// This function is very messy but as the comment at the top of the file says, all of this async API access code is rough.
|
// ID in the data returned by the TigerCenter API.
|
||||||
func getOccupancyPercentage(locationId: Int, completionHandler: @escaping (Result<Double, Error>) -> Void) {
|
func getOccupancyPercentage(mdoId: Int, completionHandler: @escaping (Result<Double, Error>) -> Void) {
|
||||||
// We need to use the TigerCenter location ID to get the maps API ID.
|
let urlString = "https://maps.rit.edu/proxySearch/densityMapDetail.php?mdo=\(mdoId)"
|
||||||
var url_string = "https://maps.rit.edu/api/api-dining.php?id=\(locationId)"
|
print("making request to \(urlString)")
|
||||||
print("making request to \(url_string)")
|
|
||||||
|
|
||||||
guard let url = URL(string: url_string) else {
|
guard let url = URL(string: urlString) else {
|
||||||
print("Invalid URL")
|
print("Invalid URL")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let request = URLRequest(url: url)
|
let occRequest = URLRequest(url: url)
|
||||||
|
|
||||||
var mapsId: Int = 0
|
URLSession.shared.dataTask(with: occRequest) { data, response, error in
|
||||||
|
|
||||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
completionHandler(.failure(error))
|
completionHandler(.failure(error))
|
||||||
return
|
return
|
||||||
@ -113,50 +110,16 @@ func getOccupancyPercentage(locationId: Int, completionHandler: @escaping (Resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let decoded = try JSONDecoder().decode(MapsMiddlemanParser.self, from: data)
|
let occupancy = try JSONDecoder().decode([DiningOccupancyParser].self, from: data)
|
||||||
mapsId = Int(decoded.properties.mdoid)!
|
if !occupancy.isEmpty {
|
||||||
|
print("current occupancy: \(occupancy[0].count)")
|
||||||
// Use the newly-acquired maps ID to request the occupancy information for the location.
|
print("maximum occupancy: \(occupancy[0].max_occ)")
|
||||||
url_string = "https://maps.rit.edu/proxySearch/densityMapDetail.php?mdo=\(mapsId)"
|
let occupancyPercentage = Double(occupancy[0].count) / Double(occupancy[0].max_occ) * 100
|
||||||
print("making request to \(url_string)")
|
print("occupancy percentage: \(occupancyPercentage)%")
|
||||||
|
completionHandler(.success(occupancyPercentage))
|
||||||
guard let url = URL(string: url_string) else {
|
} else {
|
||||||
print("Invalid URL")
|
completionHandler(.failure(DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Failed to decode JSON"))))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
let occ_request = URLRequest(url: url)
|
|
||||||
|
|
||||||
URLSession.shared.dataTask(with: occ_request) { data, response, error in
|
|
||||||
if let error = error {
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let data = data else {
|
|
||||||
completionHandler(.failure(URLError(.badServerResponse)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
|
|
||||||
completionHandler(.failure(InvalidHTTPError.invalid))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
let occupancy = try JSONDecoder().decode([DiningOccupancyParser].self, from: data)
|
|
||||||
if !occupancy.isEmpty {
|
|
||||||
print("current occupancy: \(occupancy[0].count)")
|
|
||||||
print("maximum occupancy: \(occupancy[0].max_occ)")
|
|
||||||
let occupancyPercentage = Double(occupancy[0].count) / Double(occupancy[0].max_occ) * 100
|
|
||||||
print("occupancy percentage: \(occupancyPercentage)%")
|
|
||||||
completionHandler(.success(occupancyPercentage))
|
|
||||||
} else {
|
|
||||||
completionHandler(.failure(DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Failed to decode JSON"))))
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
completionHandler(.failure(DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Failed to decode JSON"))))
|
|
||||||
}
|
|
||||||
}.resume()
|
|
||||||
} catch {
|
} catch {
|
||||||
completionHandler(.failure(DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Failed to decode JSON"))))
|
completionHandler(.failure(DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Failed to decode JSON"))))
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ func parseOpenStatus(openTime: Date, closeTime: Date) -> OpenStatus {
|
|||||||
return openStatus
|
return openStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLocationInfo(location: DiningLocationParser) -> DiningLocation {
|
func parseLocationInfo(location: DiningLocationParser, forDate: Date?) -> DiningLocation {
|
||||||
print("beginning parse for \(location.name)")
|
print("beginning parse for \(location.name)")
|
||||||
|
|
||||||
// The descriptions sometimes have HTML <br /> tags despite also having \n. Those need to be removed.
|
// The descriptions sometimes have HTML <br /> tags despite also having \n. Those need to be removed.
|
||||||
@ -41,6 +41,7 @@ func parseLocationInfo(location: DiningLocationParser) -> DiningLocation {
|
|||||||
if location.events.isEmpty {
|
if location.events.isEmpty {
|
||||||
return DiningLocation(
|
return DiningLocation(
|
||||||
id: location.id,
|
id: location.id,
|
||||||
|
mdoId: location.mdoId,
|
||||||
name: location.name,
|
name: location.name,
|
||||||
summary: location.summary,
|
summary: location.summary,
|
||||||
desc: desc,
|
desc: desc,
|
||||||
@ -68,8 +69,15 @@ func parseLocationInfo(location: DiningLocationParser) -> DiningLocation {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !openStrings.contains(event.startTime), !closeStrings.contains(event.endTime) {
|
if !openStrings.contains(event.startTime), !closeStrings.contains(event.endTime) {
|
||||||
openStrings.append(event.startTime)
|
// Verify that the current weekday falls within the schedule. The regular event schedule specifies which days of the week
|
||||||
closeStrings.append(event.endTime)
|
// it applies to, and if the current day isn't in that list and there are no exceptions, that means there are no hours
|
||||||
|
// for this location.
|
||||||
|
let weekdayFormatter = DateFormatter()
|
||||||
|
weekdayFormatter.dateFormat = "EEEE"
|
||||||
|
if event.daysOfWeek.contains(weekdayFormatter.string(from: forDate ?? Date()).uppercased()) {
|
||||||
|
openStrings.append(event.startTime)
|
||||||
|
closeStrings.append(event.endTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,6 +87,7 @@ func parseLocationInfo(location: DiningLocationParser) -> DiningLocation {
|
|||||||
if openStrings.isEmpty || closeStrings.isEmpty {
|
if openStrings.isEmpty || closeStrings.isEmpty {
|
||||||
return DiningLocation(
|
return DiningLocation(
|
||||||
id: location.id,
|
id: location.id,
|
||||||
|
mdoId: location.mdoId,
|
||||||
name: location.name,
|
name: location.name,
|
||||||
summary: location.summary,
|
summary: location.summary,
|
||||||
desc: desc,
|
desc: desc,
|
||||||
@ -235,6 +244,7 @@ func parseLocationInfo(location: DiningLocationParser) -> DiningLocation {
|
|||||||
|
|
||||||
return DiningLocation(
|
return DiningLocation(
|
||||||
id: location.id,
|
id: location.id,
|
||||||
|
mdoId: location.mdoId,
|
||||||
name: location.name,
|
name: location.name,
|
||||||
summary: location.summary,
|
summary: location.summary,
|
||||||
desc: desc,
|
desc: desc,
|
||||||
|
@ -24,6 +24,7 @@ struct DiningLocationParser: Decodable {
|
|||||||
}
|
}
|
||||||
let startTime: String
|
let startTime: String
|
||||||
let endTime: String
|
let endTime: String
|
||||||
|
let daysOfWeek: [String]
|
||||||
let exceptions: [HoursException]?
|
let exceptions: [HoursException]?
|
||||||
}
|
}
|
||||||
// An individual "menu", which can be either a daily special item or a visitng chef. Description needs to be optional because
|
// An individual "menu", which can be either a daily special item or a visitng chef. Description needs to be optional because
|
||||||
@ -35,6 +36,7 @@ struct DiningLocationParser: Decodable {
|
|||||||
}
|
}
|
||||||
// Other basic information to read from a location's JSON that we'll need later.
|
// Other basic information to read from a location's JSON that we'll need later.
|
||||||
let id: Int
|
let id: Int
|
||||||
|
let mdoId: Int
|
||||||
let name: String
|
let name: String
|
||||||
let summary: String
|
let summary: String
|
||||||
let description: String
|
let description: String
|
||||||
@ -89,6 +91,7 @@ struct DailySpecial: Equatable, Hashable {
|
|||||||
// The basic information about a dining location needed to display it in the app after parsing is finished.
|
// The basic information about a dining location needed to display it in the app after parsing is finished.
|
||||||
struct DiningLocation: Identifiable, Hashable {
|
struct DiningLocation: Identifiable, Hashable {
|
||||||
let id: Int
|
let id: Int
|
||||||
|
let mdoId: Int
|
||||||
let name: String
|
let name: String
|
||||||
let summary: String
|
let summary: String
|
||||||
let desc: String
|
let desc: String
|
||||||
|
@ -19,6 +19,7 @@ struct DetailView: View {
|
|||||||
@State private var weeklyHours: [[String]] = []
|
@State private var weeklyHours: [[String]] = []
|
||||||
@State private var occupancyLoading: Bool = true
|
@State private var occupancyLoading: Bool = true
|
||||||
@State private var occupancyPercentage: Double = 0.0
|
@State private var occupancyPercentage: Double = 0.0
|
||||||
|
@State private var focusedDate: Date = Date()
|
||||||
private let daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
private let daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
||||||
|
|
||||||
private var animation: Animation {
|
private var animation: Animation {
|
||||||
@ -30,7 +31,7 @@ struct DetailView: View {
|
|||||||
private func requestDone(result: Result<DiningLocationParser, Error>) -> Void {
|
private func requestDone(result: Result<DiningLocationParser, Error>) -> Void {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let location):
|
case .success(let location):
|
||||||
let diningInfo = parseLocationInfo(location: location)
|
let diningInfo = parseLocationInfo(location: location, forDate: focusedDate)
|
||||||
if let times = diningInfo.diningTimes, !times.isEmpty {
|
if let times = diningInfo.diningTimes, !times.isEmpty {
|
||||||
var timeStrings: [String] = []
|
var timeStrings: [String] = []
|
||||||
for time in times {
|
for time in times {
|
||||||
@ -44,11 +45,14 @@ struct DetailView: View {
|
|||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
if week.count > 0 {
|
if week.count > 0 {
|
||||||
|
// Saving this to a state variable SUCKS, but I needed a quick fix and all of this request code is still pending a
|
||||||
|
// rewrite anyway to be properly async like the code in ContentView and VisitingChefs.
|
||||||
|
focusedDate = week.removeFirst()
|
||||||
DispatchQueue.global().async {
|
DispatchQueue.global().async {
|
||||||
let date_string = week.removeFirst().formatted(.iso8601
|
let dateString = focusedDate.formatted(.iso8601
|
||||||
.year().month().day()
|
.year().month().day()
|
||||||
.dateSeparator(.dash))
|
.dateSeparator(.dash))
|
||||||
getSingleDiningInfo(date: date_string, locationId: location.id, completionHandler: requestDone)
|
getSingleDiningInfo(date: dateString, locationId: location.id, completionHandler: requestDone)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
@ -74,7 +78,7 @@ struct DetailView: View {
|
|||||||
// Only fetch occupancy data if the location is actually open right now. Otherwise, just exit early and hide the spinner.
|
// Only fetch occupancy data if the location is actually open right now. Otherwise, just exit early and hide the spinner.
|
||||||
if location.open == .open || location.open == .closingSoon {
|
if location.open == .open || location.open == .closingSoon {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
getOccupancyPercentage(locationId: location.id) { result in
|
getOccupancyPercentage(mdoId: location.mdoId) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let occupancy):
|
case .success(let occupancy):
|
||||||
DispatchQueue.main.sync {
|
DispatchQueue.main.sync {
|
||||||
@ -297,6 +301,7 @@ struct DetailView: View {
|
|||||||
#Preview {
|
#Preview {
|
||||||
DetailView(location: DiningLocation(
|
DetailView(location: DiningLocation(
|
||||||
id: 0,
|
id: 0,
|
||||||
|
mdoId: 0,
|
||||||
name: "Example",
|
name: "Example",
|
||||||
summary: "A Place",
|
summary: "A Place",
|
||||||
desc: "A long description of the place",
|
desc: "A long description of the place",
|
||||||
|
@ -34,7 +34,7 @@ struct VisitingChefs: View {
|
|||||||
switch result {
|
switch result {
|
||||||
case .success(let locations):
|
case .success(let locations):
|
||||||
for i in 0..<locations.locations.count {
|
for i in 0..<locations.locations.count {
|
||||||
let diningInfo = parseLocationInfo(location: locations.locations[i])
|
let diningInfo = parseLocationInfo(location: locations.locations[i], forDate: nil)
|
||||||
print(diningInfo.name)
|
print(diningInfo.name)
|
||||||
// Only save the locations that actually have visiting chefs to avoid extra iterations later.
|
// Only save the locations that actually have visiting chefs to avoid extra iterations later.
|
||||||
if let visitingChefs = diningInfo.visitingChefs, !visitingChefs.isEmpty {
|
if let visitingChefs = diningInfo.visitingChefs, !visitingChefs.isEmpty {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user