From 919d85ccd91853fd859945cb641c496134264c23 Mon Sep 17 00:00:00 2001 From: jmelitski Date: Tue, 20 Feb 2024 20:43:13 -0500 Subject: [PATCH 01/12] Initial update for aesthetics: todo: nav bar updates with scrolling, change view for each dining station. --- Gemfile.lock | 1 + .../DiningVenueDetailMenuView.swift | 119 ++++++++-------- .../Detail View/DiningVenueDetailView.swift | 134 +++++++++--------- .../Detail View/MenuDisclosureGroup.swift | 74 ++++++---- PennMobileShared/Dining/DiningAPI.swift | 3 + .../Dining/Models/DiningMenu.swift | 95 +++++++++++++ 6 files changed, 276 insertions(+), 150 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index fd5fff904..697542f12 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -276,6 +276,7 @@ GEM PLATFORMS universal-darwin-21 universal-darwin-22 + universal-darwin-23 x86_64-darwin-19 x86_64-linux diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift index 5a759ed5e..94ec525cd 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift @@ -14,92 +14,93 @@ struct DiningVenueDetailMenuView: View { var menus: [DiningMenu] var id: Int var venue: DiningVenue? + var globalScrollProxy: ScrollViewProxy @State var menuDate: Date - @State private var menuIndex: Int + @State private var currentMenu: DiningMenu @State private var showMenu: Bool + @State private var selectedStation: DiningStation? @EnvironmentObject var diningVM: DiningViewModelSwiftUI - init(menus: [DiningMenu], id: Int, venue: DiningVenue? = nil, menuDate: Date = Date(), showMenu: Bool = false) { + init(menus: [DiningMenu], id: Int, venue: DiningVenue? = nil, menuDate: Date = Date(), globalScrollProxy: ScrollViewProxy, showMenu: Bool = false) { self.menus = menus self.id = id self.venue = venue + self.globalScrollProxy = globalScrollProxy _showMenu = State(initialValue: showMenu) _menuDate = State(initialValue: menuDate) - _menuIndex = State(initialValue: 0) - _menuIndex = State(initialValue: self.getIndex()) + _currentMenu = State(initialValue: menus[0]) + _currentMenu = State(initialValue: self.getMenu()) + _selectedStation = State(initialValue: currentMenu.stations.first ?? nil) } - func getIndex() -> Int { + func getMenu() -> DiningMenu { var inx = 0 if self.venue != nil && Calendar.current.isDate(self.menuDate, inSameDayAs: Date()) { if let meal = self.venue!.currentOrNearestMeal { inx = self.menus.firstIndex { $0.service == meal.label } ?? inx } } - return inx + return menus[inx] } - var body: some View { - DatePicker(selection: $menuDate, in: Date()...Date().addingTimeInterval(86400 * 6), displayedComponents: .date) { - Text("Menu date") - }.onChange(of: menuDate) { newMenuDate in - menuIndex = 0 - Task.init() { - await diningVM.refreshMenus(cache: false, at: newMenuDate) - } - if Calendar.current.isDate(newMenuDate, inSameDayAs: Date()) { - menuIndex = getIndex() - } - } - VStack { - Button { - showMenu.toggle() - } label: { - CardView { - HStack { - Text("Menu") - .font(.system(size: 20, design: .rounded)) - .bold() - Spacer() - Image(systemName: "chevron.right") + LazyVStack(pinnedViews: [.sectionHeaders]) { + // Date Picker and Meal selector + HStack { + if menus.count > 0 { + Picker("Menu", selection: self.$currentMenu) { + ForEach(menus, id: \.self) { menu in + Text(menu.service) + } } - .padding() - .foregroundColor(.blue).font(Font.system(size: 24).weight(.bold)) + .pickerStyle(MenuPickerStyle()) } - .frame(height: 24) - .padding([.top, .bottom]) - } - .sheet(isPresented: $showMenu) { - MenuWebView(url: URL(string: DiningVenue.menuUrlDict[id] ?? "https://university-of-pennsylvania.cafebonappetit.com/")!) - } - if menus.count > 0 { - Picker("Menu", selection: self.$menuIndex) { - ForEach(0 ..< menus.count, id: \.self) { - Text(menus[$0].service) + + DatePicker(selection: $menuDate, in: Date()...Date().addingTimeInterval(86400 * 6), displayedComponents: .date) { + } + .onChange(of: menuDate) { newMenuDate in + currentMenu = menus[0] + + Task.init() { + await diningVM.refreshMenus(cache: false, at: newMenuDate) + } + if Calendar.current.isDate(newMenuDate, inSameDayAs: Date()) { + currentMenu = getMenu() } } - .pickerStyle(SegmentedPickerStyle()) - DiningMenuRow(diningMenu: menus[menuIndex]) - .transition(.opacity) } - } - } -} - -struct DiningVenueDetailMenuView_Previews: PreviewProvider { - let diningVenues: MenuList = Bundle.main.decode("mock_menu.json") - - static var previews: some View { - return NavigationView { - ScrollView { - VStack { - DiningVenueDetailMenuView(menus: [], id: 1) - Spacer() + Section { + ForEach(currentMenu.stations, id: \.self) { station in + DiningStationRow(diningStation: station) + .bold(selectedStation != nil && selectedStation! == station) } - }.navigationTitle("Dining") - .padding() + } header: { + DiningMenuViewHeader(diningMenu: $currentMenu, selectedStation: $selectedStation) + .onChange(of: currentMenu) { _ in + selectedStation = currentMenu.stations.first ?? nil + } + } + }.onChange(of: selectedStation) { _ in + withAnimation { + globalScrollProxy.scrollTo(selectedStation!, anchor: .top) + } } } } +//struct DiningVenueDetailMenuView_Previews: PreviewProvider { +// let diningVenues: MenuList = Bundle.main.decode("mock_menu.json") +// +// static var previews: some View { +// return NavigationView { +// ScrollView { +// VStack { +// DiningVenueDetailMenuView(menus: [], id: 1) +// Spacer() +// } +// }.navigationTitle("Dining") +// .padding() +// } +// } +//} + struct MenuWebView: UIViewRepresentable { let url: URL func makeUIView(context: Context) -> WKWebView { diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift index da7343ab2..e1fbeb1ec 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift @@ -29,77 +29,79 @@ struct DiningVenueDetailView: View { GeometryReader { fullGeo in let imageHeight = fullGeo.size.height * 4/9 let isFavorite = diningVM.favoriteVenues.contains { $0.id == venue.id } - - ScrollView { - GeometryReader { geometry in - let minY = geometry.frame(in: .global).minY - - ZStack(alignment: .bottomLeading) { - KFImage(self.venue.image) - .resizable() - .scaledToFill() - .frame(width: geometry.size.width, height: imageHeight + max(0, minY)) - .offset(y: min(0, minY) * -2/3) - .allowsHitTesting(false) - .clipped() - - LinearGradient(gradient: Gradient(colors: [.black.opacity(0.6), .black.opacity(0.2), .clear, .black.opacity(0.3), .black]), startPoint: .init(x: 0.5, y: 0.2), endPoint: .init(x: 0.5, y: 1)) - - Text(venue.name) - .padding() - .foregroundColor(.white) - .font(.system(size: 40, weight: .bold)) - .minimumScaleFactor(0.2) - .lineLimit(1) - .background(GeometryReader { geometry in - let minY = geometry.frame(in: .global).minY - Color.clear.onChange(of: minY) { minY in - showTitle = minY <= 64 - } - }) - } - .offset(y: -max(0, minY)) - } - .edgesIgnoringSafeArea(.all) - .frame(height: imageHeight) - .zIndex(2) - - VStack(spacing: 10) { - Picker("Section", selection: self.$pickerIndex) { - ForEach(0 ..< self.sectionTitle.count, id: \.self) { - Text(self.sectionTitle[$0]) + ScrollViewReader { fullReader in + ScrollView { + // Image and Name + GeometryReader { geometry in + let minY = geometry.frame(in: .global).minY + + ZStack(alignment: .bottomLeading) { + KFImage(self.venue.image) + .resizable() + .scaledToFill() + .frame(width: geometry.size.width, height: imageHeight + max(0, minY)) + .offset(y: min(0, minY) * -2/3) + .allowsHitTesting(false) + .clipped() + + LinearGradient(gradient: Gradient(colors: [.black.opacity(0.6), .black.opacity(0.2), .clear, .black.opacity(0.3), .black]), startPoint: .init(x: 0.5, y: 0.2), endPoint: .init(x: 0.5, y: 1)) + + Text(venue.name) + .padding() + .foregroundColor(.white) + .font(.system(size: 40, weight: .bold)) + .minimumScaleFactor(0.2) + .lineLimit(1) + .background(GeometryReader { geometry in + let minY = geometry.frame(in: .global).minY + Color.clear.onChange(of: minY) { minY in + showTitle = minY <= 64 + } + }) } + .offset(y: -max(0, minY)) } - .pickerStyle(SegmentedPickerStyle()) - - Divider() - - VStack { - if self.pickerIndex == 0 { - DiningVenueDetailMenuView(menus: diningVM.diningMenus[venue.id]?.menus ?? [], id: venue.id, venue: venue) - } else if self.pickerIndex == 1 { - DiningVenueDetailHoursView(for: venue) - } else { - DiningVenueDetailLocationView(for: venue, screenHeight: fullGeo.size.width) + .edgesIgnoringSafeArea(.all) + .frame(height: imageHeight) + .zIndex(2) + + VStack(spacing: 10) { + Picker("Section", selection: self.$pickerIndex) { + ForEach(0 ..< self.sectionTitle.count, id: \.self) { + Text(self.sectionTitle[$0]) + } } - - Spacer() - }.frame(minHeight: fullGeo.size.height - 80) - }.padding(.horizontal) - } - .navigationTitle(Text(showTitle ? venue.name : "")) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem { - Button(action: isFavorite ? { diningVM.removeVenueFromFavorites(venue: venue) } : { diningVM.addVenueToFavorites(venue: venue) }) { - Image(systemName: isFavorite ? "star.fill" : "star") - .font(.system(size: 20, weight: .light)) + .pickerStyle(SegmentedPickerStyle()) + + Divider() + + VStack { + if self.pickerIndex == 0 { + DiningVenueDetailMenuView(menus: diningVM.diningMenus[venue.id]?.menus ?? [], id: venue.id, venue: venue, globalScrollProxy: fullReader) + } else if self.pickerIndex == 1 { + DiningVenueDetailHoursView(for: venue) + } else { + DiningVenueDetailLocationView(for: venue, screenHeight: fullGeo.size.width) + } + + Spacer() + }.frame(minHeight: fullGeo.size.height - 80) + }.padding(.horizontal) + } + .navigationTitle(Text(showTitle ? venue.name : "")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem { + Button(action: isFavorite ? { diningVM.removeVenueFromFavorites(venue: venue) } : { diningVM.addVenueToFavorites(venue: venue) }) { + Image(systemName: isFavorite ? "star.fill" : "star") + .font(.system(size: 20, weight: .light)) + } + .tint(.yellow) } - .tint(.yellow) } - } - .onAppear { - FirebaseAnalyticsManager.shared.trackScreen("Venue Detail View") + .onAppear { + FirebaseAnalyticsManager.shared.trackScreen("Venue Detail View") + } } } } diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift index 71ca7adda..70e733be6 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift @@ -9,25 +9,49 @@ import SwiftUI import PennMobileShared -struct DiningMenuRow: View { - var diningMenu: DiningMenu + + +struct DiningMenuViewHeader: View { + @Binding var diningMenu: DiningMenu + @Binding var selectedStation: DiningStation? + var body: some View { - VStack(spacing: 0) { - ForEach(diningMenu.stations, id: \.self) { diningStation in - DiningStationRow(for: diningStation) + VStack { + Divider() + ScrollViewReader { proxy in + ScrollView(.horizontal) { + HStack(spacing: 15) { + ForEach(diningMenu.stations, id: \.hashValue) { diningStation in + Text(diningStation.name.uppercased()) + .bold(selectedStation != nil && selectedStation == diningStation) + .underline(selectedStation != nil && selectedStation == diningStation) + .font(.system(size: 16)) + .onTapGesture { + withAnimation { + selectedStation = diningStation + } + }.onAppear { + print(diningStation.hashValue) + } + } + }.onChange(of: selectedStation) { _ in + withAnimation { + proxy.scrollTo(selectedStation!.hashValue, anchor: .leading) + } + + } + } + .padding(.vertical, 2) } - } - .background(Color.grey7.cornerRadius(8)) + Divider() + }.background(Color(.systemBackground)) } } struct DiningStationRow: View { @State var isExpanded = false let diningStation: DiningStation - init (for diningStation: DiningStation) { - self.diningStation = diningStation - } var body: some View { VStack(spacing: 0) { @@ -176,21 +200,21 @@ extension AnyTransition { } } -struct MenuDisclosureGroup_Previews: PreviewProvider { - static var previews: some View { - let diningVenues: MenuList = Bundle.main.decode("mock_menu.json") - - return NavigationView { - ScrollView { - VStack { - DiningVenueDetailMenuView(menus: diningVenues.menus, id: 1) - Spacer() - } - }.navigationTitle("Dining") - .padding() - } - } -} +//struct MenuDisclosureGroup_Previews: PreviewProvider { +// static var previews: some View { +// let diningVenues: MenuList = Bundle.main.decode("mock_menu.json") +// +// return NavigationView { +// ScrollView { +// VStack { +// DiningVenueDetailMenuView(menus: diningVenues.menus, id: 1) +// Spacer() +// } +// }.navigationTitle("Dining") +// .padding() +// } +// } +//} extension String { func capitalizeMainWords() -> String { diff --git a/PennMobileShared/Dining/DiningAPI.swift b/PennMobileShared/Dining/DiningAPI.swift index 697f72691..d595580aa 100644 --- a/PennMobileShared/Dining/DiningAPI.swift +++ b/PennMobileShared/Dining/DiningAPI.swift @@ -8,6 +8,7 @@ import SwiftyJSON import Foundation +import JavaScriptCore public class DiningAPI { public static let defaultVenueIds: [Int] = [593, 636, 1442, 639] @@ -26,6 +27,8 @@ public class DiningAPI { return .failure(.serverError) } + + let decoder = JSONDecoder() let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" diff --git a/PennMobileShared/Dining/Models/DiningMenu.swift b/PennMobileShared/Dining/Models/DiningMenu.swift index a5782b561..3ed190160 100644 --- a/PennMobileShared/Dining/Models/DiningMenu.swift +++ b/PennMobileShared/Dining/Models/DiningMenu.swift @@ -34,6 +34,22 @@ public struct DiningMenu: Codable, Hashable { case stations case service } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.venueInfo = try container.decode(VenueInfo.self, forKey: .venueInfo) + self.date = try container.decode(Date.self, forKey: .date) + self.startTime = try container.decode(String.self, forKey: .startTime) + self.endTime = try container.decode(String.self, forKey: .endTime) + self.service = try container.decode(String.self, forKey: .service) + + self.stations = try container.decode([DiningStation].self, forKey: .stations).sorted { + if (DiningStation.getWeight(station: $0) == DiningStation.getWeight(station: $1)) { + return $0.name > $1.name + } + return DiningStation.getWeight(station: $0) < DiningStation.getWeight(station: $1) + } + } } public struct VenueInfo: Codable, Hashable { @@ -56,6 +72,85 @@ public struct DiningStation: Codable, Hashable { case name case items } + + public static func getWeight(station: DiningStation) -> Int { + let dictionary: [String:Int] = [ + // 1-10 Specials + "chef's table":1, + "special events":2, + "expo":3, + "expo toppings":4, + "feature entree":5, + "vegan feature entrée":6, + "smoothie bar":7, + "cafe specials":8, + + + // 11-25 Larger Entree Stations + "grill": 11, + "vegan grill": 12, + "pennsburg grill": 13, + "hill grill lunch": 14, + "hill grill lunch sides": 15, + "smoque'd deli": 16, + "pizza": 17, + "flatbread": 18, + + //26 - 49 Smaller Entrees + "salad bar": 26, + "salads": 27, + "insalata": 28, + "catch": 29, + "breakfast bar": 30, + "breakfast": 31, + "rise and dine": 32, + "global fusion": 33, + "global fusion sides": 34, + "near & far": 35, + "mezze": 36, + "grotto":37, + "simplyoasis": 38, + "simplyoasis sides": 39, + "comfort": 40, + "melts": 41, + + // 50 IS THE DEFAULT + + // 51 - 60 Sides + "kettles": 51, + "fruit plus": 52, + "hand fruit": 53, + "fruit & yogurt": 54, + "fruit and yogurt": 55, + "breads and bagels": 56, + "breads and toast": 57, + "cereal": 58, + + // 61 - 65 Desserts + "sweets & treats": 61, + "sweets and treats": 62, + "ice cream": 63, + "ice cream bar": 64, + "dessert": 65, + + // 66 - 70 Assorted Toppings + "condiments": 66, + "on the side": 67, + "dairy comfort": 68, + "flavors": 69, + "vegan flavors": 70, + + // 71 - 75 Drinks + "beverages": 71, + "coffee": 72 + ] + + return dictionary[station.name.lowercased()] ?? 50 + + } + + + } public struct DiningStationItem: Codable, Hashable { From bd465eca543d5f5a5a378e12a772119fd3b45184 Mon Sep 17 00:00:00 2001 From: jmelitski Date: Mon, 26 Feb 2024 19:43:27 -0500 Subject: [PATCH 02/12] Initial update to dining UI. Still need to update individual stations but the scrolling logic is almost complete (except for the header scrolling when the overall list is interacted with). --- .../DiningVenueDetailMenuView.swift | 126 +++++++++--------- .../Detail View/DiningVenueDetailView.swift | 3 +- .../Detail View/MenuDisclosureGroup.swift | 39 +++--- .../Dining/Models/DiningMenu.swift | 10 ++ .../Models/DiningVenue+Extensions.swift | 4 +- 5 files changed, 96 insertions(+), 86 deletions(-) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift index 94ec525cd..483a4a1ba 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift @@ -2,8 +2,8 @@ // DiningVenueDetailMenuView.swift // PennMobile // -// Created by CHOI Jongmin on 23/6/2020. -// Copyright © 2020 PennLabs. All rights reserved. +// Created by Jon Melitski on 2/26/2024. +// Copyright © 2024 PennLabs. All rights reserved. // import SwiftUI @@ -11,96 +11,94 @@ import WebKit import PennMobileShared struct DiningVenueDetailMenuView: View { - var menus: [DiningMenu] + + @EnvironmentObject var diningVM: DiningViewModelSwiftUI + var id: Int - var venue: DiningVenue? - var globalScrollProxy: ScrollViewProxy + var venue: DiningVenue + var parentScrollProxy: ScrollViewProxy + + /// Notable invariant, the menuDate must ALWAYS match all of the menus in the array. @State var menuDate: Date - @State private var currentMenu: DiningMenu - @State private var showMenu: Bool + @State var menus: [DiningMenu] + + // Both are nil on init + @State private var currentMenu: DiningMenu? @State private var selectedStation: DiningStation? - @EnvironmentObject var diningVM: DiningViewModelSwiftUI - init(menus: [DiningMenu], id: Int, venue: DiningVenue? = nil, menuDate: Date = Date(), globalScrollProxy: ScrollViewProxy, showMenu: Bool = false) { - self.menus = menus + + init(menus: [DiningMenu], id: Int, venue: DiningVenue, menuDate: Date = Date(), parentScrollProxy: ScrollViewProxy) { self.id = id self.venue = venue - self.globalScrollProxy = globalScrollProxy - _showMenu = State(initialValue: showMenu) + self.parentScrollProxy = parentScrollProxy + _menus = State(initialValue: menus) _menuDate = State(initialValue: menuDate) - _currentMenu = State(initialValue: menus[0]) - _currentMenu = State(initialValue: self.getMenu()) - _selectedStation = State(initialValue: currentMenu.stations.first ?? nil) + _currentMenu = State(initialValue: getMenu()) + _selectedStation = State(initialValue: currentMenu?.stations.first ?? nil) } - func getMenu() -> DiningMenu { - var inx = 0 - if self.venue != nil && Calendar.current.isDate(self.menuDate, inSameDayAs: Date()) { - if let meal = self.venue!.currentOrNearestMeal { - inx = self.menus.firstIndex { $0.service == meal.label } ?? inx - } + + /// Constraints of this function: + /// Need to know if a meal is currently going on, to return it. + /// If there is a meal today that is closest (utilities), return it. + /// If the selected date is not the current day, return the first menu. + /// If at any point, the list of menus is empty, return nil. + func getMenu() -> DiningMenu? { + if (menus.count == 0) { return nil } + + if (!Calendar.current.isDate(menuDate, inSameDayAs: Date())) { + return menus[0] + } + + guard let nearestIndex = venue.currentOrNearestMealIndex else { + return nil } - return menus[inx] + + return menus[nearestIndex] } + var body: some View { LazyVStack(pinnedViews: [.sectionHeaders]) { - // Date Picker and Meal selector HStack { - if menus.count > 0 { - Picker("Menu", selection: self.$currentMenu) { + if currentMenu != nil { + Picker("Menu", selection: Binding($currentMenu)!) { ForEach(menus, id: \.self) { menu in Text(menu.service) } - } - .pickerStyle(MenuPickerStyle()) - } - - DatePicker(selection: $menuDate, in: Date()...Date().addingTimeInterval(86400 * 6), displayedComponents: .date) { - } - .onChange(of: menuDate) { newMenuDate in - currentMenu = menus[0] - - Task.init() { - await diningVM.refreshMenus(cache: false, at: newMenuDate) - } - if Calendar.current.isDate(newMenuDate, inSameDayAs: Date()) { - currentMenu = getMenu() - } + }.pickerStyle(MenuPickerStyle()) + } else { + Text("Closed Today") } + DatePicker("", selection: $menuDate, in: Date()...Date().add(minutes: 8640), displayedComponents: .date) } Section { - ForEach(currentMenu.stations, id: \.self) { station in + ForEach(currentMenu?.stations ?? [], id: \.vertUID) { station in DiningStationRow(diningStation: station) - .bold(selectedStation != nil && selectedStation! == station) + .bold(selectedStation != nil && selectedStation == station) + } + .onChange(of: selectedStation) { new in + withAnimation { + parentScrollProxy.scrollTo(new!.vertUID, anchor: .top) + } } } header: { DiningMenuViewHeader(diningMenu: $currentMenu, selectedStation: $selectedStation) - .onChange(of: currentMenu) { _ in - selectedStation = currentMenu.stations.first ?? nil - } } - }.onChange(of: selectedStation) { _ in - withAnimation { - globalScrollProxy.scrollTo(selectedStation!, anchor: .top) + } + + .onChange(of: currentMenu) { _ in + print((currentMenu?.service ?? "no menu") + " on " + menuDate.description) + selectedStation = currentMenu?.stations.first ?? nil + } + .onChange(of: menuDate) { newDate in + Task.init() { + await diningVM.refreshMenus(cache: false, at: newDate) + menuDate = newDate + menus = diningVM.diningMenus[venue.id]?.menus ?? [] + currentMenu = getMenu() } } } } -//struct DiningVenueDetailMenuView_Previews: PreviewProvider { -// let diningVenues: MenuList = Bundle.main.decode("mock_menu.json") -// -// static var previews: some View { -// return NavigationView { -// ScrollView { -// VStack { -// DiningVenueDetailMenuView(menus: [], id: 1) -// Spacer() -// } -// }.navigationTitle("Dining") -// .padding() -// } -// } -//} - struct MenuWebView: UIViewRepresentable { let url: URL func makeUIView(context: Context) -> WKWebView { diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift index e1fbeb1ec..efc3fdaf0 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift @@ -77,13 +77,12 @@ struct DiningVenueDetailView: View { VStack { if self.pickerIndex == 0 { - DiningVenueDetailMenuView(menus: diningVM.diningMenus[venue.id]?.menus ?? [], id: venue.id, venue: venue, globalScrollProxy: fullReader) + DiningVenueDetailMenuView(menus: diningVM.diningMenus[venue.id]?.menus ?? [], id: venue.id, venue: venue, parentScrollProxy: fullReader) } else if self.pickerIndex == 1 { DiningVenueDetailHoursView(for: venue) } else { DiningVenueDetailLocationView(for: venue, screenHeight: fullGeo.size.width) } - Spacer() }.frame(minHeight: fullGeo.size.height - 80) }.padding(.horizontal) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift index 70e733be6..f3eaacf2f 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift @@ -9,48 +9,51 @@ import SwiftUI import PennMobileShared - - - struct DiningMenuViewHeader: View { - @Binding var diningMenu: DiningMenu + @Binding var diningMenu: DiningMenu? @Binding var selectedStation: DiningStation? + @State var internalSelection: DiningStation? + var body: some View { VStack { Divider() ScrollViewReader { proxy in - ScrollView(.horizontal) { - HStack(spacing: 15) { - ForEach(diningMenu.stations, id: \.hashValue) { diningStation in + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 30) { + ForEach(diningMenu?.stations ?? [], id: \.horizUID) { diningStation in Text(diningStation.name.uppercased()) .bold(selectedStation != nil && selectedStation == diningStation) .underline(selectedStation != nil && selectedStation == diningStation) .font(.system(size: 16)) .onTapGesture { - withAnimation { - selectedStation = diningStation - } - }.onAppear { - print(diningStation.hashValue) + internalSelection = diningStation } - } - }.onChange(of: selectedStation) { _ in - withAnimation { - proxy.scrollTo(selectedStation!.hashValue, anchor: .leading) - } + }.onChange(of: internalSelection) { new in + withAnimation { + proxy.scrollTo(new!.horizUID, anchor: .leading) + } + + selectedStation = internalSelection + } } } .padding(.vertical, 2) } Divider() }.background(Color(.systemBackground)) + .onAppear { + internalSelection = selectedStation + } + .onChange(of: selectedStation) { _ in + internalSelection = selectedStation + } } } struct DiningStationRow: View { - @State var isExpanded = false + @State var isExpanded = true let diningStation: DiningStation var body: some View { diff --git a/PennMobileShared/Dining/Models/DiningMenu.swift b/PennMobileShared/Dining/Models/DiningMenu.swift index 3ed190160..a50b5e6b7 100644 --- a/PennMobileShared/Dining/Models/DiningMenu.swift +++ b/PennMobileShared/Dining/Models/DiningMenu.swift @@ -67,6 +67,16 @@ public struct VenueInfo: Codable, Hashable { public struct DiningStation: Codable, Hashable { public let name: String public let items: [DiningStationItem] + public let vertUID: UUID + public let horizUID: UUID + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.items = try container.decode([DiningStationItem].self, forKey: .items) + self.vertUID = UUID() + self.horizUID = UUID() + } public enum CodingKeys: String, CodingKey { case name diff --git a/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift b/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift index f7b89997a..0f9a82c16 100755 --- a/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift +++ b/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift @@ -64,8 +64,8 @@ public extension DiningVenue { return self.mealsToday?.meals.firstIndex(where: { $0.isCurrentlyServing }) } - var currentOrNearestMealIndex: Int { - return self.mealsToday?.meals.firstIndex(where: { $0.isCurrentlyServing }) ?? self.mealsToday?.meals.firstIndex(where: { $0.starttime > Date() }) ?? 0 + var currentOrNearestMealIndex: Int? { + return self.mealsToday?.meals.firstIndex(where: { $0.isCurrentlyServing }) ?? self.mealsToday?.meals.firstIndex(where: { $0.starttime > Date() }) ?? nil } var currentOrNearestMeal: Meal? { From db088f0826d79347046b506bcfa6cb69f71bfd6e Mon Sep 17 00:00:00 2001 From: jmelitski Date: Mon, 26 Feb 2024 23:39:55 -0500 Subject: [PATCH 03/12] This code is functional. Still need to re-organize the Stations themselves, but the scrolling logic is 98% complete. --- .../DiningVenueDetailMenuView.swift | 27 ++++--- .../Detail View/DiningVenueDetailView.swift | 20 ++++- .../Detail View/MenuDisclosureGroup.swift | 80 ++++++++++++++++++- 3 files changed, 113 insertions(+), 14 deletions(-) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift index 483a4a1ba..368f6ac58 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift @@ -26,14 +26,18 @@ struct DiningVenueDetailMenuView: View { @State private var currentMenu: DiningMenu? @State private var selectedStation: DiningStation? - init(menus: [DiningMenu], id: Int, venue: DiningVenue, menuDate: Date = Date(), parentScrollProxy: ScrollViewProxy) { + @Binding private var parentScrollOffset: CGPoint + + init(menus: [DiningMenu], id: Int, venue: DiningVenue, menuDate: Date = Date(), parentScrollProxy: ScrollViewProxy, parentScrollOffset: Binding) { self.id = id self.venue = venue self.parentScrollProxy = parentScrollProxy + _parentScrollOffset = parentScrollOffset _menus = State(initialValue: menus) _menuDate = State(initialValue: menuDate) _currentMenu = State(initialValue: getMenu()) _selectedStation = State(initialValue: currentMenu?.stations.first ?? nil) + } /// Constraints of this function: @@ -65,23 +69,23 @@ struct DiningVenueDetailMenuView: View { } }.pickerStyle(MenuPickerStyle()) } else { - Text("Closed Today") + Text("Closed For Today") } DatePicker("", selection: $menuDate, in: Date()...Date().add(minutes: 8640), displayedComponents: .date) } + Section { - ForEach(currentMenu?.stations ?? [], id: \.vertUID) { station in - DiningStationRow(diningStation: station) - .bold(selectedStation != nil && selectedStation == station) - } - .onChange(of: selectedStation) { new in - withAnimation { - parentScrollProxy.scrollTo(new!.vertUID, anchor: .top) - } - } + DiningStationRowStack(selectedStation: $selectedStation, currentMenu: $currentMenu, parentScrollOffset: $parentScrollOffset, parentScrollProxy: parentScrollProxy) + + } header: { DiningMenuViewHeader(diningMenu: $currentMenu, selectedStation: $selectedStation) } + +// .onChange(of: parentScrollOffset) { _ in +// print(proxy.size) +// } + } .onChange(of: currentMenu) { _ in @@ -96,6 +100,7 @@ struct DiningVenueDetailMenuView: View { currentMenu = getMenu() } } + } } diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift index efc3fdaf0..024889ca2 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift @@ -23,6 +23,7 @@ struct DiningVenueDetailView: View { @Environment(\.presentationMode) var presentationMode: Binding @EnvironmentObject var diningVM: DiningViewModelSwiftUI @State private var pickerIndex = 0 + @State private var contentOffset: CGPoint = .zero @State var showTitle = false var body: some View { @@ -77,7 +78,8 @@ struct DiningVenueDetailView: View { VStack { if self.pickerIndex == 0 { - DiningVenueDetailMenuView(menus: diningVM.diningMenus[venue.id]?.menus ?? [], id: venue.id, venue: venue, parentScrollProxy: fullReader) + DiningVenueDetailMenuView(menus: diningVM.diningMenus[venue.id]?.menus ?? [], id: venue.id, venue: venue, parentScrollProxy: fullReader, + parentScrollOffset: $contentOffset) } else if self.pickerIndex == 1 { DiningVenueDetailHoursView(for: venue) } else { @@ -86,7 +88,16 @@ struct DiningVenueDetailView: View { Spacer() }.frame(minHeight: fullGeo.size.height - 80) }.padding(.horizontal) + .background(GeometryReader { geometry in + Color.clear + .preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin) + }) + .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in + self.contentOffset = value } + + } + .coordinateSpace(name: "scroll") .navigationTitle(Text(showTitle ? venue.name : "")) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -104,6 +115,13 @@ struct DiningVenueDetailView: View { } } } + + struct ScrollOffsetPreferenceKey: PreferenceKey { + static var defaultValue: CGPoint = .zero + + static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { + } + } } // Hack to enable swipe from left while disabling navigation title diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift index f3eaacf2f..f2c41043e 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift @@ -30,10 +30,11 @@ struct DiningMenuViewHeader: View { internalSelection = diningStation } }.onChange(of: internalSelection) { new in + if let newStation = new { withAnimation { - proxy.scrollTo(new!.horizUID, anchor: .leading) + proxy.scrollTo(newStation.horizUID, anchor: .leading) } - + } selectedStation = internalSelection } @@ -52,6 +53,81 @@ struct DiningMenuViewHeader: View { } } +struct DiningStationRowStack: View { + @Binding var selectedStation: DiningStation? + @Binding var currentMenu: DiningMenu? + + @Binding var parentScrollOffset: CGPoint + var parentScrollProxy: ScrollViewProxy + @State var posDictionary: [DiningStation: CGRect] = [:] + @State var scrollNext: DiningStation? + @State var checkDictionary = true + + var body: some View { + VStack { + ForEach(currentMenu?.stations ?? [], id: \.vertUID) { station in + DiningStationRow(diningStation: station) + .bold(selectedStation != nil && selectedStation == station) + .background { + GeometryReader { proxy in + Spacer() + .onChange(of: parentScrollOffset) { _ in + let thisRect = proxy.frame(in: .global) + + posDictionary.updateValue(thisRect, forKey: station) + } + } + } + } + .onChange(of: parentScrollOffset) { _ in + if (checkDictionary) { + if let defaultStation = selectedStation { + var mostVisible = (defaultStation, 0.0 as CGFloat, 0) + + posDictionary.forEach { station, rect in + let (_, mvMidY, mvPct) = mostVisible + + let intersection = rect.intersection(UIScreen.main.bounds) + let pctInFrame = Int((intersection.height * 100)/rect.height) + + if (pctInFrame > mvPct) { + mostVisible = (station, rect.midY, pctInFrame) + } + + if (pctInFrame == mvPct && abs(rect.midY - 350.0) < abs(mvMidY - 350.0)) { + mostVisible = (station, rect.midY, pctInFrame) + } + } + + let (st, _, _) = mostVisible + scrollNext = st + selectedStation = st + } + } + } + + .onChange(of: currentMenu) { _ in + posDictionary = [:] + } + .onChange(of: selectedStation) { new in + if let newStation = new { + if (newStation != scrollNext) { + checkDictionary = false + withAnimation(.easeInOut(duration: 0.3)) { + parentScrollProxy.scrollTo(newStation.vertUID, anchor: .top) + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + checkDictionary = true + } + } + } + scrollNext = nil + } + } + } +} + struct DiningStationRow: View { @State var isExpanded = true let diningStation: DiningStation From 4da4e096ea65faf28426c13beda83119caaf8ba2 Mon Sep 17 00:00:00 2001 From: jmelitski Date: Mon, 26 Feb 2024 23:48:00 -0500 Subject: [PATCH 04/12] Time zone adjustment --- .../Views/Venue/Detail View/DiningVenueDetailMenuView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift index 368f6ac58..f1911d5eb 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift @@ -28,7 +28,7 @@ struct DiningVenueDetailMenuView: View { @Binding private var parentScrollOffset: CGPoint - init(menus: [DiningMenu], id: Int, venue: DiningVenue, menuDate: Date = Date(), parentScrollProxy: ScrollViewProxy, parentScrollOffset: Binding) { + init(menus: [DiningMenu], id: Int, venue: DiningVenue, menuDate: Date = Date().localTime, parentScrollProxy: ScrollViewProxy, parentScrollOffset: Binding) { self.id = id self.venue = venue self.parentScrollProxy = parentScrollProxy @@ -71,7 +71,7 @@ struct DiningVenueDetailMenuView: View { } else { Text("Closed For Today") } - DatePicker("", selection: $menuDate, in: Date()...Date().add(minutes: 8640), displayedComponents: .date) + DatePicker("", selection: $menuDate, in: Date().localTime...Date().localTime.add(minutes: 8640), displayedComponents: .date) } Section { From 8d617a5e87005d27e6637e1afc60f65071aba0e4 Mon Sep 17 00:00:00 2001 From: jmelitski Date: Tue, 27 Feb 2024 14:28:53 -0500 Subject: [PATCH 05/12] Refactor sorting --- .../DiningVenueDetailMenuView.swift | 4 +- .../Detail View/MenuDisclosureGroup.swift | 63 ++++++++++++------- .../Dining/Models/DiningMenu.swift | 18 +++--- 3 files changed, 49 insertions(+), 36 deletions(-) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift index f1911d5eb..d80c8d523 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift @@ -71,13 +71,11 @@ struct DiningVenueDetailMenuView: View { } else { Text("Closed For Today") } - DatePicker("", selection: $menuDate, in: Date().localTime...Date().localTime.add(minutes: 8640), displayedComponents: .date) + DatePicker("", selection: $menuDate, in: Date()...Date().add(minutes: 8640), displayedComponents: .date) } Section { DiningStationRowStack(selectedStation: $selectedStation, currentMenu: $currentMenu, parentScrollOffset: $parentScrollOffset, parentScrollProxy: parentScrollProxy) - - } header: { DiningMenuViewHeader(diningMenu: $currentMenu, selectedStation: $selectedStation) } diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift index f2c41043e..5a6328341 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift @@ -25,9 +25,12 @@ struct DiningMenuViewHeader: View { Text(diningStation.name.uppercased()) .bold(selectedStation != nil && selectedStation == diningStation) .underline(selectedStation != nil && selectedStation == diningStation) - .font(.system(size: 16)) + .font(.callout) + .padding(3) .onTapGesture { - internalSelection = diningStation + withAnimation { + internalSelection = diningStation + } } }.onChange(of: internalSelection) { new in if let newStation = new { @@ -58,11 +61,12 @@ struct DiningStationRowStack: View { @Binding var currentMenu: DiningMenu? @Binding var parentScrollOffset: CGPoint - var parentScrollProxy: ScrollViewProxy @State var posDictionary: [DiningStation: CGRect] = [:] @State var scrollNext: DiningStation? @State var checkDictionary = true + var parentScrollProxy: ScrollViewProxy + var body: some View { VStack { ForEach(currentMenu?.stations ?? [], id: \.vertUID) { station in @@ -72,47 +76,57 @@ struct DiningStationRowStack: View { GeometryReader { proxy in Spacer() .onChange(of: parentScrollOffset) { _ in - let thisRect = proxy.frame(in: .global) - - posDictionary.updateValue(thisRect, forKey: station) + posDictionary.updateValue(proxy.frame(in: .global), forKey: station) } } } } .onChange(of: parentScrollOffset) { _ in if (checkDictionary) { - if let defaultStation = selectedStation { - var mostVisible = (defaultStation, 0.0 as CGFloat, 0) + /// The most visible element is that which has the highest share of the viewport, + /// relative to its own height. If two elements are equally visible, the one whose + /// midpoint is closest to y = 350 is the one that is more visible. + let sortedDict = posDictionary.sorted { (el1, el2) in + let (_, rect1) = el1 + let (_, rect2) = el2 - posDictionary.forEach { station, rect in - let (_, mvMidY, mvPct) = mostVisible - - let intersection = rect.intersection(UIScreen.main.bounds) - let pctInFrame = Int((intersection.height * 100)/rect.height) - - if (pctInFrame > mvPct) { - mostVisible = (station, rect.midY, pctInFrame) - } - - if (pctInFrame == mvPct && abs(rect.midY - 350.0) < abs(mvMidY - 350.0)) { - mostVisible = (station, rect.midY, pctInFrame) - } - } + let intersection1 = rect1.intersection(UIScreen.main.bounds) + let intersection2 = rect2.intersection(UIScreen.main.bounds) + + let pct1 = Int((intersection1.height * 100)/rect1.height) + let pct2 = Int((intersection2.height * 100)/rect2.height) - let (st, _, _) = mostVisible + return pct1 > pct2 || + (pct1 == pct2 && abs(rect1.midY-350.0) < abs(rect2.midY-350.0)) + } + + if let (st, _) = sortedDict.first { scrollNext = st selectedStation = st } } } - .onChange(of: currentMenu) { _ in posDictionary = [:] } .onChange(of: selectedStation) { new in + /// There's a lot of state changes going on here, becuase there's two + /// ScrollViewReaders interacting. On a change of selection due to vertical scrolling, + /// We don't want to reanimate the vertical scrolling window, since it wouldn't feel natural. + /// + /// Further, on vertical scroll changes as a result of clicking an item on the header bar, + /// we do not wish to adjust the selected station based on scroll (since the user just + /// selected the station) if let newStation = new { + // Don't animate if we just changed this value by vertical scrolling. if (newStation != scrollNext) { + + // Don't adjust anything on scroll while we're animating checkDictionary = false + + /// iOS 17 added an onCompletion method on an animation, but + /// PennMobile is not built for iOS 17 yet, so we are left with just + /// Dispatching an event to fire after the duration of the animation. withAnimation(.easeInOut(duration: 0.3)) { parentScrollProxy.scrollTo(newStation.vertUID, anchor: .top) } @@ -122,6 +136,7 @@ struct DiningStationRowStack: View { } } } + scrollNext = nil } } diff --git a/PennMobileShared/Dining/Models/DiningMenu.swift b/PennMobileShared/Dining/Models/DiningMenu.swift index a50b5e6b7..b7dde620a 100644 --- a/PennMobileShared/Dining/Models/DiningMenu.swift +++ b/PennMobileShared/Dining/Models/DiningMenu.swift @@ -105,6 +105,10 @@ public struct DiningStation: Codable, Hashable { "smoque'd deli": 16, "pizza": 17, "flatbread": 18, + "simplyoasis": 19, + "simplyoasis sides": 20, + "global fusion": 21, + "global fusion sides": 22, //26 - 49 Smaller Entrees "salad bar": 26, @@ -114,15 +118,11 @@ public struct DiningStation: Codable, Hashable { "breakfast bar": 30, "breakfast": 31, "rise and dine": 32, - "global fusion": 33, - "global fusion sides": 34, - "near & far": 35, - "mezze": 36, - "grotto":37, - "simplyoasis": 38, - "simplyoasis sides": 39, - "comfort": 40, - "melts": 41, + "near & far": 33, + "mezze": 34, + "grotto":35, + "comfort": 36, + "melts": 37, // 50 IS THE DEFAULT From 78cc0f9c2c044be355842caf0146e8aa24edc977 Mon Sep 17 00:00:00 2001 From: jmelitski Date: Fri, 22 Mar 2024 16:20:10 -0400 Subject: [PATCH 06/12] Random fixes --- .../DiningVenueDetailMenuView.swift | 4 +- .../Detail View/DiningVenueDetailView.swift | 32 +++- .../Detail View/MenuDisclosureGroup.swift | 82 ++++----- .../Dining/Models/DiningMenu.swift | 155 ++++++++++-------- .../Models/DiningVenue+Extensions.swift | 48 ++++-- 5 files changed, 187 insertions(+), 134 deletions(-) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift index d80c8d523..a09ff7f05 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift @@ -28,7 +28,7 @@ struct DiningVenueDetailMenuView: View { @Binding private var parentScrollOffset: CGPoint - init(menus: [DiningMenu], id: Int, venue: DiningVenue, menuDate: Date = Date().localTime, parentScrollProxy: ScrollViewProxy, parentScrollOffset: Binding) { + init(menus: [DiningMenu], id: Int, venue: DiningVenue, menuDate: Date = Date(), parentScrollProxy: ScrollViewProxy, parentScrollOffset: Binding) { self.id = id self.venue = venue self.parentScrollProxy = parentScrollProxy @@ -92,7 +92,7 @@ struct DiningVenueDetailMenuView: View { } .onChange(of: menuDate) { newDate in Task.init() { - await diningVM.refreshMenus(cache: false, at: newDate) + await diningVM.refreshMenus(cache: true, at: newDate) menuDate = newDate menus = diningVM.diningMenus[venue.id]?.menus ?? [] currentMenu = getMenu() diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift index 024889ca2..8fe0e85af 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailView.swift @@ -10,6 +10,7 @@ import SwiftUI import Kingfisher import FirebaseAnalytics import PennMobileShared +import WebKit struct DiningVenueDetailView: View { @@ -24,6 +25,7 @@ struct DiningVenueDetailView: View { @EnvironmentObject var diningVM: DiningViewModelSwiftUI @State private var pickerIndex = 0 @State private var contentOffset: CGPoint = .zero + @State private var showMenu = false @State var showTitle = false var body: some View { @@ -67,12 +69,24 @@ struct DiningVenueDetailView: View { .zIndex(2) VStack(spacing: 10) { - Picker("Section", selection: self.$pickerIndex) { - ForEach(0 ..< self.sectionTitle.count, id: \.self) { - Text(self.sectionTitle[$0]) + HStack (spacing: 10) { + Picker("Section", selection: self.$pickerIndex) { + ForEach(0 ..< self.sectionTitle.count, id: \.self) { + Text(self.sectionTitle[$0]) + } + } + .pickerStyle(SegmentedPickerStyle()) + + Button { + showMenu.toggle() + } label: { + Image(systemName:"safari") + .font(.largeTitle) + } + .sheet(isPresented: $showMenu) { + WebView(url: URL(string: DiningVenue.menuUrlDict[venue.id] ?? "https://university-of-pennsylvania.cafebonappetit.com/")!) } } - .pickerStyle(SegmentedPickerStyle()) Divider() @@ -132,3 +146,13 @@ extension UINavigationController { interactivePopGestureRecognizer?.delegate = nil } } + +struct WebView: UIViewRepresentable { + let url: URL + func makeUIView(context: Context) -> WKWebView { + return WKWebView() + } + func updateUIView(_ uiView: WKWebView, context: Context) { + uiView.load(URLRequest(url: url)) + } +} diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift index 5a6328341..b9178daaf 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift @@ -146,61 +146,59 @@ struct DiningStationRowStack: View { struct DiningStationRow: View { @State var isExpanded = true let diningStation: DiningStation + var gridColumns: [GridItem] = [] + + init(diningStation: DiningStation) { + self.diningStation = diningStation + + for _ in 0.. $1.name.count}), id: \.self) { item in + DiningStationItemView(item: item) + .padding(4) } - .padding([.top, .bottom]) } } - .background(Color.grey7.cornerRadius(8)) } } -struct DiningMenuSectionRow: View { - @Binding var isExpanded: Bool - let title: String - - init(isExpanded: Binding, title: String) { - self.title = title.capitalizeMainWords() - self._isExpanded = isExpanded - } - +struct DiningStationItemView: View { + let item: DiningStationItem + var body: some View { - HStack { - Text(title) - Spacer() - Image(systemName: "chevron.right") - .rotationEffect(.degrees(isExpanded ? -90 : 90)) - .frame(width: 28, alignment: .center) - } - .contentShape(Rectangle()) - .onTapGesture { - FirebaseAnalyticsManager.shared.trackEvent(action: "Open Menu", result: title, content: "") - withAnimation { - isExpanded.toggle() + VStack(alignment: .leading) { + HStack(alignment: .top) { + Text("• ") + Text(item.name.capitalizeMainWords()) + .font(.headline) + .frame(maxWidth: .infinity, alignment: .topLeading) } + Spacer() } } } +struct DiningMenuSectionRow: View { + let station: DiningStation + + var body: some View { + Text("a") + } +} + struct DiningStationItemRow: View { let diningStationItem: DiningStationItem @@ -238,6 +236,8 @@ struct DiningStationItemRow: View { withAnimation { isExpanded.toggle() } + }.onChange(of: isExpanded) { _ in + print(diningStationItem.desc) } if isExpanded { ItemView(name: name, description: diningStationItem.desc, ingredients: ingredients) diff --git a/PennMobileShared/Dining/Models/DiningMenu.swift b/PennMobileShared/Dining/Models/DiningMenu.swift index b7dde620a..6bd50748a 100644 --- a/PennMobileShared/Dining/Models/DiningMenu.swift +++ b/PennMobileShared/Dining/Models/DiningMenu.swift @@ -64,12 +64,86 @@ public struct VenueInfo: Codable, Hashable { } } + + public struct DiningStation: Codable, Hashable { public let name: String public let items: [DiningStationItem] public let vertUID: UUID public let horizUID: UUID + // Dictionary is (Int, Int), (Weight, Number of Columns) + public static let dictionary: [String:(Int,Int)] = [ + // 1-10 Specials + "chef's table":(1,1), + "special events":(2,1), + "expo":(3,1), + "expo toppings":(4,2), + "feature entree":(5,1), + "vegan feature entrée":(6,1), + "smoothie bar":(7,2), + "cafe specials":(8,1), + + + // 11-25 Larger Entree Stations + "grill": (11,1), + "vegan grill": (12,1), + "pennsburg grill": (13,1), + "hill grill lunch": (14,1), + "hill grill lunch sides": (15,2), + "smoque'd deli": (16,2), + "pizza": (17,1), + "flatbread": (18,1), + "simplyoasis": (19,1), + "simplyoasis sides": (20,2), + "global fusion": (21,1), + "global fusion sides": (22,2), + + //26 - 49 Smaller Entrees + "salad bar": (26,2), + "salads": (27,2), + "insalata": (28,1), + "catch": (29,1), + "breakfast bar": (30,2), + "breakfast": (31,1), + "rise and dine": (32,1), + "near & far": (33,1), + "mezze": (34,2), + "grotto":(35,1), + "comfort": (36,1), + "melts": (37,1), + + // 50 IS THE DEFAULT + + // 51 - 60 Sides + "kettles": (51,1), + "fruit plus": (52,2), + "hand fruit": (53,2), + "fruit & yogurt": (54,2), + "fruit and yogurt": (55,2), + "breads and bagels": (56,2), + "breads and toast": (57,2), + "cereal": (58,2), + + // 61 - 65 Desserts + "sweets & treats": (61,2), + "sweets and treats": (62,2), + "ice cream": (63,2), + "ice cream bar": (64,2), + "dessert": (65,1), + + // 66 - 70 Assorted Toppings + "condiments": (66,2), + "on the side": (67,2), + "dairy comfort": (68,2), + "flavors": (69,2), + "vegan flavors": (70,2), + + // 71 - 75 Drinks + "beverages": (71,2), + "coffee": (72,1) + ] + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.name = try container.decode(String.self, forKey: .name) @@ -83,79 +157,18 @@ public struct DiningStation: Codable, Hashable { case items } + + + public static func getWeight(station: DiningStation) -> Int { - let dictionary: [String:Int] = [ - // 1-10 Specials - "chef's table":1, - "special events":2, - "expo":3, - "expo toppings":4, - "feature entree":5, - "vegan feature entrée":6, - "smoothie bar":7, - "cafe specials":8, - - - // 11-25 Larger Entree Stations - "grill": 11, - "vegan grill": 12, - "pennsburg grill": 13, - "hill grill lunch": 14, - "hill grill lunch sides": 15, - "smoque'd deli": 16, - "pizza": 17, - "flatbread": 18, - "simplyoasis": 19, - "simplyoasis sides": 20, - "global fusion": 21, - "global fusion sides": 22, - - //26 - 49 Smaller Entrees - "salad bar": 26, - "salads": 27, - "insalata": 28, - "catch": 29, - "breakfast bar": 30, - "breakfast": 31, - "rise and dine": 32, - "near & far": 33, - "mezze": 34, - "grotto":35, - "comfort": 36, - "melts": 37, - - // 50 IS THE DEFAULT - - // 51 - 60 Sides - "kettles": 51, - "fruit plus": 52, - "hand fruit": 53, - "fruit & yogurt": 54, - "fruit and yogurt": 55, - "breads and bagels": 56, - "breads and toast": 57, - "cereal": 58, - - // 61 - 65 Desserts - "sweets & treats": 61, - "sweets and treats": 62, - "ice cream": 63, - "ice cream bar": 64, - "dessert": 65, - - // 66 - 70 Assorted Toppings - "condiments": 66, - "on the side": 67, - "dairy comfort": 68, - "flavors": 69, - "vegan flavors": 70, - - // 71 - 75 Drinks - "beverages": 71, - "coffee": 72 - ] + let (weight, _) = dictionary[station.name.lowercased()] ?? (50, 1) + return weight - return dictionary[station.name.lowercased()] ?? 50 + } + + public static func getColumns(station: DiningStation) -> Int { + let (_, cols) = dictionary[station.name.lowercased()] ?? (50, 1) + return cols } diff --git a/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift b/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift index 0f9a82c16..27bc409f4 100755 --- a/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift +++ b/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift @@ -22,24 +22,34 @@ public extension VenueType { public extension DiningVenue { // MARK: - Venue Status - var mealsToday: Day? { + var mealsToday: [Meal] { + + return mealsOnDate(date: Date()) + } + + func mealsOnDate(date: Date) -> [Meal] { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" + return self.days.first(where: { day in - day.date == dateFormatter.string(from: Date()) - }) + day.date == dateFormatter.string(from: date) + })?.meals.sorted(by: { el1, el2 in + return el1.starttime > el2.starttime + }) ?? [] } var isOpen: Bool { - guard let mealsToday = mealsToday else { return false } - for meal in mealsToday.meals where meal.isCurrentlyServing { + if (mealsToday == []) { return false } + + for meal in mealsToday where meal.isCurrentlyServing { return true } + return false } var currentMeal: Meal? { - return self.mealsToday?.meals.first(where: { $0.isCurrentlyServing }) ?? nil + return self.mealsToday.first(where: { $0.isCurrentlyServing }) ?? nil } var currentMealType: String? { @@ -55,25 +65,25 @@ public extension DiningVenue { } var nextMeal: Meal? { - guard let mealsToday = mealsToday else { return nil } - let now = Date() - return mealsToday.meals.first(where: { $0.starttime > now }) + if (!self.hasMealsToday) { return nil } + + return mealsToday.first(where: { $0.starttime > Date() }) } var currentMealIndex: Int? { - return self.mealsToday?.meals.firstIndex(where: { $0.isCurrentlyServing }) + return self.mealsToday.firstIndex(where: { $0.isCurrentlyServing }) } var currentOrNearestMealIndex: Int? { - return self.mealsToday?.meals.firstIndex(where: { $0.isCurrentlyServing }) ?? self.mealsToday?.meals.firstIndex(where: { $0.starttime > Date() }) ?? nil + return self.mealsToday.firstIndex(where: { $0.isCurrentlyServing }) ?? (self.mealsToday.firstIndex(where: { $0.starttime > Date() }) ?? nil) } var currentOrNearestMeal: Meal? { - return self.mealsToday?.meals.first(where: { $0.isCurrentlyServing }) ?? (self.mealsToday?.meals.first(where: { $0.starttime > Date() }) ?? nil) + return self.mealsToday.first(where: { $0.isCurrentlyServing }) ?? (self.mealsToday.first(where: { $0.starttime > Date() }) ?? nil) } var hasMealsToday: Bool { - return mealsToday != nil + return mealsToday != [] } var nextOpenedDayOfTheWeek: String { @@ -96,7 +106,7 @@ public extension DiningVenue { // MARK: - Formatted Hours var humanFormattedHoursStringForToday: String { - guard mealsToday != nil else { return "" } + guard mealsToday != [] else { return "" } return formattedHoursStringFor(Date()) } @@ -154,13 +164,15 @@ public extension DiningVenue { } var humanFormattedHoursArrayForToday: [String] { - guard mealsToday != nil else { return [] } + guard mealsToday != [] else { return [] } return formattedHoursArrayFor(Date()) } func formattedHoursArrayFor(_ date: Date) -> [String] { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" + + let dateString = dateFormatter.string(from: date) return formattedHoursArrayFor(dateString) @@ -182,8 +194,12 @@ public extension DiningVenue { formatter.pmSymbol = "pm" let moreThanOneMeal = meals.count > 1 + + let sortedMeals = meals.sorted(by: { el1, el2 in + return el1.starttime < el2.starttime + }) - for m in meals { + for m in sortedMeals { if m.starttime.minutes == 0 { formatter.dateFormat = moreThanOneMeal ? "h" : "h" } else { From 3e043e52567c91ff565532211d5ea07fe4e8eea8 Mon Sep 17 00:00:00 2001 From: jmelitski Date: Fri, 22 Mar 2024 18:18:41 -0400 Subject: [PATCH 07/12] random fixes --- .../DiningVenueDetailMenuView.swift | 4 ++- .../Detail View/MenuDisclosureGroup.swift | 28 +++++++++++++------ PennMobileShared/Dining/DiningAPI.swift | 1 + 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift index a09ff7f05..5b4e51a3a 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift @@ -77,7 +77,9 @@ struct DiningVenueDetailMenuView: View { Section { DiningStationRowStack(selectedStation: $selectedStation, currentMenu: $currentMenu, parentScrollOffset: $parentScrollOffset, parentScrollProxy: parentScrollProxy) } header: { - DiningMenuViewHeader(diningMenu: $currentMenu, selectedStation: $selectedStation) + VStack { + DiningMenuViewHeader(diningMenu: $currentMenu, selectedStation: $selectedStation) + } } // .onChange(of: parentScrollOffset) { _ in diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift index b9178daaf..6e8313f39 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift @@ -17,14 +17,17 @@ struct DiningMenuViewHeader: View { var body: some View { VStack { - Divider() + Rectangle() + .foregroundStyle(.secondary) + .frame(height: 1) ScrollViewReader { proxy in ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 30) { ForEach(diningMenu?.stations ?? [], id: \.horizUID) { diningStation in Text(diningStation.name.uppercased()) .bold(selectedStation != nil && selectedStation == diningStation) - .underline(selectedStation != nil && selectedStation == diningStation) + //.underline(selectedStation != nil && selectedStation == diningStation) + .foregroundStyle((selectedStation != nil && selectedStation == diningStation) ? .primary : .secondary) .font(.callout) .padding(3) .onTapGesture { @@ -45,7 +48,9 @@ struct DiningMenuViewHeader: View { } .padding(.vertical, 2) } - Divider() + Rectangle() + .foregroundStyle(.secondary) + .frame(height: 1) }.background(Color(.systemBackground)) .onAppear { internalSelection = selectedStation @@ -71,7 +76,6 @@ struct DiningStationRowStack: View { VStack { ForEach(currentMenu?.stations ?? [], id: \.vertUID) { station in DiningStationRow(diningStation: station) - .bold(selectedStation != nil && selectedStation == station) .background { GeometryReader { proxy in Spacer() @@ -157,7 +161,7 @@ struct DiningStationRow: View { } var body: some View { - VStack(alignment: .leading, spacing: 10) { + VStack(alignment: .leading, spacing: 6) { HStack(alignment: .top) { Text(diningStation.name.capitalizeMainWords()) .font(.title) @@ -165,12 +169,20 @@ struct DiningStationRow: View { Spacer() } - LazyVGrid(columns: gridColumns, alignment: .listRowSeparatorLeading, spacing: 10) { + LazyVGrid(columns: gridColumns, alignment: .listRowSeparatorLeading, spacing: 6) { ForEach(diningStation.items.sorted(by: {$0.name.count > $1.name.count}), id: \.self) { item in DiningStationItemView(item: item) .padding(4) } } + .padding(12) + .overlay { + RoundedRectangle(cornerRadius: 16) + .stroke(Color(UIColor.systemGray5)) + } + + + } } } @@ -181,9 +193,9 @@ struct DiningStationItemView: View { var body: some View { VStack(alignment: .leading) { HStack(alignment: .top) { - Text("• ") + //Text("• ") Text(item.name.capitalizeMainWords()) - .font(.headline) + .font(.body) .frame(maxWidth: .infinity, alignment: .topLeading) } Spacer() diff --git a/PennMobileShared/Dining/DiningAPI.swift b/PennMobileShared/Dining/DiningAPI.swift index d595580aa..6aecc653b 100644 --- a/PennMobileShared/Dining/DiningAPI.swift +++ b/PennMobileShared/Dining/DiningAPI.swift @@ -49,6 +49,7 @@ public class DiningAPI { guard let (data, _) = try? await URLSession.shared.data(from: URL(string: diningMenuUrl + dateStr + "/")!) else { return .failure(.serverError) } + print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(dateFormatter) if let diningMenus = try? decoder.decode([DiningMenu].self, from: data) { From 279c7ab206746fc4287ea3522f9f02fc4e00ed59 Mon Sep 17 00:00:00 2001 From: jon0 <35508312+jonathanmelitski@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:35:42 -0400 Subject: [PATCH 08/12] Update PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift Co-authored-by: Anthony Li --- .../Views/Venue/Detail View/DiningVenueDetailMenuView.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift index ac6016cda..197904fe5 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/DiningVenueDetailMenuView.swift @@ -81,11 +81,6 @@ struct DiningVenueDetailMenuView: View { DiningMenuViewHeader(diningMenu: $currentMenu, selectedStation: $selectedStation) } } - -// .onChange(of: parentScrollOffset) { _ in -// print(proxy.size) -// } - } .onChange(of: currentMenu) { _ in From 29f88dac3c8df1f6621f58ccfc66a2296da8345d Mon Sep 17 00:00:00 2001 From: jon0 <35508312+jonathanmelitski@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:35:50 -0400 Subject: [PATCH 09/12] Update PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift Co-authored-by: Anthony Li --- .../Venue/Detail View/MenuDisclosureGroup.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift index 6e8313f39..89abdeccf 100644 --- a/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift +++ b/PennMobile/Dining/SwiftUI/Views/Venue/Detail View/MenuDisclosureGroup.swift @@ -306,21 +306,6 @@ extension AnyTransition { } } -//struct MenuDisclosureGroup_Previews: PreviewProvider { -// static var previews: some View { -// let diningVenues: MenuList = Bundle.main.decode("mock_menu.json") -// -// return NavigationView { -// ScrollView { -// VStack { -// DiningVenueDetailMenuView(menus: diningVenues.menus, id: 1) -// Spacer() -// } -// }.navigationTitle("Dining") -// .padding() -// } -// } -//} extension String { func capitalizeMainWords() -> String { From 063a5b4c8be620d5b6d5334c416150f140d3507b Mon Sep 17 00:00:00 2001 From: jon0 <35508312+jonathanmelitski@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:35:56 -0400 Subject: [PATCH 10/12] Update PennMobileShared/Dining/DiningAPI.swift Co-authored-by: Anthony Li --- PennMobileShared/Dining/DiningAPI.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/PennMobileShared/Dining/DiningAPI.swift b/PennMobileShared/Dining/DiningAPI.swift index 6aecc653b..d595580aa 100644 --- a/PennMobileShared/Dining/DiningAPI.swift +++ b/PennMobileShared/Dining/DiningAPI.swift @@ -49,7 +49,6 @@ public class DiningAPI { guard let (data, _) = try? await URLSession.shared.data(from: URL(string: diningMenuUrl + dateStr + "/")!) else { return .failure(.serverError) } - print(String(data: data, encoding: .utf8)!) let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(dateFormatter) if let diningMenus = try? decoder.decode([DiningMenu].self, from: data) { From 2fbff5e1c901b5e88f2a04cb03136036cc6e6b25 Mon Sep 17 00:00:00 2001 From: jon0 <35508312+jonathanmelitski@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:36:00 -0400 Subject: [PATCH 11/12] Update PennMobileShared/Dining/DiningAPI.swift Co-authored-by: Anthony Li --- PennMobileShared/Dining/DiningAPI.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/PennMobileShared/Dining/DiningAPI.swift b/PennMobileShared/Dining/DiningAPI.swift index d595580aa..416120a7d 100644 --- a/PennMobileShared/Dining/DiningAPI.swift +++ b/PennMobileShared/Dining/DiningAPI.swift @@ -8,7 +8,6 @@ import SwiftyJSON import Foundation -import JavaScriptCore public class DiningAPI { public static let defaultVenueIds: [Int] = [593, 636, 1442, 639] From 31b8c906401bc1886be7130298208838fd55b615 Mon Sep 17 00:00:00 2001 From: jon0 <35508312+jonathanmelitski@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:37:03 -0400 Subject: [PATCH 12/12] Update PennMobileShared/Dining/Models/DiningVenue+Extensions.swift Co-authored-by: Anthony Li --- PennMobileShared/Dining/Models/DiningVenue+Extensions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift b/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift index 27bc409f4..1de1407fb 100755 --- a/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift +++ b/PennMobileShared/Dining/Models/DiningVenue+Extensions.swift @@ -39,7 +39,7 @@ public extension DiningVenue { } var isOpen: Bool { - if (mealsToday == []) { return false } + if mealsToday.isEmpty { return false } for meal in mealsToday where meal.isCurrentlyServing { return true