mirror of
https://github.com/NinjaCheetah/RIT-Dining.git
synced 2025-12-02 01:21:35 -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:
parent
85aa9e636d
commit
32203033b6
@ -265,7 +265,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "RIT Dining/RIT Dining.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "RIT Dining/RIT Dining.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 18;
|
CURRENT_PROJECT_VERSION = 19;
|
||||||
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@ -300,7 +300,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "RIT Dining/RIT Dining.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "RIT Dining/RIT Dining.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 18;
|
CURRENT_PROJECT_VERSION = 19;
|
||||||
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
DEVELOPMENT_TEAM = 5GF7GKNTK4;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
|||||||
51
RIT Dining/Data/DietaryRestrictions.swift
Normal file
51
RIT Dining/Data/DietaryRestrictions.swift
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// DietaryRestrictions.swift
|
||||||
|
// RIT Dining
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
16
RIT Dining/Data/MenuDietaryRestrictionsModel.swift
Normal file
16
RIT Dining/Data/MenuDietaryRestrictionsModel.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// MenuDietaryRestrictionsModel.swift
|
||||||
|
// RIT Dining
|
||||||
|
//
|
||||||
|
// Created by Campbell on 11/11/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@Observable
|
||||||
|
class MenuDietaryRestrictionsModel {
|
||||||
|
var filteredDietaryMarkers: Set<String> = []
|
||||||
|
var dietaryRestrictions = DietaryRestrictions()
|
||||||
|
var isVegetarian: Bool = false
|
||||||
|
var isVegan: Bool = false
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// Model.swift
|
// TigerCenterModel.swift
|
||||||
// RIT Dining
|
// RIT Dining
|
||||||
//
|
//
|
||||||
// Created by Campbell on 10/1/25.
|
// Created by Campbell on 10/1/25.
|
||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// A weekend food trucks even representing when it's happening and what food trucks will be there.
|
/// A weekend food trucks even representing when it's happening and what food trucks will be there.
|
||||||
struct FoodTruckEvent: Hashable {
|
struct FoodTruckEvent: Hashable {
|
||||||
let date: Date
|
let date: Date
|
||||||
let openTime: Date
|
let openTime: Date
|
||||||
|
|||||||
@ -7,12 +7,11 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// I'll be honest, I am NOT good at representing other people's JSON in my code. This kinda sucks but it gets the job done and can
|
/// Struct to parse the response data from the TigerCenter API when getting the information for a dining location.
|
||||||
// be improved later when I feel like it.
|
|
||||||
struct DiningLocationParser: Decodable {
|
struct DiningLocationParser: Decodable {
|
||||||
// An individual "event", which is just an open period for the location.
|
/// An individual "event", which is just an open period for the location.
|
||||||
struct Event: Decodable {
|
struct Event: Decodable {
|
||||||
// Hour exceptions for the given event.
|
/// Hour exceptions for the given event.
|
||||||
struct HoursException: Decodable {
|
struct HoursException: Decodable {
|
||||||
let id: Int
|
let id: Int
|
||||||
let name: String
|
let name: String
|
||||||
@ -27,14 +26,13 @@ struct DiningLocationParser: Decodable {
|
|||||||
let daysOfWeek: [String]
|
let daysOfWeek: [String]
|
||||||
let exceptions: [HoursException]?
|
let exceptions: [HoursException]?
|
||||||
}
|
}
|
||||||
// An individual "menu", which can be either a daily special item or a visitng chef. Description needs to be optional because
|
/// 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.
|
||||||
// visiting chefs have descriptions but specials do not.
|
|
||||||
struct Menu: Decodable {
|
struct Menu: Decodable {
|
||||||
let name: String
|
let name: String
|
||||||
let description: String?
|
let description: String?
|
||||||
let category: String
|
let category: String
|
||||||
}
|
}
|
||||||
// Other basic information to read from a location's JSON that we'll need later.
|
/// Other basic information to read from a location's JSON that we'll need later.
|
||||||
let id: Int
|
let id: Int
|
||||||
let mdoId: Int
|
let mdoId: Int
|
||||||
let name: String
|
let name: String
|
||||||
@ -45,12 +43,12 @@ struct DiningLocationParser: Decodable {
|
|||||||
let menus: [Menu]
|
let menus: [Menu]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Struct that probably doesn't need to exist but this made parsing the list of location responses easy.
|
/// Struct that probably doesn't need to exist but this made parsing the list of location responses easy.
|
||||||
struct DiningLocationsParser: Decodable {
|
struct DiningLocationsParser: Decodable {
|
||||||
let locations: [DiningLocationParser]
|
let locations: [DiningLocationParser]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enum to represent the four possible states a given location can be in.
|
/// Enum to represent the four possible states a given location can be in.
|
||||||
enum OpenStatus {
|
enum OpenStatus {
|
||||||
case open
|
case open
|
||||||
case closed
|
case closed
|
||||||
@ -58,13 +56,13 @@ enum OpenStatus {
|
|||||||
case closingSoon
|
case closingSoon
|
||||||
}
|
}
|
||||||
|
|
||||||
// An individual open period for a location.
|
/// An individual open period for a location.
|
||||||
struct DiningTimes: Equatable, Hashable {
|
struct DiningTimes: Equatable, Hashable {
|
||||||
var openTime: Date
|
var openTime: Date
|
||||||
var closeTime: Date
|
var closeTime: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enum to represent the five possible states a visiting chef can be in.
|
/// Enum to represent the five possible states a visiting chef can be in.
|
||||||
enum VisitingChefStatus {
|
enum VisitingChefStatus {
|
||||||
case hereNow
|
case hereNow
|
||||||
case gone
|
case gone
|
||||||
@ -73,7 +71,7 @@ enum VisitingChefStatus {
|
|||||||
case leavingSoon
|
case leavingSoon
|
||||||
}
|
}
|
||||||
|
|
||||||
// A visiting chef present at a location.
|
/// A visiting chef present at a location.
|
||||||
struct VisitingChef: Equatable, Hashable {
|
struct VisitingChef: Equatable, Hashable {
|
||||||
let name: String
|
let name: String
|
||||||
let description: String
|
let description: String
|
||||||
@ -82,19 +80,19 @@ struct VisitingChef: Equatable, Hashable {
|
|||||||
var status: VisitingChefStatus
|
var status: VisitingChefStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
// A daily special at a location.
|
/// A daily special at a location.
|
||||||
struct DailySpecial: Equatable, Hashable {
|
struct DailySpecial: Equatable, Hashable {
|
||||||
let name: String
|
let name: String
|
||||||
let type: 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.
|
/// 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 {
|
struct FDMPIds: Hashable {
|
||||||
let locationId: Int
|
let locationId: Int
|
||||||
let accountId: Int
|
let accountId: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
// The basic information about a dining location needed to display it in the app after parsing is finished.
|
/// The basic information about a dining location needed to display it in the app after parsing is finished.
|
||||||
struct DiningLocation: Identifiable, Hashable {
|
struct DiningLocation: Identifiable, Hashable {
|
||||||
let id: Int
|
let id: Int
|
||||||
let mdoId: Int
|
let mdoId: Int
|
||||||
@ -110,9 +108,9 @@ struct DiningLocation: Identifiable, Hashable {
|
|||||||
let dailySpecials: [DailySpecial]?
|
let dailySpecials: [DailySpecial]?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parser to read the occupancy data for a location.
|
/// Parser to read the occupancy data for a location.
|
||||||
struct DiningOccupancyParser: Decodable {
|
struct DiningOccupancyParser: Decodable {
|
||||||
// Represents a per-hour occupancy rating.
|
/// Represents a per-hour occupancy rating.
|
||||||
struct HourlyOccupancy: Decodable {
|
struct HourlyOccupancy: Decodable {
|
||||||
let hour: Int
|
let hour: Int
|
||||||
let today: Int
|
let today: Int
|
||||||
@ -130,7 +128,7 @@ struct DiningOccupancyParser: Decodable {
|
|||||||
let intra_loc_hours: [HourlyOccupancy]
|
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 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 {
|
struct WeeklyHours: Hashable {
|
||||||
let day: String
|
let day: String
|
||||||
let date: Date
|
let date: Date
|
||||||
|
|||||||
@ -24,12 +24,13 @@ struct AboutView: View {
|
|||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
Text("Version \(appVersionString) (\(buildNumber))")
|
Text("Version \(appVersionString) (\(buildNumber))")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
.padding(.bottom, 2)
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
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("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.")
|
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 {
|
HStack {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
openURL(URL(string: "https://github.com/NinjaCheetah/RIT-Dining")!)
|
openURL(URL(string: "https://github.com/NinjaCheetah/TigerDine")!)
|
||||||
}) {
|
}) {
|
||||||
Text("Source Code")
|
Text("Source Code")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ struct DonationView: View {
|
|||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
}
|
}
|
||||||
.font(.title)
|
.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)
|
.fontWeight(.bold)
|
||||||
.multilineTextAlignment(.center)
|
.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.")
|
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)
|
.foregroundStyle(.secondary)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
.padding(.bottom, 12)
|
.padding(.bottom, 12)
|
||||||
.onAppear {
|
|
||||||
print(menuItem.allergens)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Nutrition Facts")
|
Text("Nutrition Facts")
|
||||||
@ -17,6 +17,8 @@ struct MenuView: View {
|
|||||||
@State private var rotationDegrees: Double = 0
|
@State private var rotationDegrees: Double = 0
|
||||||
@State private var selectedMealPeriod: Int = 0
|
@State private var selectedMealPeriod: Int = 0
|
||||||
@State private var openPeriods: [Int] = []
|
@State private var openPeriods: [Int] = []
|
||||||
|
@State private var dietaryRestrictionsModel = MenuDietaryRestrictionsModel()
|
||||||
|
@State private var showingDietaryRestrictionsSheet: Bool = false
|
||||||
|
|
||||||
private var animation: Animation {
|
private var animation: Animation {
|
||||||
.linear
|
.linear
|
||||||
@ -63,6 +65,42 @@ struct MenuView: View {
|
|||||||
|
|
||||||
private var filteredMenuItems: [FDMenuItem] {
|
private var filteredMenuItems: [FDMenuItem] {
|
||||||
var newItems = menuItems
|
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
|
newItems = newItems.filter { item in
|
||||||
let searchedLocations = searchText.isEmpty || item.name.localizedCaseInsensitiveContains(searchText)
|
let searchedLocations = searchText.isEmpty || item.name.localizedCaseInsensitiveContains(searchText)
|
||||||
return searchedLocations
|
return searchedLocations
|
||||||
@ -164,6 +202,20 @@ struct MenuView: View {
|
|||||||
Image(systemName: "clock")
|
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) {
|
.onChange(of: selectedMealPeriod) {
|
||||||
rotationDegrees = 0
|
rotationDegrees = 0
|
||||||
@ -172,6 +224,9 @@ struct MenuView: View {
|
|||||||
await getMenuForPeriod(mealPeriodId: selectedMealPeriod)
|
await getMenuForPeriod(mealPeriodId: selectedMealPeriod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showingDietaryRestrictionsSheet) {
|
||||||
|
MenuDietaryRestrictionsSheet(dietaryRestrictionsModel: $dietaryRestrictionsModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user