mirror of
https://github.com/NinjaCheetah/RIT-Dining.git
synced 2026-03-05 05:25:29 -05:00
Added filtering options to dining menu view
There is now a filter button by the search bar in the menu view for locations! This opens a menu that allows you to filter by diets and to filter out any allergens that you need to avoid. These options are all written to UserDefaults, allowing you to set your options once and have them persist across menus and sessions. Also started on some refactors, these will be furthered in a later commit.
This commit is contained in:
@@ -24,12 +24,13 @@ struct AboutView: View {
|
||||
.font(.subheadline)
|
||||
Text("Version \(appVersionString) (\(buildNumber))")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom, 2)
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text("Dining locations, their descriptions, and their opening hours are sourced from the RIT student-run TigerCenter API. Building occupancy information is sourced from the official RIT maps API.")
|
||||
Text("This app is not affiliated, associated, authorized, endorsed by, or in any way officially connected with the Rochester Institute of Technology. This app is student created and maintained.")
|
||||
HStack {
|
||||
Button(action: {
|
||||
openURL(URL(string: "https://github.com/NinjaCheetah/RIT-Dining")!)
|
||||
openURL(URL(string: "https://github.com/NinjaCheetah/TigerDine")!)
|
||||
}) {
|
||||
Text("Source Code")
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ struct DonationView: View {
|
||||
.fontWeight(.bold)
|
||||
}
|
||||
.font(.title)
|
||||
Text("The RIT Dining app is free and open source software!")
|
||||
Text("The TigerDine app is free and open source software!")
|
||||
.fontWeight(.bold)
|
||||
.multilineTextAlignment(.center)
|
||||
Text("However, the Apple Developer Program is expensive, and I paid $106.19 pretty much just to distribute this app and nothing else. If you can, I'd appreciate it if you wouldn't mind tossing a coin or two my way to help and make that expense a little less painful.")
|
||||
|
||||
83
RIT Dining/Views/Menus/MenuDietaryRestrictionsSheet.swift
Normal file
83
RIT Dining/Views/Menus/MenuDietaryRestrictionsSheet.swift
Normal file
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// MenuDietaryRestrictionsSheet.swift
|
||||
// RIT Dining
|
||||
//
|
||||
// Created by Campbell on 11/11/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MenuDietaryRestrictionsSheet: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Binding var dietaryRestrictionsModel: MenuDietaryRestrictionsModel
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
Section(header: Text("Diet")) {
|
||||
Toggle(isOn: Binding(
|
||||
get: {
|
||||
dietaryRestrictionsModel.filteredDietaryMarkers.contains("Beef")
|
||||
},
|
||||
set: { isOn in
|
||||
if isOn {
|
||||
dietaryRestrictionsModel.filteredDietaryMarkers.insert("Beef")
|
||||
} else {
|
||||
dietaryRestrictionsModel.filteredDietaryMarkers.remove("Beef")
|
||||
}
|
||||
} )
|
||||
) {
|
||||
Text("No Beef")
|
||||
}
|
||||
Toggle(isOn: Binding(
|
||||
get: {
|
||||
dietaryRestrictionsModel.filteredDietaryMarkers.contains("Pork")
|
||||
},
|
||||
set: { isOn in
|
||||
if isOn {
|
||||
dietaryRestrictionsModel.filteredDietaryMarkers.insert("Pork")
|
||||
} else {
|
||||
dietaryRestrictionsModel.filteredDietaryMarkers.remove("Pork")
|
||||
}
|
||||
} )
|
||||
) {
|
||||
Text("No Pork")
|
||||
}
|
||||
Toggle(isOn: $dietaryRestrictionsModel.isVegetarian) {
|
||||
Text("Vegetarian")
|
||||
}
|
||||
Toggle(isOn: $dietaryRestrictionsModel.isVegan) {
|
||||
Text("Vegan")
|
||||
}
|
||||
}
|
||||
Section(header: Text("Allergens")) {
|
||||
ForEach(Allergen.allCases, id: \.self) { allergen in
|
||||
Toggle(isOn: Binding(
|
||||
get: {
|
||||
dietaryRestrictionsModel.dietaryRestrictions.contains(allergen)
|
||||
},
|
||||
set: { isOn in
|
||||
if isOn {
|
||||
dietaryRestrictionsModel.dietaryRestrictions.add(allergen)
|
||||
} else {
|
||||
dietaryRestrictionsModel.dietaryRestrictions.remove(allergen)
|
||||
}
|
||||
}
|
||||
)) {
|
||||
Text(allergen.rawValue.capitalized)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Menu Filters")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}) {
|
||||
Image(systemName: "xmark")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,9 +68,6 @@ struct MenuItemView: View {
|
||||
.foregroundStyle(.secondary)
|
||||
.textSelection(.enabled)
|
||||
.padding(.bottom, 12)
|
||||
.onAppear {
|
||||
print(menuItem.allergens)
|
||||
}
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Text("Nutrition Facts")
|
||||
@@ -17,6 +17,8 @@ struct MenuView: View {
|
||||
@State private var rotationDegrees: Double = 0
|
||||
@State private var selectedMealPeriod: Int = 0
|
||||
@State private var openPeriods: [Int] = []
|
||||
@State private var dietaryRestrictionsModel = MenuDietaryRestrictionsModel()
|
||||
@State private var showingDietaryRestrictionsSheet: Bool = false
|
||||
|
||||
private var animation: Animation {
|
||||
.linear
|
||||
@@ -63,6 +65,42 @@ struct MenuView: View {
|
||||
|
||||
private var filteredMenuItems: [FDMenuItem] {
|
||||
var newItems = menuItems
|
||||
// Filter out dietary restrictions, starting with pork/beef since those are tagged.
|
||||
if !dietaryRestrictionsModel.filteredDietaryMarkers.isEmpty {
|
||||
newItems = newItems.filter { item in
|
||||
for marker in dietaryRestrictionsModel.filteredDietaryMarkers {
|
||||
if item.dietaryMarkers.contains(marker) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Filter out allergens.
|
||||
newItems = newItems.filter { item in
|
||||
if !item.allergens.isEmpty {
|
||||
for allergen in item.allergens {
|
||||
if let checkingAllergen = Allergen(rawValue: allergen.lowercased()) {
|
||||
if dietaryRestrictionsModel.dietaryRestrictions.contains(checkingAllergen) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
// Filter down to vegetarian/vegan only if enabled.
|
||||
if dietaryRestrictionsModel.isVegetarian || dietaryRestrictionsModel.isVegan {
|
||||
newItems = newItems.filter { item in
|
||||
if dietaryRestrictionsModel.isVegetarian && (item.dietaryMarkers.contains("Vegetarian") || item.dietaryMarkers.contains("Vegan")) {
|
||||
return true
|
||||
} else if dietaryRestrictionsModel.isVegan && (item.dietaryMarkers.contains("Vegan")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Filter down to search contents.
|
||||
newItems = newItems.filter { item in
|
||||
let searchedLocations = searchText.isEmpty || item.name.localizedCaseInsensitiveContains(searchText)
|
||||
return searchedLocations
|
||||
@@ -164,6 +202,20 @@ struct MenuView: View {
|
||||
Image(systemName: "clock")
|
||||
}
|
||||
}
|
||||
ToolbarItemGroup(placement: .bottomBar) {
|
||||
Button(action: {
|
||||
showingDietaryRestrictionsSheet = true
|
||||
}) {
|
||||
Image(systemName: "line.3.horizontal.decrease")
|
||||
}
|
||||
if #unavailable(iOS 26.0) {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
if #available(iOS 26.0, *) {
|
||||
ToolbarSpacer(.flexible, placement: .bottomBar)
|
||||
DefaultToolbarItem(kind: .search, placement: .bottomBar)
|
||||
}
|
||||
}
|
||||
.onChange(of: selectedMealPeriod) {
|
||||
rotationDegrees = 0
|
||||
@@ -172,6 +224,9 @@ struct MenuView: View {
|
||||
await getMenuForPeriod(mealPeriodId: selectedMealPeriod)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showingDietaryRestrictionsSheet) {
|
||||
MenuDietaryRestrictionsSheet(dietaryRestrictionsModel: $dietaryRestrictionsModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user