Compare commits

...

4 Commits

Author SHA1 Message Date
6613630aa2
Add privacy policy for App Store release 2025-11-13 00:17:54 -05:00
662fece439
Fixed dietary restrictions not immediately applying to menu
The vegetarian/vegan/no beef/no pork toggles will now immediately update the menu listing as intended.
2025-11-12 23:26:15 -05:00
5895313488
Properly include icon composer made icon 2025-11-12 22:53:00 -05:00
e5d87ca488
Changed icon to new RIT-trademark-free one
Replaced the icon with a more generic tiger pawprint so that I'm not using an RIT trademark, so that I'm prepared for the first App Store release. Also added information about where menu information is being sourced from on the about screen, and fixed a bug where vegetarian/vegan/no beef/no pork toggles would not be written to UserDefaults.
2025-11-12 21:56:50 -05:00
15 changed files with 122 additions and 67 deletions

2
PRIVACY.md Normal file
View File

@ -0,0 +1,2 @@
# Privacy Policy for TigerDine
TigerDine does not collect, store, or share any personal data.

View File

@ -265,7 +265,7 @@
CODE_SIGN_ENTITLEMENTS = "RIT Dining/RIT Dining.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19;
CURRENT_PROJECT_VERSION = 21;
DEVELOPMENT_TEAM = 5GF7GKNTK4;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
@ -300,7 +300,7 @@
CODE_SIGN_ENTITLEMENTS = "RIT Dining/RIT Dining.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19;
CURRENT_PROJECT_VERSION = 21;
DEVELOPMENT_TEAM = 5GF7GKNTK4;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

View File

@ -0,0 +1,37 @@
{
"fill" : {
"automatic-gradient" : "gray:0.90568,1.00000"
},
"groups" : [
{
"layers" : [
{
"glass" : false,
"image-name" : "TigerDine Temp Logo Emblem.png",
"name" : "TigerDine Temp Logo Emblem",
"position" : {
"scale" : 0.9,
"translation-in-points" : [
0,
0
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

View File

@ -1,7 +1,17 @@
{
"images" : [
{
"filename" : "RIT Dining Temp Logo.png",
"filename" : "TigerDine Temp Logo-iOS-Default-1024x1024@1x.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "TigerDine Temp Logo-iOS-Dark-1024x1024@1x.png",
"idiom" : "universal"
}
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -24,9 +24,9 @@ func parseFDMealPlannerMenu(menu: FDMealsParser) -> [FDMenuItem] {
// englishAlternateName holds the proper name of the item, but it's blank for some items for some reason. If that's the
// case, then we should fall back on componentName, which is less user-friendly but works as a backup.
let realName = if recipe.englishAlternateName != "" {
recipe.englishAlternateName
recipe.englishAlternateName.trimmingCharacters(in: .whitespaces)
} else {
recipe.componentName
recipe.componentName.trimmingCharacters(in: .whitespaces)
}
let allergens = recipe.allergenName != "" ? recipe.allergenName.components(separatedBy: ",") : []
// Get the list of dietary markers (Vegan, Vegetarian, Pork, Beef), and drop "Vegetarian" if "Vegan" is also included since

View File

@ -7,10 +7,31 @@
import SwiftUI
@Observable
class MenuDietaryRestrictionsModel {
var filteredDietaryMarkers: Set<String> = []
class MenuDietaryRestrictionsModel: ObservableObject {
var dietaryRestrictions = DietaryRestrictions()
var isVegetarian: Bool = false
var isVegan: Bool = false
// I thought these could be @AppStorage keys but apparently not, because SwiftUI would subscribe to updates from those if
// they aren't being used directly inside the view.
@Published var isVegetarian: Bool {
didSet { UserDefaults.standard.set(isVegetarian, forKey: "isVegetarian") }
}
@Published var isVegan: Bool {
didSet { UserDefaults.standard.set(isVegan, forKey: "isVegan") }
}
@Published var noBeef: Bool {
didSet { UserDefaults.standard.set(noBeef, forKey: "noBeef") }
}
@Published var noPork: Bool {
didSet { UserDefaults.standard.set(noPork, forKey: "noPork") }
}
init() {
self.isVegetarian = UserDefaults.standard.bool(forKey: "isVegetarian")
self.isVegan = UserDefaults.standard.bool(forKey: "isVegan")
self.noBeef = UserDefaults.standard.bool(forKey: "noBeef")
self.noPork = UserDefaults.standard.bool(forKey: "noPork")
}
}

View File

@ -20,36 +20,43 @@ struct AboutView: View {
.clipShape(RoundedRectangle(cornerRadius: 20))
Text("TigerDine")
.font(.title)
.fontWeight(.bold)
Text("An unofficial RIT Dining app")
.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("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. Menu and nutritional information is sourced from the data provided to FD MealPlanner by RIT Dining through the FD MealPlanner 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 {
VStack(alignment: .center, spacing: 8) {
HStack(spacing: 8) {
Button(action: {
openURL(URL(string: "https://github.com/NinjaCheetah/TigerDine")!)
}) {
Text("Source Code")
Label("Source Code", systemImage: "network")
}
Text("")
.foregroundStyle(.secondary)
Button(action: {
openURL(URL(string: "https://tigercenter.rit.edu/")!)
}) {
Text("TigerCenter")
Label("TigerCenter", systemImage: "fork.knife.circle")
}
Text("")
.foregroundStyle(.secondary)
}
HStack(spacing: 8) {
Button(action: {
openURL(URL(string: "https://maps.rit.edu/")!)
}) {
Text("Official RIT Map")
Label("Official RIT Map", systemImage: "map")
}
Button(action: {
openURL(URL(string: "https://fdmealplanner.com/")!)
}) {
Label("FD MealPlanner", systemImage: "menucard")
}
}
}
.frame(maxWidth: .infinity)
}
Spacer()
}
.padding()

View File

@ -9,38 +9,16 @@ import SwiftUI
struct MenuDietaryRestrictionsSheet: View {
@Environment(\.dismiss) var dismiss
@Binding var dietaryRestrictionsModel: MenuDietaryRestrictionsModel
@ObservedObject 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")
}
} )
) {
Toggle(isOn: $dietaryRestrictionsModel.noBeef) {
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")
}
} )
) {
Toggle(isOn: $dietaryRestrictionsModel.noPork) {
Text("No Pork")
}
Toggle(isOn: $dietaryRestrictionsModel.isVegetarian) {

View File

@ -17,7 +17,7 @@ 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()
@StateObject private var dietaryRestrictionsModel = MenuDietaryRestrictionsModel()
@State private var showingDietaryRestrictionsSheet: Bool = false
private var animation: Animation {
@ -65,17 +65,6 @@ 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 {
@ -100,6 +89,17 @@ struct MenuView: View {
return false
}
}
// Filter out pork/beef.
if dietaryRestrictionsModel.noBeef {
newItems = newItems.filter { item in
item.dietaryMarkers.contains("Beef") == false
}
}
if dietaryRestrictionsModel.noPork {
newItems = newItems.filter { item in
item.dietaryMarkers.contains("Pork") == false
}
}
// Filter down to search contents.
newItems = newItems.filter { item in
let searchedLocations = searchText.isEmpty || item.name.localizedCaseInsensitiveContains(searchText)
@ -225,7 +225,7 @@ struct MenuView: View {
}
}
.sheet(isPresented: $showingDietaryRestrictionsSheet) {
MenuDietaryRestrictionsSheet(dietaryRestrictionsModel: $dietaryRestrictionsModel)
MenuDietaryRestrictionsSheet(dietaryRestrictionsModel: dietaryRestrictionsModel)
}
}
}