Mostly fixed multi opening period widgets!

- The opening status label on widgets should update properly on time now.
- Improved some of the logic related to determining opening statuses. Guards exist!
- Reduced the text sizes so that more of the location names fits in the widgets. Also ensures that the full opening times will be displayed (this always worked for 24-hour time but wasn't guaranteed to fit for 12-hour time).
This commit is contained in:
Campbell 2026-01-14 21:43:33 -05:00
parent 71c37749e3
commit b51335768f
Signed by: NinjaCheetah
GPG Key ID: 39C2500E1778B156
4 changed files with 54 additions and 57 deletions

View File

@ -8,48 +8,50 @@
import Foundation import Foundation
/// Gets the current open status of a location based on the open time and close time. /// Gets the current open status of a location based on the open time and close time.
func parseOpenStatus(openTime: Date, closeTime: Date) -> OpenStatus { func parseOpenStatus(openTime: Date, closeTime: Date, referenceTime: 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 // If the location is open but the close time is within the next 30 minutes, label it as closing soon, and do the opposite
// 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. // if it's closed but the open time is within the next 30 minutes.
let calendar = Calendar.current let calendar = Calendar.current
let now = Date() if referenceTime >= openTime && referenceTime <= closeTime {
var openStatus: OpenStatus = .closed
if now >= openTime && now <= closeTime {
// This is basically just for Bytes, it checks the case where the open and close times are exactly 24 hours apart, which is // This is basically just for Bytes, it checks the case where the open and close times are exactly 24 hours apart, which is
// only true for 24-hour locations. // only true for 24-hour locations.
if closeTime == calendar.date(byAdding: .day, value: 1, to: openTime)! { if closeTime == calendar.date(byAdding: .day, value: 1, to: openTime)! {
openStatus = .open return .open
} else if closeTime < calendar.date(byAdding: .minute, value: 30, to: now)! { } else if closeTime < calendar.date(byAdding: .minute, value: 30, to: referenceTime)! {
openStatus = .closingSoon return .closingSoon
} else {
openStatus = .open
} }
} else if openTime <= calendar.date(byAdding: .minute, value: 30, to: now)! && closeTime > now { return .open
openStatus = .openingSoon } else if referenceTime < openTime && openTime <= calendar.date(byAdding: .minute, value: 30, to: referenceTime)! {
} else { return .openingSoon
openStatus = .closed
}
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 { } else {
return .closed return .closed
} }
} }
/// 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]?, referenceTime: Date) -> OpenStatus {
var openStatus: OpenStatus = .closed
guard let diningTimes, !diningTimes.isEmpty else {
return .closed
}
for i in diningTimes.indices {
openStatus = parseOpenStatus(
openTime: diningTimes[i].openTime,
closeTime: diningTimes[i].closeTime,
referenceTime: referenceTime
)
// 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 {
return openStatus
}
}
return .closed
}
/// Parses the JSON responses from the TigerCenter API into the format used throughout TigerDine. /// Parses the JSON responses from the TigerCenter API into the format used throughout TigerDine.
func parseLocationInfo(location: DiningLocationParser, forDate: Date?) -> DiningLocation { func parseLocationInfo(location: DiningLocationParser, forDate: Date?) -> DiningLocation {
print("beginning parse for \(location.name)") print("beginning parse for \(location.name)")
@ -181,7 +183,7 @@ func parseLocationInfo(location: DiningLocationParser, forDate: Date?) -> Dining
// 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. // 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.
var openStatus: OpenStatus = .closed var openStatus: OpenStatus = .closed
for i in diningTimes.indices { for i in diningTimes.indices {
openStatus = parseOpenStatus(openTime: diningTimes[i].openTime, closeTime: diningTimes[i].closeTime) openStatus = parseOpenStatus(openTime: diningTimes[i].openTime, closeTime: diningTimes[i].closeTime, referenceTime: now)
// If the first event pass came back closed, loop again in case a later event has a different status. This is mostly to // 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. // accurately catch Gracie's multiple open periods each day.
if openStatus != .closed { if openStatus != .closed {
@ -242,7 +244,7 @@ func parseLocationInfo(location: DiningLocationParser, forDate: Date?) -> Dining
} }
// Parse the chef's status, mapping the OpenStatus to a VisitingChefStatus. // Parse the chef's status, mapping the OpenStatus to a VisitingChefStatus.
let visitngChefStatus: VisitingChefStatus = switch parseOpenStatus(openTime: openTime, closeTime: closeTime) { let visitngChefStatus: VisitingChefStatus = switch parseOpenStatus(openTime: openTime, closeTime: closeTime, referenceTime: now) {
case .open: case .open:
.hereNow .hereNow
case .closed: case .closed:
@ -297,14 +299,15 @@ extension DiningLocation {
// Updates the open status of a location and of its visiting chefs, so that the labels in the UI update automatically as // 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. // time progresses and locations open/close/etc.
mutating func updateOpenStatus() { mutating func updateOpenStatus() {
let now = Date()
// Gets the open status with the multi opening period compatible function. // Gets the open status with the multi opening period compatible function.
self.open = parseMultiOpenStatus(diningTimes: diningTimes) self.open = parseMultiOpenStatus(diningTimes: diningTimes, referenceTime: now)
if let visitingChefs = visitingChefs, !visitingChefs.isEmpty { if let visitingChefs = visitingChefs, !visitingChefs.isEmpty {
let now = Date()
for i in visitingChefs.indices { for i in visitingChefs.indices {
self.visitingChefs![i].status = switch parseOpenStatus( self.visitingChefs![i].status = switch parseOpenStatus(
openTime: visitingChefs[i].openTime, openTime: visitingChefs[i].openTime,
closeTime: visitingChefs[i].closeTime) { closeTime: visitingChefs[i].closeTime,
referenceTime: now) {
case .open: case .open:
.hereNow .hereNow
case .closed: case .closed:

View File

@ -292,7 +292,7 @@
CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements; CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 28; CURRENT_PROJECT_VERSION = 29;
DEVELOPMENT_TEAM = 5GF7GKNTK4; DEVELOPMENT_TEAM = 5GF7GKNTK4;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TigerDineWidgets/Info.plist; INFOPLIST_FILE = TigerDineWidgets/Info.plist;
@ -325,7 +325,7 @@
CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements; CODE_SIGN_ENTITLEMENTS = TigerDineWidgets/TigerDineWidgets.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 28; CURRENT_PROJECT_VERSION = 29;
DEVELOPMENT_TEAM = 5GF7GKNTK4; DEVELOPMENT_TEAM = 5GF7GKNTK4;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = TigerDineWidgets/Info.plist; INFOPLIST_FILE = TigerDineWidgets/Info.plist;
@ -481,7 +481,7 @@
CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements; CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 28; CURRENT_PROJECT_VERSION = 29;
DEVELOPMENT_TEAM = 5GF7GKNTK4; DEVELOPMENT_TEAM = 5GF7GKNTK4;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -518,7 +518,7 @@
CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements; CODE_SIGN_ENTITLEMENTS = TigerDine/TigerDine.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 28; CURRENT_PROJECT_VERSION = 29;
DEVELOPMENT_TEAM = 5GF7GKNTK4; DEVELOPMENT_TEAM = 5GF7GKNTK4;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;

View File

@ -9,13 +9,13 @@ import SwiftUI
struct OpeningHoursGauge: View { struct OpeningHoursGauge: View {
let diningTimes: [DiningTimes]? let diningTimes: [DiningTimes]?
let now: Date let referenceTime: Date
private let dayDuration: TimeInterval = 86_400 private let dayDuration: TimeInterval = 86_400
private var barFillColor: Color { private var barFillColor: Color {
if let diningTimes = diningTimes { if let diningTimes = diningTimes {
let openStatus = parseMultiOpenStatus(diningTimes: diningTimes) let openStatus = parseMultiOpenStatus(diningTimes: diningTimes, referenceTime: referenceTime)
switch openStatus { switch openStatus {
case .open: case .open:
return Color.green return Color.green
@ -34,10 +34,10 @@ struct OpeningHoursGauge: View {
let width = geometry.size.width let width = geometry.size.width
let barHeight: CGFloat = 16 let barHeight: CGFloat = 16
let startOfToday = Calendar.current.startOfDay(for: now) let startOfToday = Calendar.current.startOfDay(for: referenceTime)
let startOfTomorrow = Calendar.current.date(byAdding: .day, value: 1, to: startOfToday)! let startOfTomorrow = Calendar.current.date(byAdding: .day, value: 1, to: startOfToday)!
let nowX = position(for: now, start: startOfToday, width: width) let nowX = position(for: referenceTime, start: startOfToday, width: width)
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
Capsule() Capsule()

View File

@ -89,9 +89,7 @@ struct Provider: AppIntentTimelineProvider {
close: Date? close: Date?
) -> [Date] { ) -> [Date] {
var dates: Set<Date> = [] var dates: Set<Date> = [now]
dates.insert(now)
if let open = open, let close = close { if let open = open, let close = close {
dates.insert(open) dates.insert(open)
@ -127,39 +125,35 @@ struct OpenWidgetEntryView : View {
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Text(entry.name) Text(entry.name)
.font(.title2) .font(.title3)
.fontWeight(.bold) .fontWeight(.bold)
// Should maybe try to unify this with the almost-identical UI code in DetailView.
if let diningTimes = entry.diningTimes { if let diningTimes = entry.diningTimes {
let openStatus = parseMultiOpenStatus(diningTimes: diningTimes) let openStatus = parseMultiOpenStatus(diningTimes: diningTimes, referenceTime: entry.date)
switch openStatus { switch openStatus {
case .open: case .open:
Text("Open") Text("Open")
.font(.title3)
.foregroundStyle(.green) .foregroundStyle(.green)
case .closed: case .closed:
Text("Closed") Text("Closed")
.font(.title3)
.foregroundStyle(.red) .foregroundStyle(.red)
case .openingSoon: case .openingSoon:
Text("Opening Soon") Text("Opening Soon")
.font(.title3)
.foregroundStyle(.orange) .foregroundStyle(.orange)
case .closingSoon: case .closingSoon:
Text("Closing Soon") Text("Closing Soon")
.font(.title3)
.foregroundStyle(.orange) .foregroundStyle(.orange)
} }
Text("\(dateDisplay.string(from: diningTimes[0].openTime)) - \(dateDisplay.string(from: diningTimes[0].closeTime))") Text("\(dateDisplay.string(from: diningTimes[0].openTime)) - \(dateDisplay.string(from: diningTimes[0].closeTime))")
.font(.system(size: 15))
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} else { } else {
Text("Closed") Text("Closed")
.font(.title3)
.foregroundStyle(.red) .foregroundStyle(.red)
Text("Not Open Today") Text("Not Open Today")
.font(.system(size: 15))
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
@ -167,7 +161,7 @@ struct OpenWidgetEntryView : View {
OpeningHoursGauge( OpeningHoursGauge(
diningTimes: entry.diningTimes, diningTimes: entry.diningTimes,
now: entry.date referenceTime: entry.date
) )
} }
} }