Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions Eatery Blue.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
5DCB869C2782396000134066 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCB869B2782396000134066 /* main.swift */; };
5DCB86A22783544C00134066 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCB86A12783544C00134066 /* LocationManager.swift */; };
5DCB86A427835EF900134066 /* DummyLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DCB86A327835EF900134066 /* DummyLocationManager.swift */; };
5DDAD4732773D57A00CC98DD /* DummyData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDAD4722773D57A00CC98DD /* DummyData.swift */; };
5DDAD4752773D70900CC98DD /* EateryMediumCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDAD4742773D70900CC98DD /* EateryMediumCardView.swift */; };
5DDAD4772773DA9800CC98DD /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDAD4762773DA9800CC98DD /* RootViewController.swift */; };
5DDAD47D2773E8BA00CC98DD /* PillFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDAD47C2773E8BA00CC98DD /* PillFiltersView.swift */; };
Expand Down Expand Up @@ -162,6 +161,7 @@
BFD955952900A8150049D77C /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD955942900A8150049D77C /* UIColor+Extension.swift */; };
C31EC93B2CF3DEBB00B2E99F /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = C31EC93A2CF3DEBB00B2E99F /* FirebaseMessaging */; };
D00DC9592BB370520051D3EC /* CompareMenusTabsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00DC9582BB370520051D3EC /* CompareMenusTabsViewController.swift */; };
D00FEC9A2F4BF4DB0081CEE0 /* DateDecodingStrategy+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00FEC992F4BF4CF0081CEE0 /* DateDecodingStrategy+Extension.swift */; };
D01D80772BC638C900253976 /* CompareMenusInternalOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01D80762BC638C900253976 /* CompareMenusInternalOnboardingView.swift */; };
D03A749B2B92BE9500C14083 /* CompareMenusButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03A749A2B92BE9500C14083 /* CompareMenusButton.swift */; };
D04FB8792BCEE7820058F732 /* SettingsAppIconSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FB8782BCEE7820058F732 /* SettingsAppIconSheetViewController.swift */; };
Expand All @@ -173,6 +173,7 @@
D053BF622CCDAA250060F82C /* EateryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053BF612CCDAA250060F82C /* EateryListView.swift */; };
D0548AD72BB5201B0064489D /* ScrollHitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0548AD62BB5201B0064489D /* ScrollHitView.swift */; };
D06230E82CB9D4A70092C04A /* FavoritesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06230E72CB9D4A70092C04A /* FavoritesViewController.swift */; };
D063DB782F36942E00740573 /* EateryType+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D063DB772F36941C00740573 /* EateryType+Extension.swift */; };
D064656D2C8CE634001902A4 /* NotificationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D064656C2C8CE634001902A4 /* NotificationButton.swift */; };
D06AE51C2CE1CC4500C0AFF5 /* TabButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06AE51B2CE1CC3700C0AFF5 /* TabButtonView.swift */; };
D0730BB82D2B49AB00F9070A /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0730BB72D2B49A600F9070A /* HomeViewController.swift */; };
Expand Down Expand Up @@ -309,7 +310,6 @@
5DCB869B2782396000134066 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
5DCB86A12783544C00134066 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
5DCB86A327835EF900134066 /* DummyLocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyLocationManager.swift; sourceTree = "<group>"; };
5DDAD4722773D57A00CC98DD /* DummyData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyData.swift; sourceTree = "<group>"; };
5DDAD4742773D70900CC98DD /* EateryMediumCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EateryMediumCardView.swift; sourceTree = "<group>"; };
5DDAD4762773DA9800CC98DD /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = "<group>"; };
5DDAD47C2773E8BA00CC98DD /* PillFiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillFiltersView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -359,6 +359,7 @@
BFD955942900A8150049D77C /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = "<group>"; };
C31EC9392CF3DC0800B2E99F /* Eatery Blue.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Eatery Blue.entitlements"; sourceTree = "<group>"; };
D00DC9582BB370520051D3EC /* CompareMenusTabsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompareMenusTabsViewController.swift; sourceTree = "<group>"; };
D00FEC992F4BF4CF0081CEE0 /* DateDecodingStrategy+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateDecodingStrategy+Extension.swift"; sourceTree = "<group>"; };
D01D80762BC638C900253976 /* CompareMenusInternalOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompareMenusInternalOnboardingView.swift; sourceTree = "<group>"; };
D03A749A2B92BE9500C14083 /* CompareMenusButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompareMenusButton.swift; sourceTree = "<group>"; };
D04FB8782BCEE7820058F732 /* SettingsAppIconSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsAppIconSheetViewController.swift; sourceTree = "<group>"; };
Expand All @@ -369,6 +370,7 @@
D053BF612CCDAA250060F82C /* EateryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EateryListView.swift; sourceTree = "<group>"; };
D0548AD62BB5201B0064489D /* ScrollHitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollHitView.swift; sourceTree = "<group>"; };
D06230E72CB9D4A70092C04A /* FavoritesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewController.swift; sourceTree = "<group>"; };
D063DB772F36941C00740573 /* EateryType+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EateryType+Extension.swift"; sourceTree = "<group>"; };
D064656C2C8CE634001902A4 /* NotificationButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButton.swift; sourceTree = "<group>"; };
D06AE51B2CE1CC3700C0AFF5 /* TabButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabButtonView.swift; sourceTree = "<group>"; };
D0730BB72D2B49A600F9070A /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -658,7 +660,6 @@
5DCB869F27823DDB00134066 /* Testing */ = {
isa = PBXGroup;
children = (
5DDAD4722773D57A00CC98DD /* DummyData.swift */,
5DCB86A327835EF900134066 /* DummyLocationManager.swift */,
);
path = Testing;
Expand Down Expand Up @@ -687,6 +688,8 @@
5DF304B327797BD200E2C9A4 /* Extensions */ = {
isa = PBXGroup;
children = (
D00FEC992F4BF4CF0081CEE0 /* DateDecodingStrategy+Extension.swift */,
D063DB772F36941C00740573 /* EateryType+Extension.swift */,
D0B499132D6D17460044C856 /* UIView+Extension.swift */,
D0B499112D6D10AD0044C856 /* UIViewController+Extension.swift */,
D0A7A3072D3419BA00D9B472 /* NSAttributedString+Extension.swift */,
Expand Down Expand Up @@ -1169,7 +1172,6 @@
5DF5F7EB2774D8FE001ADCDC /* EateryViewController.swift in Sources */,
5DF304A527791D2C00E2C9A4 /* EateryModelController.swift in Sources */,
5DF938D1277662CC008ED0A1 /* PaymentMethodFilterView.swift in Sources */,
5DDAD4732773D57A00CC98DD /* DummyData.swift in Sources */,
5D1F13B2277638D4001A21B9 /* SheetPresentationController.swift in Sources */,
5DF938D327768B84008ED0A1 /* SheetViewController.swift in Sources */,
FD88925A2AE9B33C0084EDAD /* EateryMemoryCache.swift in Sources */,
Expand All @@ -1185,6 +1187,7 @@
D0730BB82D2B49AB00F9070A /* HomeViewController.swift in Sources */,
5D73C37F2780D5850020507C /* HomeSearchEmptyModelController.swift in Sources */,
5D34F090279B48CE000F9056 /* EateryCardAlertView.swift in Sources */,
D00FEC9A2F4BF4DB0081CEE0 /* DateDecodingStrategy+Extension.swift in Sources */,
5DF304CB277A36DE00E2C9A4 /* IssueTypeButtonView.swift in Sources */,
D0B499122D6D10B10044C856 /* UIViewController+Extension.swift in Sources */,
5DF304C6277A22AD00E2C9A4 /* ReportIssueButtonView.swift in Sources */,
Expand Down Expand Up @@ -1214,6 +1217,7 @@
5D468A00277D146500986AFF /* ClearTableViewCell.swift in Sources */,
D0A7A3082D3419C300D9B472 /* NSAttributedString+Extension.swift in Sources */,
D09D20A52B96DE19004C72ED /* CompareMenusNavigationView.swift in Sources */,
D063DB782F36942E00740573 /* EateryType+Extension.swift in Sources */,
D0A7A30A2D342E1800D9B472 /* MenusViewController.swift in Sources */,
5D468A15277E3FDC00986AFF /* Networking.swift in Sources */,
5D0CDC4F2774FDAE00AD0E27 /* MenuHeaderView.swift in Sources */,
Expand Down Expand Up @@ -1489,7 +1493,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3;
MARKETING_VERSION = 3.4;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = org.cuappdev.eatery;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -1532,7 +1536,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.3;
MARKETING_VERSION = 3.4;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_BUNDLE_IDENTIFIER = org.cuappdev.eatery;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
60 changes: 60 additions & 0 deletions Eatery Blue/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
_ application: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Check for updates
Task {
do {
let serverVersion = try await Networking.default.getAppVersion()
let deviceVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0"

if shouldForceUpdate(device: deviceVersion, server: serverVersion) {
await MainActor.run {
showForceUpdateAlert(
appStoreURL: "https://apps.apple.com/us/app/eatery-cornell-dining/id1089672962"
)
}
}
} catch {
print("Version check failed: \(error)")
}
}

FirebaseApp.configure()

// Request notification permissions
Expand Down Expand Up @@ -126,4 +144,46 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
Logger.notifications.info("Notification tapped: \(response.notification.request.content.userInfo)")
completionHandler()
}

// MARK: - Helper functions

func shouldForceUpdate(device: String, server: String) -> Bool {
let parse = { (v: String) in v.split(separator: ".").prefix(2).compactMap { Int($0) } }

let d = parse(device)
let s = parse(server)

// Compare arrays: [3, 4] > [3, 3]
return s.lexicographicallyPrecedes(d) == false && s != d
}

@MainActor
private func showForceUpdateAlert(appStoreURL: String) {
let alert = UIAlertController(
title: "Update Required",
message: "A new version of Eatery is available.",
preferredStyle: .alert
)

let updateAction = UIAlertAction(title: "Update Now", style: .default) { _ in
if let url = URL(string: appStoreURL) {
UIApplication.shared.open(url)
}

self.showForceUpdateAlert(appStoreURL: appStoreURL)
}

alert.addAction(updateAction)

// Find the current active window's rootViewController
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first(where: { $0.isKeyWindow })?.rootViewController {
// Find the top-most controller (in case a modal is already up)
var topController = rootVC
while let presented = topController.presentedViewController {
topController = presented
}
topController.present(alert, animated: true)
}
}
}
30 changes: 30 additions & 0 deletions Eatery Blue/Extensions/DateDecodingStrategy+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// DateDecodingStrategy+Extension.swift
// Eatery Blue
//
// Created by Peter Bidoshi on 2/22/26.
//

import Foundation

extension JSONDecoder.DateDecodingStrategy {
static let fracSecondsISO8601 = custom { decoder in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)

let formatter = ISO8601DateFormatter()

// Strategy 1: Try with milliseconds
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
if let date = formatter.date(from: dateString) { return date }

// Strategy 2: Try without milliseconds
formatter.formatOptions = [.withInternetDateTime]
if let date = formatter.date(from: dateString) { return date }

throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Unrecognized date format: \(dateString)"
)
}
}
28 changes: 28 additions & 0 deletions Eatery Blue/Extensions/EateryType+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// EateryType+Extension.swift
// Eatery Blue
//
// Created by Peter Bidoshi on 2/6/26.
//

