Skip to content

Commit 2c04d5a

Browse files
authored
Merge pull request #439 from pennlabs/native-dining-menus
Added native dining menus
2 parents e96eb7b + d064f2c commit 2c04d5a

15 files changed

Lines changed: 372 additions & 260 deletions

File tree

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"info" : {
3-
"version" : 1,
4-
"author" : "xcode"
3+
"author" : "xcode",
4+
"version" : 1
55
}
6-
}
6+
}
Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
{
2-
"info" : {
3-
"version" : 1,
4-
"author" : "xcode"
5-
},
62
"colors" : [
73
{
8-
"idiom" : "universal",
94
"color" : {
105
"platform" : "ios",
116
"reference" : "systemGray6Color"
12-
}
7+
},
8+
"idiom" : "universal"
139
},
1410
{
15-
"idiom" : "universal",
1611
"appearances" : [
1712
{
1813
"appearance" : "luminosity",
1914
"value" : "dark"
2015
}
2116
],
2217
"color" : {
23-
"platform" : "ios",
24-
"reference" : "systemGray6Color"
25-
}
18+
"color-space" : "srgb",
19+
"components" : {
20+
"alpha" : "1.000",
21+
"blue" : "0.118",
22+
"green" : "0.110",
23+
"red" : "0.110"
24+
}
25+
},
26+
"idiom" : "universal"
2627
}
27-
]
28-
}
28+
],
29+
"info" : {
30+
"author" : "xcode",
31+
"version" : 1
32+
},
33+
"properties" : {
34+
"localizable" : true
35+
}
36+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "250",
9+
"green" : "250",
10+
"red" : "250"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0.060",
27+
"green" : "0.055",
28+
"red" : "0.055"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
{
2-
"info" : {
3-
"version" : 1,
4-
"author" : "xcode"
5-
},
62
"colors" : [
73
{
8-
"idiom" : "universal",
94
"color" : {
105
"platform" : "ios",
116
"reference" : "systemBackgroundColor"
12-
}
7+
},
8+
"idiom" : "universal"
139
},
1410
{
15-
"idiom" : "universal",
1611
"appearances" : [
1712
{
1813
"appearance" : "luminosity",
1914
"value" : "dark"
2015
}
2116
],
2217
"color" : {
23-
"platform" : "ios",
24-
"reference" : "systemBackgroundColor"
25-
}
18+
"color-space" : "extended-gray",
19+
"components" : {
20+
"alpha" : "1.000",
21+
"white" : "0.000"
22+
}
23+
},
24+
"idiom" : "universal"
2625
}
27-
]
28-
}
26+
],
27+
"info" : {
28+
"author" : "xcode",
29+
"version" : 1
30+
}
31+
}

PennMobile/Dining/Model/DiningMenu.swift

Lines changed: 35 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -8,105 +8,62 @@
88

99
import Foundation
1010

