diff --git a/Shared/SharedComponents.swift b/Shared/Components/SharedUtils.swift similarity index 98% rename from Shared/SharedComponents.swift rename to Shared/Components/SharedUtils.swift index 74346c8..593cffe 100644 --- a/Shared/SharedComponents.swift +++ b/Shared/Components/SharedUtils.swift @@ -1,5 +1,5 @@ // -// SharedComponents.swift +// SharedUtils.swift // TigerDine // // Created by Campbell on 9/8/25. diff --git a/TigerDine/Components/TigerCenterParsers.swift b/Shared/Components/TigerCenterParsers.swift similarity index 92% rename from TigerDine/Components/TigerCenterParsers.swift rename to Shared/Components/TigerCenterParsers.swift index 0b57291..9afd43e 100644 --- a/TigerDine/Components/TigerCenterParsers.swift +++ b/Shared/Components/TigerCenterParsers.swift @@ -7,6 +7,7 @@ import Foundation +/// Gets the current open status of a location based on the open time and close time. func parseOpenStatus(openTime: Date, closeTime: Date) -> OpenStatus { // This can probably be done a little cleaner but it's okay for now. If the location is open but the close date is within the next // 30 minutes, label it as closing soon, and do the opposite if it's closed but the open date is within the next 30 minutes. @@ -31,6 +32,25 @@ func parseOpenStatus(openTime: Date, closeTime: Date) -> OpenStatus { return openStatus } +/// Gets the current open status of a location with multiple opening periods based on all of its open and close times. +func parseMultiOpenStatus(diningTimes: [DiningTimes]?) -> OpenStatus { + var openStatus: OpenStatus = .closed + if let diningTimes = diningTimes, !diningTimes.isEmpty { + for i in diningTimes.indices { + openStatus = parseOpenStatus(openTime: diningTimes[i].openTime, closeTime: diningTimes[i].closeTime) + // If the first event pass came back closed, loop again in case a later event has a different status. This is mostly to + // accurately catch Gracie's/Brick City Cafe's multiple open periods each day. + if openStatus != .closed { + break + } + } + return openStatus + } else { + return .closed + } +} + +/// Parses the JSON responses from the TigerCenter API into the format used throughout TigerDine. func parseLocationInfo(location: DiningLocationParser, forDate: Date?) -> DiningLocation { print("beginning parse for \(location.name)") @@ -277,20 +297,8 @@ extension DiningLocation { // Updates the open status of a location and of its visiting chefs, so that the labels in the UI update automatically as // time progresses and locations open/close/etc. mutating func updateOpenStatus() { - var openStatus: OpenStatus = .closed - if let diningTimes = diningTimes, !diningTimes.isEmpty { - for i in diningTimes.indices { - openStatus = parseOpenStatus(openTime: diningTimes[i].openTime, closeTime: diningTimes[i].closeTime) - // If the first event pass came back closed, loop again in case a later event has a different status. This is mostly to - // accurately catch Gracie's multiple open periods each day. - if openStatus != .closed { - break - } - } - self.open = openStatus - } else { - self.open = .closed - } + // Gets the open status with the multi opening period compatible function. + self.open = parseMultiOpenStatus(diningTimes: diningTimes) if let visitingChefs = visitingChefs, !visitingChefs.isEmpty { let now = Date() for i in visitingChefs.indices { diff --git a/TigerDine/Data/Static/FDMPMealPeriods.swift b/Shared/Data/Static/FDMPMealPeriods.swift similarity index 100% rename from TigerDine/Data/Static/FDMPMealPeriods.swift rename to Shared/Data/Static/FDMPMealPeriods.swift diff --git a/TigerDine/Data/Static/TCtoFDMPMap.swift b/Shared/Data/Static/TCtoFDMPMap.swift similarity index 100% rename from TigerDine/Data/Static/TCtoFDMPMap.swift rename to Shared/Data/Static/TCtoFDMPMap.swift diff --git a/TigerDine.xcodeproj/project.pbxproj b/TigerDine.xcodeproj/project.pbxproj index 150fdd3..6f44a6c 100644 --- a/TigerDine.xcodeproj/project.pbxproj +++ b/TigerDine.xcodeproj/project.pbxproj @@ -55,7 +55,10 @@ 374CDA742F10B24F00D8C50A /* Exceptions for "Shared" folder in "TigerDineWidgets" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - SharedComponents.swift, + Components/SharedUtils.swift, + Components/TigerCenterParsers.swift, + Data/Static/FDMPMealPeriods.swift, + Data/Static/TCtoFDMPMap.swift, Types/TigerCenterTypes.swift, ); target = 374CDA572F10A19500D8C50A /* TigerDineWidgets */; @@ -289,7 +292,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 27; + CURRENT_PROJECT_VERSION = 28; DEVELOPMENT_TEAM = 5GF7GKNTK4; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TigerDineWidgets/Info.plist; @@ -322,7 +325,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 27; + CURRENT_PROJECT_VERSION = 28; DEVELOPMENT_TEAM = 5GF7GKNTK4; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = TigerDineWidgets/Info.plist; @@ -478,7 +481,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 27; + CURRENT_PROJECT_VERSION = 28; DEVELOPMENT_TEAM = 5GF7GKNTK4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -515,7 +518,7 @@ CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 27; + CURRENT_PROJECT_VERSION = 28; DEVELOPMENT_TEAM = 5GF7GKNTK4; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/TigerDineWidgets/Components/HoursGague.swift b/TigerDineWidgets/Components/HoursGague.swift index 8e0632b..daf522e 100644 --- a/TigerDineWidgets/Components/HoursGague.swift +++ b/TigerDineWidgets/Components/HoursGague.swift @@ -8,28 +8,21 @@ import SwiftUI struct OpeningHoursGauge: View { - let openTime: Date? - let closeTime: Date? + let diningTimes: [DiningTimes]? let now: Date private let dayDuration: TimeInterval = 86_400 private var barFillColor: Color { - let calendar = Calendar.current - - if let openTime = openTime, let closeTime = closeTime { - if now >= openTime && now <= closeTime { - if closeTime == calendar.date(byAdding: .day, value: 1, to: openTime)! { - return Color.green - } else if closeTime < calendar.date(byAdding: .minute, value: 30, to: now)! { - return Color.orange - } else { - return Color.green - } - } else if openTime <= calendar.date(byAdding: .minute, value: 30, to: now)! && closeTime > now { - return Color.orange - } else { + if let diningTimes = diningTimes { + let openStatus = parseMultiOpenStatus(diningTimes: diningTimes) + switch openStatus { + case .open: + return Color.green + case .closed: return Color.red + case .openingSoon, .closingSoon: + return Color.orange } } else { return Color.red @@ -53,24 +46,27 @@ struct OpeningHoursGauge: View { // We can skip drawing this entire capsule if the location is never open, since there would be no opening period // to draw. - if let openTime = openTime, let closeTime = closeTime { - let openX = position(for: openTime, start: startOfToday, width: width) - let closeX = position( - for: closeTime, - start: closeTime < openTime ? startOfTomorrow : startOfToday, - width: width - ) - - Capsule() - .fill( - LinearGradient( - colors: [barFillColor.opacity(0.7), barFillColor], - startPoint: .leading, - endPoint: .trailing - ) + if let diningTimes = diningTimes { + // Need to iterate here to account for locations that have multiple opening periods (Gracie's/Brick City Cafe). + ForEach(diningTimes, id: \.self) { diningTime in + let openX = position(for: diningTime.openTime, start: startOfToday, width: width) + let closeX = position( + for: diningTime.closeTime, + start: diningTime.closeTime < diningTime.openTime ? startOfTomorrow : startOfToday, + width: width ) - .frame(width: max(0, closeX - openX), height: barHeight) - .offset(x: openX) + + Capsule() + .fill( + LinearGradient( + colors: [barFillColor.opacity(0.7), barFillColor], + startPoint: .leading, + endPoint: .trailing + ) + ) + .frame(width: max(0, closeX - openX), height: barHeight) + .offset(x: openX) + } } Circle() diff --git a/TigerDineWidgets/Widgets/HoursWidget.swift b/TigerDineWidgets/Widgets/HoursWidget.swift index 1cd006a..2749bf1 100644 --- a/TigerDineWidgets/Widgets/HoursWidget.swift +++ b/TigerDineWidgets/Widgets/HoursWidget.swift @@ -18,8 +18,9 @@ struct Provider: AppIntentTimelineProvider { return OpenEntry( date: Date(), name: "Select a Location", - openTime: startOfToday, - closeTime: startOfTomorrow + diningTimes: [ + DiningTimes(openTime: startOfToday, closeTime: startOfTomorrow) + ] ) } @@ -44,16 +45,15 @@ struct Provider: AppIntentTimelineProvider { let updateDates = buildUpdateSchedule( now: Date(), - open: baseEntry.openTime, - close: baseEntry.closeTime + open: baseEntry.diningTimes?.first!.openTime, + close: baseEntry.diningTimes?.first!.closeTime ) let entries = updateDates.map { OpenEntry( date: $0, name: baseEntry.name, - openTime: baseEntry.openTime, - closeTime: baseEntry.closeTime + diningTimes: baseEntry.diningTimes ) } @@ -79,8 +79,7 @@ struct Provider: AppIntentTimelineProvider { return OpenEntry( date: Date(), name: location.name, - openTime: location.diningTimes?.first?.openTime, - closeTime: location.diningTimes?.first?.closeTime + diningTimes: location.diningTimes ) } @@ -117,8 +116,7 @@ struct Provider: AppIntentTimelineProvider { struct OpenEntry: TimelineEntry { let date: Date let name: String - let openTime: Date? - let closeTime: Date? + let diningTimes: [DiningTimes]? } struct OpenWidgetEntryView : View { @@ -133,32 +131,28 @@ struct OpenWidgetEntryView : View { .fontWeight(.bold) // Should maybe try to unify this with the almost-identical UI code in DetailView. - if let openTime = entry.openTime, let closeTime = entry.closeTime { - if entry.date >= openTime && entry.date <= closeTime { - if closeTime == calendar.date(byAdding: .day, value: 1, to: openTime)! { - Text("Open") - .font(.title3) - .foregroundStyle(.green) - } else if closeTime < calendar.date(byAdding: .minute, value: 30, to: entry.date)! { - Text("Closing Soon") - .font(.title3) - .foregroundStyle(.orange) - } else { - Text("Open") - .font(.title3) - .foregroundStyle(.green) - } - } else if openTime <= calendar.date(byAdding: .minute, value: 30, to: entry.date)! && closeTime > entry.date { - Text("Opening Soon") + if let diningTimes = entry.diningTimes { + let openStatus = parseMultiOpenStatus(diningTimes: diningTimes) + switch openStatus { + case .open: + Text("Open") .font(.title3) - .foregroundStyle(.orange) - } else { + .foregroundStyle(.green) + case .closed: Text("Closed") .font(.title3) .foregroundStyle(.red) + case .openingSoon: + Text("Opening Soon") + .font(.title3) + .foregroundStyle(.orange) + case .closingSoon: + Text("Closing Soon") + .font(.title3) + .foregroundStyle(.orange) } - Text("\(dateDisplay.string(from: openTime)) - \(dateDisplay.string(from: closeTime))") + Text("\(dateDisplay.string(from: diningTimes[0].openTime)) - \(dateDisplay.string(from: diningTimes[0].closeTime))") .foregroundStyle(.secondary) } else { Text("Closed") @@ -172,8 +166,7 @@ struct OpenWidgetEntryView : View { Spacer() OpeningHoursGauge( - openTime: entry.openTime, - closeTime: entry.closeTime, + diningTimes: entry.diningTimes, now: entry.date ) } @@ -201,6 +194,24 @@ struct HoursWidget: Widget { #Preview(as: .systemSmall) { HoursWidget() } timeline: { - OpenEntry(date: .now, name: "Beanz", openTime: Date(timeIntervalSince1970: 1767963600), closeTime: Date(timeIntervalSince1970: 1767988800)) - OpenEntry(date: Date(timeIntervalSince1970: 1767978000), name: "Beanz", openTime: Date(timeIntervalSince1970: 1767963600), closeTime: Date(timeIntervalSince1970: 1767988800)) + OpenEntry( + date: .now, + name: "Beanz", + diningTimes: [ + DiningTimes( + openTime: Date(timeIntervalSince1970: 1767963600), + closeTime: Date(timeIntervalSince1970: 1767988800) + ) + ] + ) + OpenEntry( + date: Date(timeIntervalSince1970: 1767978000), + name: "Beanz", + diningTimes: [ + DiningTimes( + openTime: Date(timeIntervalSince1970: 1767963600), + closeTime: Date(timeIntervalSince1970: 1767988800) + ) + ] + ) }