import EateryModel

extension Array where Element == EateryType {
func validated() -> [EateryType]? {
guard count > 0 else { return nil }
return self
}

func formatted() -> String {
// should be "{0}, {1}, and {2}"
let count = self.count
if count == 1 {
return self[0].description
} else if count == 2 {
return "\(self[0].description) and \(self[1].description)"
} else {
let allButLast = self[0 ..< (count - 1)].map { $0.description }.joined(separator: ", ")
return "\(allButLast), and \(self[count - 1].description)"
}
}
}
32 changes: 9 additions & 23 deletions Eatery Blue/Extensions/String+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,21 @@
// Created by Jayson Hahn on 10/17/23.
//

import EateryModel
import Foundation

extension String {
static let Eatery = String()

/**
Determines the correct meal based on the time of day.

This function gets the time of day from Date and returns a string of the meal type. If it is a specific meal time,
that meal time is returned, and if it is not a specific meal time, the next meal time is returned.
Determines if a string has meaningful characters (not just whitespace or empty string).

- Returns: If it is 10:30 pm - 10:29 am inclusive, breakfest is returned. If it is 10:30 am - 2:29 pm inclusive,
lunch is returned. If it is 2:30 pm - 8:29 pm inclusive, dinner is returned.
If it is 8:30 pm - 10:29 pm inclusive, late dinner is returned.
- Returns: `nil` if the string is empty or only contains whitespace/newline characters.
Otherwise, returns the original string.
*/
func mealFromTime() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH"
guard let hour = Int(dateFormatter.string(from: Date())) else { return "Breakfast" }
dateFormatter.dateFormat = "mm"
guard let minute = Int(dateFormatter.string(from: Date())) else { return "Breakfast" }
if hour < 10 || hour == 10 && minute < 30 || hour >= 23 || hour == 22 && minute >= 30 {
return "Breakfast"
} else if hour == 10 && minute >= 30 || hour > 10 && hour < 14 || hour == 14 && minute < 30 {
return "Lunch"
} else if hour == 14 && minute >= 30 || hour > 14 && hour < 20 || hour == 20 && minute < 30 {
return "Dinner"
} else {
return "Late Dinner"
func validated() -> String? {
if trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return nil
}

return self
}
}
4 changes: 2 additions & 2 deletions Eatery Blue/Model/CoreDataStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CoreDataStack {
}
}

