diff --git a/RIT Dining.xcodeproj/project.pbxproj b/RIT Dining.xcodeproj/project.pbxproj index e9e37c3..331f097 100644 --- a/RIT Dining.xcodeproj/project.pbxproj +++ b/RIT Dining.xcodeproj/project.pbxproj @@ -257,7 +257,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 12; DEVELOPMENT_TEAM = 5GF7GKNTK4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -291,7 +291,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 11; + CURRENT_PROJECT_VERSION = 12; DEVELOPMENT_TEAM = 5GF7GKNTK4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/RIT Dining/ContentView.swift b/RIT Dining/ContentView.swift index 6fe0539..e3f6e3d 100644 --- a/RIT Dining/ContentView.swift +++ b/RIT Dining/ContentView.swift @@ -33,7 +33,7 @@ struct ContentView: View { switch result { case .success(let locations): for i in 0..) -> Void) { - // We need to use the TigerCenter location ID to get the maps API ID. - var url_string = "https://maps.rit.edu/api/api-dining.php?id=\(locationId)" - print("making request to \(url_string)") +// Get the occupancy information for a location using its MDO ID, whatever that stands for. This ID is provided alongside the other main +// ID in the data returned by the TigerCenter API. +func getOccupancyPercentage(mdoId: Int, completionHandler: @escaping (Result) -> Void) { + let urlString = "https://maps.rit.edu/proxySearch/densityMapDetail.php?mdo=\(mdoId)" + print("making request to \(urlString)") - guard let url = URL(string: url_string) else { + guard let url = URL(string: urlString) else { print("Invalid URL") return } - let request = URLRequest(url: url) + let occRequest = URLRequest(url: url) - var mapsId: Int = 0 - - URLSession.shared.dataTask(with: request) { data, response, error in + URLSession.shared.dataTask(with: occRequest) { data, response, error in if let error = error { completionHandler(.failure(error)) return @@ -113,50 +110,16 @@ func getOccupancyPercentage(locationId: Int, completionHandler: @escaping (Resul } do { - let decoded = try JSONDecoder().decode(MapsMiddlemanParser.self, from: data) - mapsId = Int(decoded.properties.mdoid)! - - // Use the newly-acquired maps ID to request the occupancy information for the location. - url_string = "https://maps.rit.edu/proxySearch/densityMapDetail.php?mdo=\(mapsId)" - print("making request to \(url_string)") - - guard let url = URL(string: url_string) else { - print("Invalid URL") - return + 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")))) } - 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 { completionHandler(.failure(DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Failed to decode JSON")))) } diff --git a/RIT Dining/Data/Parsers.swift b/RIT Dining/Data/Parsers.swift index ea4c01f..ea5ef3b 100644 --- a/RIT Dining/Data/Parsers.swift +++ b/RIT Dining/Data/Parsers.swift @@ -31,7 +31,7 @@ func parseOpenStatus(openTime: Date, closeTime: Date) -> OpenStatus { return openStatus } -func parseLocationInfo(location: DiningLocationParser) -> DiningLocation { +func parseLocationInfo(location: DiningLocationParser, forDate: Date?) -> DiningLocation { print("beginning parse for \(location.name)") // The descriptions sometimes have HTML
tags despite also having \n. Those need to be removed. @@ -41,6 +41,7 @@ func parseLocationInfo(location: DiningLocationParser) -> DiningLocation { if location.events.isEmpty { return DiningLocation( id: location.id, + mdoId: location.mdoId, name: location.name, summary: location.summary, desc: desc, @@ -68,8 +69,15 @@ func parseLocationInfo(location: DiningLocationParser) -> DiningLocation { } } else { if !openStrings.contains(event.startTime), !closeStrings.contains(event.endTime) { - openStrings.append(event.startTime) - closeStrings.append(event.endTime) + // Verify that the current weekday falls within the schedule. The regular event schedule specifies which days of the week + // 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 { return DiningLocation( id: location.id, + mdoId: location.mdoId, name: location.name, summary: location.summary, desc: desc, @@ -235,6 +244,7 @@ func parseLocationInfo(location: DiningLocationParser) -> DiningLocation { return DiningLocation( id: location.id, + mdoId: location.mdoId, name: location.name, summary: location.summary, desc: desc, diff --git a/RIT Dining/Data/Types.swift b/RIT Dining/Data/Types.swift index fefae0d..e2e5d2d 100644 --- a/RIT Dining/Data/Types.swift +++ b/RIT Dining/Data/Types.swift @@ -24,6 +24,7 @@ struct DiningLocationParser: Decodable { } let startTime: String let endTime: String + let daysOfWeek: [String] let exceptions: [HoursException]? } // 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. let id: Int + let mdoId: Int let name: String let summary: 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. struct DiningLocation: Identifiable, Hashable { let id: Int + let mdoId: Int let name: String let summary: String let desc: String diff --git a/RIT Dining/Views/DetailView.swift b/RIT Dining/Views/DetailView.swift index 4f38c4a..2ad9e93 100644 --- a/RIT Dining/Views/DetailView.swift +++ b/RIT Dining/Views/DetailView.swift @@ -19,6 +19,7 @@ struct DetailView: View { @State private var weeklyHours: [[String]] = [] @State private var occupancyLoading: Bool = true @State private var occupancyPercentage: Double = 0.0 + @State private var focusedDate: Date = Date() private let daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] private var animation: Animation { @@ -30,7 +31,7 @@ struct DetailView: View { private func requestDone(result: Result) -> Void { switch result { case .success(let location): - let diningInfo = parseLocationInfo(location: location) + let diningInfo = parseLocationInfo(location: location, forDate: focusedDate) if let times = diningInfo.diningTimes, !times.isEmpty { var timeStrings: [String] = [] for time in times { @@ -44,11 +45,14 @@ struct DetailView: View { print(error) } 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 { - let date_string = week.removeFirst().formatted(.iso8601 + let dateString = focusedDate.formatted(.iso8601 .year().month().day() .dateSeparator(.dash)) - getSingleDiningInfo(date: date_string, locationId: location.id, completionHandler: requestDone) + getSingleDiningInfo(date: dateString, locationId: location.id, completionHandler: requestDone) } } else { 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. if location.open == .open || location.open == .closingSoon { DispatchQueue.main.async { - getOccupancyPercentage(locationId: location.id) { result in + getOccupancyPercentage(mdoId: location.mdoId) { result in switch result { case .success(let occupancy): DispatchQueue.main.sync { @@ -297,6 +301,7 @@ struct DetailView: View { #Preview { DetailView(location: DiningLocation( id: 0, + mdoId: 0, name: "Example", summary: "A Place", desc: "A long description of the place", diff --git a/RIT Dining/Views/VisitingChefs.swift b/RIT Dining/Views/VisitingChefs.swift index 5382e21..6ded0a1 100644 --- a/RIT Dining/Views/VisitingChefs.swift +++ b/RIT Dining/Views/VisitingChefs.swift @@ -34,7 +34,7 @@ struct VisitingChefs: View { switch result { case .success(let locations): for i in 0..