From ebff2c9c3267b0d03a9cff39f471ac1ce00f062c Mon Sep 17 00:00:00 2001 From: Tony Bark <35226681+tonytins@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:19:55 -0400 Subject: [PATCH 1/2] Expand station and status models Added richer station metadata and status handling to match the Amtrak API payload. This commit introduces new station fields for name, timezone, schedule/actual timings, platform, and status, along with extra train row properties for origin and arrival/departure details used by the UI. --- Sources/AmtrakStatus/Models.swift | 38 +++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/Sources/AmtrakStatus/Models.swift b/Sources/AmtrakStatus/Models.swift index 29effcb..ad3f39d 100644 --- a/Sources/AmtrakStatus/Models.swift +++ b/Sources/AmtrakStatus/Models.swift @@ -4,9 +4,27 @@ struct TrainAlert: Codable { var message: String } -struct Station: Codable { +struct Station: Codable, Sendable { + var name: String var code: String - var platform: String? + var tz: String + var bus: Bool + var schArr: String + var schDep: String + var arr: String + var dep: String + var arrCmnt: String + var depCmnt: String + var status: StationStatus + var stopIconColor: String + var platform: String +} + +enum StationStatus: String, Codable, Sendable { + case enroute = "Enroute" + case station = "Station" + case departed = "Departed" + case unknown = "Unknown" } struct Train: Codable { @@ -65,15 +83,25 @@ struct StaleData: Codable { typealias TrainResponse = [String: [Train]] typealias StationResponse = [String: StationMeta] +struct AmtrakDateFormatting { + let isoFormatter = ISO8601DateFormatter() + + +} + struct TrainStatusRow: Identifiable { var id: String { trainID } - + var trainID: String var trainNum: String + var originating: String var routeName: String - var origin: String - var destination: String var platform: String + var arrival: String + var departure: String + var origin: String + var syatus: String + var destination: String } From bf67475107934e743cdafc54bb46966b2ce35adc Mon Sep 17 00:00:00 2001 From: Tony Bark <35226681+tonytins@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:52:27 -0400 Subject: [PATCH 2/2] Cleaned up and removed columns so it is what an actual Solari board would show - This update adds ISO date/time helpers for Amtrak timestamps, refactors the train table to show arrival time, status, and track details, and tracks the current station context in the UI. - It also hardens station status decoding with a safe fallback to Unknown and simplifies the row model to match the new display. --- .../AmtrakStatus/AmtrakDateFormatting.swift | 37 +++++++++ Sources/AmtrakStatus/ContentView.swift | 77 +++++++++++++------ Sources/AmtrakStatus/Models.swift | 16 ++-- 3 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 Sources/AmtrakStatus/AmtrakDateFormatting.swift diff --git a/Sources/AmtrakStatus/AmtrakDateFormatting.swift b/Sources/AmtrakStatus/AmtrakDateFormatting.swift new file mode 100644 index 0000000..912c9c7 --- /dev/null +++ b/Sources/AmtrakStatus/AmtrakDateFormatting.swift @@ -0,0 +1,37 @@ +import Foundation + +struct AmtrakDateFormatting { + static let isoFormatter = ISO8601DateFormatter() + + static let timeFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter + }() + + static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + formatter.timeStyle = .none + return formatter + }() + + static func time(from isoString: String?) -> String? { + guard let isoString, !isoString.isEmpty, let date = isoFormatter.date( + from: isoString) else { + return nil + } + + return timeFormatter.string(from: date) + } + + static func date(from isoString: String?) -> String? { + guard let isoString, !isoString.isEmpty, let date = isoFormatter.date( + from: isoString) else { + return nil + } + + return dateFormatter.string(from: date) + } +} diff --git a/Sources/AmtrakStatus/ContentView.swift b/Sources/AmtrakStatus/ContentView.swift index 01a165f..316a852 100644 --- a/Sources/AmtrakStatus/ContentView.swift +++ b/Sources/AmtrakStatus/ContentView.swift @@ -1,14 +1,9 @@ import SwiftCrossUI +import Foundation extension Train { - var displayNumber: String { - let parts = trainID.split(separator: "-", maxSplits: 1) - - guard parts.count <= 2 else { - return trainNum - } - - return "\(trainNum) (\(parts[1]))" + var serviceDate: String { + AmtrakDateFormatting.date(from: createdAt) ?? "-" } } @@ -19,6 +14,9 @@ struct ContentView: View { @State var rows: [TrainStatusRow] = [] @State var isLoading = false @State var errorMessage: String? + @State var currentStation: String = "" + + let unknown = "Unknown" var body: some View { VStack { @@ -56,11 +54,13 @@ struct ContentView: View { } Table(rows) { + TableColumn("Time", value: \TrainStatusRow.arrival) TableColumn("Number", value: \TrainStatusRow.trainNum) - TableColumn("Route", value: \TrainStatusRow.routeName) - TableColumn("Platform", value: \TrainStatusRow.platform) - TableColumn("Origin", value: \TrainStatusRow.origin) - TableColumn("Destination", value: \TrainStatusRow.destination) + TableColumn("Train", value: \TrainStatusRow.routeName) + TableColumn("To", value: \TrainStatusRow.origin) + TableColumn("From", value: \TrainStatusRow.destination) + TableColumn("Status", value: \TrainStatusRow.status) + TableColumn("Track", value: \TrainStatusRow.platform) }.overlay(alignment: .bottomTrailing) { if isLoading { @@ -73,6 +73,8 @@ struct ContentView: View { HStack { if let errorMessage { Text(errorMessage) + } else if !currentStation.isEmpty { + Text(currentStation) } }.padding(10) } @@ -117,17 +119,7 @@ struct ContentView: View { where: { $0.code == code }, )?.platform - - newRows.append(TrainStatusRow( - trainID: train.trainID, - trainNum: train.displayNumber, - routeName: train.routeName, - origin: train.origName, - destination: train.destName, - platform: ( - platform?.isEmpty == false, - ) ? platform! : "", - )) + newRows.append(trainStatus(for: train, atStationCode: code)) } } @@ -141,4 +133,43 @@ struct ContentView: View { rows = [] } } + + func isOnTime(arr: String?, schArr: String?) -> String { + let amtrakDate = AmtrakDateFormatting() + + if AmtrakDateFormatting + .time(from: arr) != AmtrakDateFormatting + .time(from: schArr) { + return "Late" + } + + return "On Time" + } + + func trainStatus(for train: Train, atStationCode code: String) -> TrainStatusRow { + let leg = train.stations?.first { $0.code == code } + let platform = leg?.platform ?? "-" + let stationName = leg?.name ?? unknown + let arrival = AmtrakDateFormatting.time(from: leg?.arr) ?? AmtrakDateFormatting.time( + from: leg?.schArr + ) + let depature = AmtrakDateFormatting.time(from: leg?.dep) ?? AmtrakDateFormatting.time( + from: leg?.schDep + ) + + currentStation = "\(stationName) station" + + return TrainStatusRow( + trainID: train.trainID, + trainNum: train.trainNum, + routeName: train.routeName, + status: isOnTime(arr: leg?.arr, schArr: leg?.schArr), + platform: leg?.platform ?? "", + arrival: arrival ?? "", + departure: depature ?? "", + origin: train.origName, + destination: train.destName, + // serviceDate: train.serviceDate + ) + } } diff --git a/Sources/AmtrakStatus/Models.swift b/Sources/AmtrakStatus/Models.swift index ad3f39d..401b91c 100644 --- a/Sources/AmtrakStatus/Models.swift +++ b/Sources/AmtrakStatus/Models.swift @@ -25,6 +25,12 @@ enum StationStatus: String, Codable, Sendable { case station = "Station" case departed = "Departed" case unknown = "Unknown" + + init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + let rawValue = try container.decode(String.self) + self = StationStatus(rawValue: rawValue) ?? .unknown + } } struct Train: Codable { @@ -83,12 +89,6 @@ struct StaleData: Codable { typealias TrainResponse = [String: [Train]] typealias StationResponse = [String: StationMeta] -struct AmtrakDateFormatting { - let isoFormatter = ISO8601DateFormatter() - - -} - struct TrainStatusRow: Identifiable { var id: String { trainID @@ -96,12 +96,12 @@ struct TrainStatusRow: Identifiable { var trainID: String var trainNum: String - var originating: String var routeName: String + var status: String var platform: String var arrival: String var departure: String var origin: String - var syatus: String var destination: String + // var serviceDate: String }