func metadata(eateryId: Int64) -> EateryMetadata {
func metadata(eateryId: Int) -> EateryMetadata {
let fetchRequest = NSFetchRequest<EateryMetadata>()
fetchRequest.entity = EateryMetadata.entity()
fetchRequest.predicate = NSPredicate(format: "eateryId == %d", eateryId)
Expand All @@ -49,7 +49,7 @@ class CoreDataStack {

} else {
let metadata = EateryMetadata(context: context)
metadata.eateryId = eateryId
metadata.eateryId = Int64(eateryId)
save()
return metadata
}
Expand Down
8 changes: 4 additions & 4 deletions Eatery Blue/Model/EateryFilter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ struct EateryFilter: Codable {
if brbsEnabled || mealSwipesEnabled {
return .or([
brbsEnabled ? .acceptsPaymentMethod(.brbs) : .false,
mealSwipesEnabled ? .acceptsPaymentMethod(.mealSwipes) : .false
mealSwipesEnabled ? .acceptsPaymentMethod(.mealSwipe) : .false
])
} else {
return .true
Expand All @@ -69,9 +69,9 @@ struct EateryFilter: Codable {
func campusAreaPredicate() -> EateryPredicate {
if north || west || central {
return .or([
north ? .campusArea("North") : .false,
west ? .campusArea("West") : .false,
central ? .campusArea("Central") : .false
north ? .campusArea(.north) : .false,
west ? .campusArea(.west) : .false,
central ? .campusArea(.central) : .false
])
} else {
return .true
Expand Down
Loading