11-
struct DiningMenuAPIResponse: Codable {
11+
struct MenuList: Codable {
1212
static let directory = "diningMenus.json"
1313

14-
let document: Document
15-
16-
enum CodingKeys: String, CodingKey {
17-
case document = "Document"
18-
}
19-
20-
struct Document: Codable {
21-
let dateString: String
22-
let menuDocument: MenuDocument
23-
24-
enum CodingKeys: String, CodingKey {
25-
case dateString = "menudate"
26-
case menuDocument = "tblMenu"
27-
}
28-
}
29-
}
30-
31-
struct MenuDocument: Codable {
3214
let menus: [DiningMenu]
33-
34-
enum CodingKeys: String, CodingKey {
35-
case menus = "tblDayPart"
36-
}
3715
}
3816

3917
struct DiningMenu: Codable, Hashable {
40-
41-
let mealType: String
42-
let diningStations: [DiningStation]
18+
let venueInfo: VenueInfo
19+
let date: Date
20+
let startTime: String
21+
let endTime: String
22+
let stations: [DiningStation]
23+
let service: String
4324

4425
enum CodingKeys: String, CodingKey {
45-
case mealType = "txtDayPartDescription"
46-
case diningStations = "tblStation"
26+
case venueInfo = "venue"
27+
case date
28+
case startTime = "start_time"
29+
case endTime = "end_time"
30+
case stations
31+
case service
4732
}
4833
}
4934

50-
struct DiningStation: Codable, Hashable {
51-
let stationDescription: String
52-
let diningStationItems: [DiningStationItem]
53-
35+
struct VenueInfo: Codable, Hashable {
36+
let id: Int
37+
let name: String
38+
let image: String
39+
5440
enum CodingKeys: String, CodingKey {
55-
case stationDescription = "txtStationDescription"
56-
case diningStationItems = "tblItem"
41+
case id = "venue_id"
42+
case name
43+
case image = "image_url"
5744
}
5845
}
5946

60-
struct DiningStationItem: Codable, Hashable {
61-
62-
let tableAttribute: Attribute
63-
let title: String
64-
let description: String
47+
struct DiningStation: Codable, Hashable {
48+
let name: String
49+
let items: [DiningStationItem]
6550

6651
enum CodingKeys: String, CodingKey {
67-
case tableAttribute = "tblAttributes"
68-
case title = "txtTitle"
69-
case description = "txtDescription"
70-
}
71-
72-
init(from decoder: Decoder) throws {
73-
let container = try decoder.container(keyedBy: CodingKeys.self)
74-
75-
if let data = try? container.decode(Attribute.self, forKey: .tableAttribute) {
76-
self.tableAttribute = data
77-
} else {
78-
self.tableAttribute = Attribute()
79-
}
80-
81-
// self.tblFarmToFork = try! container.decode(String.self, forKey: .tblFarmToFork)
82-
self.title = try container.decode(String.self, forKey: .title)
83-
self.description = try container.decode(String.self, forKey: .description)
52+
case name
53+
case items
8454
}
8555
}
8656

87-
struct Attribute: Codable, Hashable {
88-
init() {
89-
attributeDescriptions = []
90-
}
91-
92-
let attributeDescriptions: [AttributeDescription]
57+
struct DiningStationItem: Codable, Hashable {
58+
let id: Int
59+
let name: String
60+
let desc: String
61+
let ingredients: String
9362

9463
enum CodingKeys: String, CodingKey {
95-
case attributeDescriptions = "txtAttribute"
64+
case id = "item_id"
65+
case name
66+
case desc = "description"
67+
case ingredients
9668
}
97-
98-
init(from decoder: Decoder) throws {
99-
let container = try decoder.container(keyedBy: CodingKeys.self)
100-
101-
if let data = try? container.decode(AttributeDescription.self, forKey: .attributeDescriptions) {
102-
self.attributeDescriptions = [data]
103-
} else {
104-
let data = try container.decode([AttributeDescription].self, forKey: .attributeDescriptions)
105-
self.attributeDescriptions = data
106-
}
107-
}
108-
}
109-
110-
struct AttributeDescription: Codable, Hashable {
111-
let description: String
11269
}

PennMobile/Dining/Model/DiningVenue+Extensions.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ extension DiningVenue {
7070
var currentOrNearestMealIndex: Int {
7171
return self.mealsToday?.meals.firstIndex(where: { $0.isCurrentlyServing }) ?? self.mealsToday?.meals.firstIndex(where: { $0.starttime > Date() }) ?? 0
7272
}
73+
74+
var currentOrNearestMeal: Meal? {
75+
return self.mealsToday?.meals.first(where: { $0.isCurrentlyServing }) ?? (self.mealsToday?.meals.first(where: { $0.starttime > Date() }) ?? nil)
76+
}
7377

7478
var hasMealsToday: Bool {
7579
return mealsToday != nil

PennMobile/Dining/Networking + Cache/DiningAPI.swift

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ class DiningAPI: Requestable {
1414
static let instance = DiningAPI()
1515

1616
let diningUrl = "https://pennmobile.org/api/dining/venues/"
17-
let diningMenuUrl = "https://pennmobile.org/api/dining/daily_menu/"
18-
// TODO: Broken API, need to fetch locally
17+
let diningMenuUrl = "https://pennmobile.org/api/dining/menus/"
1918

2019
let diningInsightsUrl = "https://pennmobile.org/api/dining/"
2120

@@ -35,8 +34,22 @@ class DiningAPI: Requestable {
3534
}
3635
}
3736

38-
func fetchDiningMenu(for id: Int, at date: Date = Date(), _ completion: @escaping (_ result: Result<DiningMenuAPIResponse, NetworkingError>) -> Void) {
39-
return completion(.failure(.parsingError))
37+
func fetchDiningMenus(at date: Date = Date()) async -> Result<[MenuList], NetworkingError> {
38+
let dateFormatter = DateFormatter()
39+
dateFormatter.dateFormat = "yyyy-MM-dd"
40+
let dateStr = dateFormatter.string(from: date)
41+
guard let (data, _) = try? await URLSession.shared.data(from: URL(string: diningMenuUrl + dateStr + "/")!) else {
42+
return .failure(.serverError)
43+
}
44+
let decoder = JSONDecoder()
45+
decoder.dateDecodingStrategy = .formatted(dateFormatter)
46+
if let diningMenus = try? decoder.decode([DiningMenu].self, from: data) {
47+
let menus: [Int: [DiningMenu]] = Dictionary(grouping: diningMenus, by: { $0.venueInfo.id })
48+
let result = menus.values.map { MenuList(menus: $0) }
49+
return .success(result)
50+
} else {
51+
return .failure(.parsingError)
52+
}
4053
}
4154
}
4255

@@ -62,10 +75,10 @@ extension DiningAPI {
6275
func getVenues<T: Collection>(with ids: T) -> [DiningVenue] where T.Element == Int {
6376
return getVenues().filter({ ids.contains($0.id) })
6477
}
65-
66-
func getMenus() -> [Int: DiningMenuAPIResponse] {
67-
if Storage.fileExists(DiningMenuAPIResponse.directory, in: .caches) {
68-
return Storage.retrieve(DiningMenuAPIResponse.directory, from: .caches, as: [Int: DiningMenuAPIResponse].self)
78+
79+
func getMenus() -> [Int: MenuList] {
80+
if Storage.fileExists(MenuList.directory, in: .caches) {
81+
return Storage.retrieve(MenuList.directory, from: .caches, as: [Int: MenuList].self)
6982
} else {
7083
return [:]
7184
}
@@ -76,15 +89,21 @@ extension DiningAPI {
7689
Storage.store(venues, to: .caches, as: DiningVenue.directory)
7790
}
7891

79-
func saveToCache(id: Int, _ menu: DiningMenuAPIResponse) {
80-
if Storage.fileExists(DiningMenuAPIResponse.directory, in: .caches) {
81-
var menus = Storage.retrieve(DiningMenuAPIResponse.directory, from: .caches, as: [Int: DiningMenuAPIResponse].self)
92+
func saveMenuToCache(id: Int, _ menu: MenuList) {
93+
if Storage.fileExists(MenuList.directory, in: .caches) {
94+
var menus = Storage.retrieve(MenuList.directory, from: .caches, as: [Int: MenuList].self)
8295

8396
menus[id] = menu
8497

85-
Storage.store(menus, to: .caches, as: DiningMenuAPIResponse.directory)
98+
Storage.store(menus, to: .caches, as: MenuList.directory)
8699
} else {
87-
Storage.store([id: menu], to: .caches, as: DiningMenuAPIResponse.directory)
100+
Storage.store([id: menu], to: .caches, as: MenuList.directory)
101+
}
102+
}
103+
104+
func saveAllMenusToCache(menus: [Int: MenuList]) {
105+
for (id, menu) in menus {
106+
self.saveMenuToCache(id: id, menu)
88107
}
89108
}
90109
}

PennMobile/Dining/SwiftUI/DiningAnalyticsViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ class DiningAnalyticsViewModel: ObservableObject {
6161
case .failure:
6262
return
6363
case .success(let balanceList):
64-
let planStartDateResult = await DiningAPI.instance.getDiningPlanStartDate(diningToken: diningToken)
6564
if startDateStr != self.formatter.string(from: Date()) {
6665
let newDollarHistory = balanceList.map({DiningAnalyticsBalance(date: self.formatter.date(from: $0.date)!, balance: Double($0.diningDollars) ?? 0.0)})
6766
let newSwipeHistory = balanceList.map({DiningAnalyticsBalance(date: self.formatter.date(from: $0.date)!, balance: Double($0.regularVisits))})
@@ -81,6 +80,7 @@ class DiningAnalyticsViewModel: ObservableObject {
8180
// If no dining plan found, refresh will return, these are just placeholders
8281
var startDollarBalance = maxDollarBalance
8382
var startSwipeBalance = maxSwipeBalance
83+
let planStartDateResult = await DiningAPI.instance.getDiningPlanStartDate(diningToken: diningToken)
8484
switch planStartDateResult {
8585
case .failure:
8686
return

0 commit comments

Comments
 (0)