From 71c37749e36dc15d70ac8bbd00aa2e12013c22bf Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Wed, 14 Jan 2026 00:16:40 -0500 Subject: [PATCH] Fixed multi opening period locations in widgets Widgets for locations with multiple opening periods will still only display the first time span, but the bar will now show multiple filled in sections and the opening status label will correctly represent both periods. --- .../SharedUtils.swift} | 2 +- .../Components/TigerCenterParsers.swift | 36 +++++---- .../Data/Static/FDMPMealPeriods.swift | 0 .../Data/Static/TCtoFDMPMap.swift | 0 TigerDine.xcodeproj/project.pbxproj | 13 +-- TigerDineWidgets/Components/HoursGague.swift | 62 +++++++-------- TigerDineWidgets/Widgets/HoursWidget.swift | 79 +++++++++++-------- 7 files changed, 105 insertions(+), 87 deletions(-) rename Shared/{SharedComponents.swift => Components/SharedUtils.swift} (98%) rename {TigerDine => Shared}/Components/TigerCenterParsers.swift (92%) rename {TigerDine => Shared}/Data/Static/FDMPMealPeriods.swift (100%) rename {TigerDine => Shared}/Data/Static/TCtoFDMPMap.swift (100%) 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) + ) + ] + ) }