mirror of
https://github.com/NinjaCheetah/RIT-Dining.git
synced 2026-03-04 21:25:27 -05:00
Replace all instances of "RIT Dining" with "TigerDine"
The project and some files were still named that way, so that's been fixed now. The bundle ID is stuck that way forever but oh well. Nobody will see that.
This commit is contained in:
51
TigerDine/Data/DietaryRestrictions.swift
Normal file
51
TigerDine/Data/DietaryRestrictions.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// DietaryRestrictions.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 11/11/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum Allergen: String, Codable, CaseIterable {
|
||||
case coconut
|
||||
case egg
|
||||
case gluten
|
||||
case milk
|
||||
case peanut
|
||||
case sesame
|
||||
case shellfish
|
||||
case soy
|
||||
case treenut
|
||||
case wheat
|
||||
}
|
||||
|
||||
@Observable
|
||||
class DietaryRestrictions {
|
||||
private var dietaryRestrictions: Set<String>
|
||||
private let key = "DietaryRestrictions"
|
||||
|
||||
init() {
|
||||
let favorites = UserDefaults.standard.array(forKey: key) as? [String] ?? [String]()
|
||||
dietaryRestrictions = Set(favorites)
|
||||
}
|
||||
|
||||
func contains(_ restriction: Allergen) -> Bool {
|
||||
dietaryRestrictions.contains(restriction.rawValue)
|
||||
}
|
||||
|
||||
func add(_ restriction: Allergen) {
|
||||
dietaryRestrictions.insert(restriction.rawValue)
|
||||
save()
|
||||
}
|
||||
|
||||
func remove(_ restriction: Allergen) {
|
||||
dietaryRestrictions.remove(restriction.rawValue)
|
||||
save()
|
||||
}
|
||||
|
||||
func save() {
|
||||
let favorites = Array(dietaryRestrictions)
|
||||
UserDefaults.standard.set(favorites, forKey: key)
|
||||
}
|
||||
}
|
||||
117
TigerDine/Data/DiningModel.swift
Normal file
117
TigerDine/Data/DiningModel.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// DiningModel.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 10/1/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
class DiningModel {
|
||||
var locationsByDay = [[DiningLocation]]()
|
||||
var daysRepresented = [Date]()
|
||||
var lastRefreshed: Date?
|
||||
var visitingChefPushes = VisitingChefPushesModel()
|
||||
var notifyingChefs = NotifyingChefs()
|
||||
|
||||
// This is the actual method responsible for making requests to the API for the current day and next 6 days to collect all
|
||||
// of the information that the app needs for the various view. Making it part of the model allows it to be updated from
|
||||
// any view at any time, and prevents excess API requests (if you never refresh, the app will never need to make more than 7
|
||||
// calls per launch).
|
||||
func getHoursByDay() async throws {
|
||||
let calendar = Calendar.current
|
||||
let today = calendar.startOfDay(for: Date())
|
||||
let week: [Date] = (0..<7).compactMap { offset in
|
||||
calendar.date(byAdding: .day, value: offset, to: today)
|
||||
}
|
||||
daysRepresented = week
|
||||
var newLocationsByDay = [[DiningLocation]]()
|
||||
for day in week {
|
||||
let dateString = day.formatted(.iso8601
|
||||
.year().month().day()
|
||||
.dateSeparator(.dash))
|
||||
switch await getAllDiningInfo(date: dateString) {
|
||||
case .success(let locations):
|
||||
var newDiningLocations = [DiningLocation]()
|
||||
for i in 0..<locations.locations.count {
|
||||
let diningInfo = parseLocationInfo(location: locations.locations[i], forDate: day)
|
||||
newDiningLocations.append(diningInfo)
|
||||
}
|
||||
newLocationsByDay.append(newDiningLocations)
|
||||
case .failure(let error):
|
||||
throw(error)
|
||||
}
|
||||
}
|
||||
locationsByDay = newLocationsByDay
|
||||
lastRefreshed = Date()
|
||||
}
|
||||
|
||||
// Iterates through all of the locations and updates their open status indicator based on the current time. Does a replace
|
||||
// to make sure that it updates any views observing this model.
|
||||
func updateOpenStatuses() {
|
||||
locationsByDay = locationsByDay.map { day in
|
||||
day.map { location in
|
||||
var location = location
|
||||
location.updateOpenStatus()
|
||||
return location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scheduleAllPushes() async {
|
||||
for day in locationsByDay {
|
||||
for location in day {
|
||||
if let visitingChefs = location.visitingChefs {
|
||||
for chef in visitingChefs {
|
||||
if notifyingChefs.contains(chef.name) {
|
||||
await visitingChefPushes.scheduleNewPush(
|
||||
name: chef.name,
|
||||
location: location.name,
|
||||
startTime: chef.openTime,
|
||||
endTime: chef.closeTime
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Run a cleanup task once we're done scheduling.
|
||||
await cleanupPushes()
|
||||
}
|
||||
|
||||
// Cleanup old push notifications that have already gone by so we're not still tracking them forever and ever.
|
||||
func cleanupPushes() async {
|
||||
let now = Date()
|
||||
for push in visitingChefPushes.pushes {
|
||||
if now > push.endTime {
|
||||
visitingChefPushes.pushes.remove(at: visitingChefPushes.pushes.firstIndex(of: push)!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancelAllPushes() async {
|
||||
let uuids = visitingChefPushes.pushes.map(\.uuid)
|
||||
await cancelVisitingChefNotifs(uuids: uuids)
|
||||
visitingChefPushes.pushes.removeAll()
|
||||
}
|
||||
|
||||
func schedulePushesForChef(_ chefName: String) async {
|
||||
for day in locationsByDay {
|
||||
for location in day {
|
||||
if let visitingChefs = location.visitingChefs {
|
||||
for chef in visitingChefs {
|
||||
if chef.name == chefName && notifyingChefs.contains(chef.name) {
|
||||
await visitingChefPushes.scheduleNewPush(
|
||||
name: chef.name,
|
||||
location: location.name,
|
||||
startTime: chef.openTime,
|
||||
endTime: chef.closeTime
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
TigerDine/Data/Favorites.swift
Normal file
38
TigerDine/Data/Favorites.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// Favorites.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 9/22/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
class Favorites {
|
||||
private var favoriteLocations: Set<Int>
|
||||
private let key = "Favorites"
|
||||
|
||||
init() {
|
||||
let favorites = UserDefaults.standard.array(forKey: key) as? [Int] ?? [Int]()
|
||||
favoriteLocations = Set(favorites)
|
||||
}
|
||||
|
||||
func contains(_ location: DiningLocation) -> Bool {
|
||||
favoriteLocations.contains(location.id)
|
||||
}
|
||||
|
||||
func add(_ location: DiningLocation) {
|
||||
favoriteLocations.insert(location.id)
|
||||
save()
|
||||
}
|
||||
|
||||
func remove(_ location: DiningLocation) {
|
||||
favoriteLocations.remove(location.id)
|
||||
save()
|
||||
}
|
||||
|
||||
func save() {
|
||||
let favorites = Array(favoriteLocations)
|
||||
UserDefaults.standard.set(favorites, forKey: key)
|
||||
}
|
||||
}
|
||||
37
TigerDine/Data/MenuDietaryRestrictionsModel.swift
Normal file
37
TigerDine/Data/MenuDietaryRestrictionsModel.swift
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// MenuDietaryRestrictionsModel.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 11/11/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
class MenuDietaryRestrictionsModel: ObservableObject {
|
||||
var dietaryRestrictions = DietaryRestrictions()
|
||||
|
||||
// I thought these could be @AppStorage keys but apparently not, because SwiftUI would subscribe to updates from those if
|
||||
// they aren't being used directly inside the view.
|
||||
@Published var isVegetarian: Bool {
|
||||
didSet { UserDefaults.standard.set(isVegetarian, forKey: "isVegetarian") }
|
||||
}
|
||||
|
||||
@Published var isVegan: Bool {
|
||||
didSet { UserDefaults.standard.set(isVegan, forKey: "isVegan") }
|
||||
}
|
||||
|
||||
@Published var noBeef: Bool {
|
||||
didSet { UserDefaults.standard.set(noBeef, forKey: "noBeef") }
|
||||
}
|
||||
|
||||
@Published var noPork: Bool {
|
||||
didSet { UserDefaults.standard.set(noPork, forKey: "noPork") }
|
||||
}
|
||||
|
||||
init() {
|
||||
self.isVegetarian = UserDefaults.standard.bool(forKey: "isVegetarian")
|
||||
self.isVegan = UserDefaults.standard.bool(forKey: "isVegan")
|
||||
self.noBeef = UserDefaults.standard.bool(forKey: "noBeef")
|
||||
self.noPork = UserDefaults.standard.bool(forKey: "noPork")
|
||||
}
|
||||
}
|
||||
38
TigerDine/Data/NotifyingChefs.swift
Normal file
38
TigerDine/Data/NotifyingChefs.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// NotifyingChefs.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 10/1/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
class NotifyingChefs {
|
||||
private var notifyingChefs: Set<String>
|
||||
private let key = "NotifyingChefs"
|
||||
|
||||
init() {
|
||||
let chefs = UserDefaults.standard.array(forKey: key) as? [String] ?? [String]()
|
||||
notifyingChefs = Set(chefs)
|
||||
}
|
||||
|
||||
func contains(_ chef: String) -> Bool {
|
||||
notifyingChefs.contains(chef.lowercased())
|
||||
}
|
||||
|
||||
func add(_ chef: String) {
|
||||
notifyingChefs.insert(chef.lowercased())
|
||||
save()
|
||||
}
|
||||
|
||||
func remove(_ chef: String) {
|
||||
notifyingChefs.remove(chef.lowercased())
|
||||
save()
|
||||
}
|
||||
|
||||
func save() {
|
||||
let chefs = Array(notifyingChefs)
|
||||
UserDefaults.standard.set(chefs, forKey: key)
|
||||
}
|
||||
}
|
||||
86
TigerDine/Data/PushesModel.swift
Normal file
86
TigerDine/Data/PushesModel.swift
Normal file
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// PushesModel.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 11/20/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@Observable
|
||||
class VisitingChefPushesModel {
|
||||
var pushes: [ScheduledVistingChefPush] = [] {
|
||||
didSet {
|
||||
save()
|
||||
}
|
||||
}
|
||||
private let key = "ScheduledVisitingChefPushes"
|
||||
|
||||
init() {
|
||||
load()
|
||||
}
|
||||
|
||||
/// Schedule a new push notification with the notification center and save its information to UserDefaults if it succeeded.
|
||||
func scheduleNewPush(name: String, location: String, startTime: Date, endTime: Date) async {
|
||||
guard !pushAlreadyRegisered(name: name, location: location, startTime: startTime, endTime: endTime) else { return }
|
||||
let uuid_string = await scheduleVisitingChefNotif(
|
||||
name: name,
|
||||
location: location,
|
||||
startTime: startTime,
|
||||
endTime: endTime
|
||||
)
|
||||
// An empty UUID means that the notification wasn't scheduled for one reason or another. This is ignored for now.
|
||||
if uuid_string != "" {
|
||||
pushes.append(
|
||||
ScheduledVistingChefPush(
|
||||
uuid: uuid_string,
|
||||
name: name,
|
||||
location: location,
|
||||
startTime: startTime,
|
||||
endTime: endTime
|
||||
)
|
||||
)
|
||||
save()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancel all reigstered push notifications for a specified visiting chef.
|
||||
func cancelPushesForChef(name: String) {
|
||||
var uuids: [String] = []
|
||||
for push in pushes {
|
||||
if push.name == name {
|
||||
uuids.append(push.uuid)
|
||||
}
|
||||
}
|
||||
Task {
|
||||
await cancelVisitingChefNotifs(uuids: uuids)
|
||||
}
|
||||
// Once they're canceled, we can drop them from the list.
|
||||
pushes.removeAll { $0.name == name }
|
||||
save()
|
||||
}
|
||||
|
||||
func pushAlreadyRegisered(name: String, location: String, startTime: Date, endTime: Date) -> Bool {
|
||||
for push in pushes {
|
||||
if push.name == name && push.location == location && push.startTime == startTime && push.endTime == endTime {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func save() {
|
||||
let encoder = JSONEncoder()
|
||||
if let data = try? encoder.encode(pushes) {
|
||||
UserDefaults.standard.set(data, forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
private func load() {
|
||||
let decoder = JSONDecoder()
|
||||
if let data = UserDefaults.standard.data(forKey: key),
|
||||
let decoded = try? decoder.decode([ScheduledVistingChefPush].self, from: data) {
|
||||
pushes = decoded
|
||||
}
|
||||
}
|
||||
}
|
||||
16
TigerDine/Data/Static/FDMPMealPeriods.swift
Normal file
16
TigerDine/Data/Static/FDMPMealPeriods.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// FDMPMealPeriods.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 11/8/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let fdmpMealPeriodsMap: [Int: String] = [
|
||||
1: "Breakfast",
|
||||
2: "Lunch",
|
||||
3: "Dinner",
|
||||
6: "Late Night",
|
||||
8: "All Day",
|
||||
]
|
||||
25
TigerDine/Data/Static/TCtoFDMPMap.swift
Normal file
25
TigerDine/Data/Static/TCtoFDMPMap.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// TCtoFDMPMap.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 11/8/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Maps the IDs used by TigerCenter to the locationId and accountId values used by FD MealPlanner. This is used to get menus for locations from their detail views.
|
||||
let tCtoFDMPMap: [Int: (Int, Int)] = [
|
||||
// These are ordered based on the way that they're ordered in the FD MealPlanner search API response.
|
||||
30: (1, 1), // Artesano
|
||||
31: (2, 2), // Beanz
|
||||
23: (7, 7), // Crossroads
|
||||
25: (8, 8), // Cantina
|
||||
34: (6, 6), // Ctr-Alt-DELi
|
||||
21: (10, 10), // Gracie's
|
||||
22: (4, 4), // Brick City Cafe
|
||||
441: (11, 11), // Loaded Latke
|
||||
38: (12, 12), // Midnight Oil
|
||||
26: (14, 4), // RITZ
|
||||
35: (18, 17), // The College Grind
|
||||
24: (15, 14), // The Commons
|
||||
]
|
||||
124
TigerDine/Data/Types/FDMealPlannerTypes.swift
Normal file
124
TigerDine/Data/Types/FDMealPlannerTypes.swift
Normal file
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// FDMealPlannerTypes.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 11/3/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Struct to parse the response from the FDMP search API. This API returns all of the dining locations that are have menus available and the required IDs needed to get those menus.
|
||||
struct FDSearchResponseParser: Decodable {
|
||||
/// The main response body containing the result count and the results themselves.
|
||||
struct Data: Decodable {
|
||||
/// The key information returned for each location in the search results. These values are required to pass along to the menu API.
|
||||
struct Result: Decodable {
|
||||
let locationId: Int
|
||||
let accountId: Int
|
||||
let tenantId: Int
|
||||
let locationName: String
|
||||
let locationCode: String
|
||||
let locationDisplayName: String
|
||||
let accountName: String
|
||||
}
|
||||
let result: [Result]
|
||||
let totalCount: Int
|
||||
}
|
||||
let success: Bool
|
||||
let errorMessage: String?
|
||||
let data: Data
|
||||
}
|
||||
|
||||
/// Struct to parse the response from the FDMP meal periods API. This API returns all potentail meal periods for a location based on its ID. This meal period ID is required to get the menu for that meal period from the meals API.
|
||||
struct FDMealPeriodsParser: Decodable {
|
||||
/// The response body, which is a list of responses that include a meal period and the ID that maps to it.
|
||||
struct Data: Decodable {
|
||||
let id: Int
|
||||
let mealPeriodName: String
|
||||
}
|
||||
let success: Bool
|
||||
let errorMessage: String?
|
||||
let data: [Data]
|
||||
}
|
||||
|
||||
/// Struct to parse the response from the FDMP meals API. This API contains the actual menu information for the specified location during the specified meal period. It doesn't contain every menu item, but it's the best source of menu information that I can access.
|
||||
struct FDMealsParser: Decodable, Hashable {
|
||||
/// The actual response body.
|
||||
struct Result: Decodable, Hashable {
|
||||
/// An individual item on the menu at this location and its information.
|
||||
struct MenuRecipe: Decodable, Hashable {
|
||||
let componentName: String
|
||||
let componentId: Int
|
||||
let componentTypeId: Int
|
||||
let englishAlternateName: String
|
||||
let category: String
|
||||
let allergenName: String
|
||||
let calories: String
|
||||
let carbohydrates: String
|
||||
let carbohydratesUOM: String
|
||||
let dietaryFiber: String
|
||||
let dietaryFiberUOM: String
|
||||
let fat: String
|
||||
let fatUOM: String
|
||||
let protein: String
|
||||
let proteinUOM: String
|
||||
let saturatedFat: String
|
||||
let saturatedFatUOM: String
|
||||
let transFattyAcid: String
|
||||
let transFattyAcidUOM: String
|
||||
let calcium: String
|
||||
let calciumUOM: String
|
||||
let cholesterol: String
|
||||
let cholesterolUOM: String
|
||||
let iron: String
|
||||
let ironUOM: String
|
||||
let sodium: String
|
||||
let sodiumUOM: String
|
||||
let vitaminA: String
|
||||
let vitaminAUOM: String
|
||||
let vitaminC: String
|
||||
let vitaminCUOM: String
|
||||
let totalSugars: String
|
||||
let totalSugarsUOM: String
|
||||
let recipeProductDietaryName: String
|
||||
let ingredientStatement: String
|
||||
let sellingPrice: Double
|
||||
let productMeasuringSize: Int
|
||||
let productMeasuringSizeUnit: String
|
||||
let itemsToOrder: Int
|
||||
}
|
||||
let menuId: Int
|
||||
let menuForDate: String
|
||||
let menuToDate: String
|
||||
let accountId: Int
|
||||
let accountName: String
|
||||
let menuTypeName: String
|
||||
let mealPeriodId: Int
|
||||
let allMenuRecipes: [MenuRecipe]?
|
||||
}
|
||||
let responseStatus: String?
|
||||
let result: [Result]
|
||||
}
|
||||
|
||||
/// A single nutritional entry, including the amount and the unit. Used over a tuple for hashable purposes.
|
||||
struct FDNutritionalEntry: Hashable {
|
||||
let type: String
|
||||
let amount: Double
|
||||
let unit: String
|
||||
}
|
||||
|
||||
/// A single menu item, stripped down and reorganized to a format that actually makes sense for me to use in the rest of the app.
|
||||
struct FDMenuItem: Hashable, Identifiable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let exactName: String
|
||||
let category: String
|
||||
let allergens: [String]
|
||||
let calories: Int
|
||||
let nutritionalEntries: [FDNutritionalEntry]
|
||||
let dietaryMarkers: [String]
|
||||
let ingredients: String
|
||||
let price: Double
|
||||
let servingSize: Int
|
||||
let servingSizeUnit: String
|
||||
}
|
||||
18
TigerDine/Data/Types/FoodTruckTypes.swift
Normal file
18
TigerDine/Data/Types/FoodTruckTypes.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// FoodTruckTypes.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 11/3/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A weekend food trucks even representing when it's happening and what food trucks will be there.
|
||||
struct FoodTruckEvent: Hashable {
|
||||
let date: Date
|
||||
let openTime: Date
|
||||
let closeTime: Date
|
||||
let location: String
|
||||
let trucks: [String]
|
||||
}
|
||||
|
||||
17
TigerDine/Data/Types/PushTypes.swift
Normal file
17
TigerDine/Data/Types/PushTypes.swift
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// PushTypes.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 11/20/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Struct to represent a visiting chef notification that has already been scheduled, allowing it to be loaded again later to recall what notifications have been scheduled.
|
||||
struct ScheduledVistingChefPush: Codable, Equatable {
|
||||
let uuid: String
|
||||
let name: String
|
||||
let location: String
|
||||
let startTime: Date
|
||||
let endTime: Date
|
||||
}
|
||||
136
TigerDine/Data/Types/TigerCenterTypes.swift
Normal file
136
TigerDine/Data/Types/TigerCenterTypes.swift
Normal file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// TigerCenterTypes.swift
|
||||
// TigerDine
|
||||
//
|
||||
// Created by Campbell on 9/2/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Struct to parse the response data from the TigerCenter API when getting the information for a dining location.
|
||||
struct DiningLocationParser: Decodable {
|
||||
/// An individual "event", which is just an open period for the location.
|
||||
struct Event: Decodable {
|
||||
/// Hour exceptions for the given event.
|
||||
struct HoursException: Decodable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let startTime: String
|
||||
let endTime: String
|
||||
let startDate: String
|
||||
let endDate: String
|
||||
let open: Bool
|
||||
}
|
||||
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 visiting chefs have descriptions but specials do not.
|
||||
struct Menu: Decodable {
|
||||
let name: String
|
||||
let description: String?
|
||||
let category: String
|
||||
}
|
||||
/// 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
|
||||
let mapsUrl: String
|
||||
let events: [Event]
|
||||
let menus: [Menu]
|
||||
}
|
||||
|
||||
/// Struct that probably doesn't need to exist but this made parsing the list of location responses easy.
|
||||
struct DiningLocationsParser: Decodable {
|
||||
let locations: [DiningLocationParser]
|
||||
}
|
||||
|
||||
/// Enum to represent the four possible states a given location can be in.
|
||||
enum OpenStatus {
|
||||
case open
|
||||
case closed
|
||||
case openingSoon
|
||||
case closingSoon
|
||||
}
|
||||
|
||||
/// An individual open period for a location.
|
||||
struct DiningTimes: Equatable, Hashable {
|
||||
var openTime: Date
|
||||
var closeTime: Date
|
||||
}
|
||||
|
||||
/// Enum to represent the five possible states a visiting chef can be in.
|
||||
enum VisitingChefStatus {
|
||||
case hereNow
|
||||
case gone
|
||||
case arrivingLater
|
||||
case arrivingSoon
|
||||
case leavingSoon
|
||||
}
|
||||
|
||||
/// A visiting chef present at a location.
|
||||
struct VisitingChef: Equatable, Hashable {
|
||||
let name: String
|
||||
let description: String
|
||||
var openTime: Date
|
||||
var closeTime: Date
|
||||
var status: VisitingChefStatus
|
||||
}
|
||||
|
||||
/// A daily special at a location.
|
||||
struct DailySpecial: Equatable, Hashable {
|
||||
let name: String
|
||||
let type: String
|
||||
}
|
||||
|
||||
/// The IDs required to get the menu for a location from FD MealPlanner. Only present if the location appears in the map.
|
||||
struct FDMPIds: Hashable {
|
||||
let locationId: Int
|
||||
let accountId: Int
|
||||
}
|
||||
|
||||
/// 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 fdmpIds: FDMPIds?
|
||||
let name: String
|
||||
let summary: String
|
||||
let desc: String
|
||||
let mapsUrl: String
|
||||
let date: Date
|
||||
let diningTimes: [DiningTimes]?
|
||||
var open: OpenStatus
|
||||
var visitingChefs: [VisitingChef]?
|
||||
let dailySpecials: [DailySpecial]?
|
||||
}
|
||||
|
||||
/// Parser to read the occupancy data for a location.
|
||||
struct DiningOccupancyParser: Decodable {
|
||||
/// Represents a per-hour occupancy rating.
|
||||
struct HourlyOccupancy: Decodable {
|
||||
let hour: Int
|
||||
let today: Int
|
||||
let today_max: Int
|
||||
let one_week_ago: Int
|
||||
let one_week_ago_max: Int
|
||||
let average: Int
|
||||
}
|
||||
let count: Int
|
||||
let location: String
|
||||
let building: String
|
||||
let mdo_id: Int
|
||||
let max_occ: Int
|
||||
let open_status: String
|
||||
let intra_loc_hours: [HourlyOccupancy]
|
||||
}
|
||||
|
||||
/// Struct used to represent a day and its hours as strings. Type used for the hours of today and the next 6 days used in DetailView.
|
||||
struct WeeklyHours: Hashable {
|
||||
let day: String
|
||||
let date: Date
|
||||
let timeStrings: [String]
|
||||
}
|
||||
Reference in New Issue
Block a user