mirror of
https://github.com/tonytins/amtkstat.git
synced 2026-10-24 05:32:54 -04:00
Finished wiring up loadTrainStatus()
Did a little code formatting after that cause Xcode is weird
This commit is contained in:
parent
3eb2018a8e
commit
f20d49f075
5 changed files with 62 additions and 45 deletions
|
|
@ -8,7 +8,7 @@ let package = Package(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(
|
.package(
|
||||||
url: "https://github.com/moreSwift/swift-cross-ui",
|
url: "https://github.com/moreSwift/swift-cross-ui",
|
||||||
.upToNextMinor(from: "0.7.0")
|
.upToNextMinor(from: "0.7.0"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
|
|
@ -17,7 +17,7 @@ let package = Package(
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "SwiftCrossUI", package: "swift-cross-ui"),
|
.product(name: "SwiftCrossUI", package: "swift-cross-ui"),
|
||||||
.product(name: "DefaultBackend", package: "swift-cross-ui"),
|
.product(name: "DefaultBackend", package: "swift-cross-ui"),
|
||||||
]
|
],
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,56 +9,60 @@ enum AmtrakError: Error {
|
||||||
|
|
||||||
struct AmtrakClient {
|
struct AmtrakClient {
|
||||||
var decoder = JSONDecoder()
|
var decoder = JSONDecoder()
|
||||||
|
|
||||||
let stationBaseURL = "https://api.amtraker.com/v3/stations"
|
let stationBaseURL = "https://api.amtraker.com/v3/stations"
|
||||||
let trainsBaseURL = "https://api.amtraker.com/v3/trains"
|
let trainsBaseURL = "https://api.amtraker.com/v3/trains"
|
||||||
let staleBaseURL = "https://api.amtraker.com/v3/stale"
|
let staleBaseURL = "https://api.amtraker.com/v3/stale"
|
||||||
|
|
||||||
func fetchTrain(number trainid: String) async throws -> TrainResponse {
|
func fetchTrain(number trainid: String) async throws -> TrainResponse {
|
||||||
try await get("\(trainsBaseURL)/\(trainid)")
|
try await get("\(trainsBaseURL)/\(trainid)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAllTrains() async throws -> TrainResponse {
|
func fetchAllTrains() async throws -> TrainResponse {
|
||||||
try await get(trainsBaseURL)
|
try await get(trainsBaseURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchAllTrainStatus(num trainNum: String) async throws -> [Train] {
|
||||||
|
let reponse = try await fetchTrain(number: trainNum)
|
||||||
|
return reponse.values.flatMap(\.self)
|
||||||
|
}
|
||||||
|
|
||||||
func fetchStation(code stationid: String) async throws -> StationResponse {
|
func fetchStation(code stationid: String) async throws -> StationResponse {
|
||||||
try await get("\(stationBaseURL)/\(stationid)")
|
try await get("\(stationBaseURL)/\(stationid)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchAllStations() async throws -> StationResponse {
|
func fetchAllStations() async throws -> StationResponse {
|
||||||
try await get(stationBaseURL)
|
try await get(stationBaseURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func trainNumber(fromTrainId trainId: String) -> String {
|
func trainNumber(fromTrainId trainId: String) -> String {
|
||||||
trainId.split(separator: "-").first.map(String.init) ?? trainId
|
trainId.split(separator: "-").first.map(String.init) ?? trainId
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchStationTrainNumbers(stationCode code: String) async throws -> [String] {
|
func fetchStationTrainNumbers(stationCode code: String) async throws -> [String] {
|
||||||
let reponse = try await fetchStation(code: code)
|
let reponse = try await fetchStation(code: code)
|
||||||
|
|
||||||
guard let meta = reponse[code] else {
|
guard let meta = reponse[code] else {
|
||||||
throw AmtrakError.stationNotFound(code)
|
throw AmtrakError.stationNotFound(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array(Set(meta.trains.map(trainNumber(fromTrainId:))))
|
return Array(Set(meta.trains.map(trainNumber(fromTrainId:))))
|
||||||
}
|
}
|
||||||
|
|
||||||
func get<T: Decodable>(_ urlString: String) async throws -> T {
|
func get<T: Decodable>(_ urlString: String) async throws -> T {
|
||||||
|
|
||||||
guard let url = URL(string: urlString) else {
|
guard let url = URL(string: urlString) else {
|
||||||
throw AmtrakError.invalidURL(urlString)
|
throw AmtrakError.invalidURL(urlString)
|
||||||
}
|
}
|
||||||
|
|
||||||
let (data, reponse) = try await URLSession.shared.data(from: url)
|
let (data, reponse) = try await URLSession.shared.data(from: url)
|
||||||
guard let http = reponse as? HTTPURLResponse else {
|
guard let http = reponse as? HTTPURLResponse else {
|
||||||
throw AmtrakError.invalidResponse
|
throw AmtrakError.invalidResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
guard (200..<300).contains(http.statusCode) else {
|
guard (200 ..< 300).contains(http.statusCode) else {
|
||||||
throw AmtrakError.httpStatus(http.statusCode)
|
throw AmtrakError.httpStatus(http.statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return try decoder.decode(T.self, from: data)
|
return try decoder.decode(T.self, from: data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import SwiftCrossUI
|
|
||||||
import DefaultBackend
|
import DefaultBackend
|
||||||
|
import SwiftCrossUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct AmtrakStatusApp: App {
|
struct AmtrakStatusApp: App {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ import SwiftCrossUI
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@State var client = AmtrakClient()
|
@State var client = AmtrakClient()
|
||||||
|
|
||||||
@State var stationCode = "ASD" // Ashland, VA
|
@State var stationCode = "ASD" // Ashland, VA
|
||||||
@State var rows: [TrainStatusRow] = []
|
@State var rows: [TrainStatusRow] = []
|
||||||
@State var isLoading = false
|
@State var isLoading = false
|
||||||
@State var errorMessage: String?
|
@State var errorMessage: String?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
// TODO: Add pre-selected options in the form of buttons like the original
|
// TODO: Add pre-selected options in the form of buttons like the original
|
||||||
|
|
@ -15,28 +15,25 @@ struct ContentView: View {
|
||||||
Text("Station Code:")
|
Text("Station Code:")
|
||||||
TextField("e.g. ASD", text: $stationCode)
|
TextField("e.g. ASD", text: $stationCode)
|
||||||
.frame(width: 100)
|
.frame(width: 100)
|
||||||
|
|
||||||
Button("Refresh")
|
Button("Refresh") {
|
||||||
{
|
|
||||||
Task {
|
Task {
|
||||||
await loadTrainStatus(forStationCode: normalizedStationCode)
|
await loadTrainStatus(forStationCode: normalizedStationCode)
|
||||||
}
|
}
|
||||||
}.disabled(normalizedStationCode.isEmpty || isLoading)
|
}.disabled(normalizedStationCode.isEmpty || isLoading)
|
||||||
}.padding(10)
|
}.padding(10)
|
||||||
|
|
||||||
if let errorMessage {
|
if let errorMessage {
|
||||||
Text(errorMessage).padding(10)
|
Text(errorMessage).padding(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
Table(rows)
|
Table(rows) {
|
||||||
{
|
|
||||||
TableColumn("Number", value: \TrainStatusRow.trainNum)
|
TableColumn("Number", value: \TrainStatusRow.trainNum)
|
||||||
TableColumn("Number", value: \TrainStatusRow.routeName)
|
TableColumn("Number", value: \TrainStatusRow.routeName)
|
||||||
TableColumn("On Time", value: \TrainStatusRow.onTime)
|
TableColumn("On Time", value: \TrainStatusRow.onTime)
|
||||||
TableColumn("Platform", value: \TrainStatusRow.platform)
|
TableColumn("Platform", value: \TrainStatusRow.platform)
|
||||||
|
|
||||||
}.overlay(alignment: .bottomTrailing)
|
}.overlay(alignment: .bottomTrailing) {
|
||||||
{
|
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.padding(10)
|
.padding(10)
|
||||||
|
|
@ -49,39 +46,53 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var normalizedStationCode: String {
|
var normalizedStationCode: String {
|
||||||
stationCode.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
|
stationCode.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTrainStatus(forStationCode code: String) async {
|
func loadTrainStatus(forStationCode code: String) async {
|
||||||
guard !code.isEmpty else {
|
guard !code.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
errorMessage = nil
|
errorMessage = nil
|
||||||
|
|
||||||
defer { isLoading = false }
|
defer { isLoading = false }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let trainNumbers = try await client.fetchStationTrainNumbers(
|
let trainNumbers = try await client.fetchStationTrainNumbers(
|
||||||
stationCode: code
|
stationCode: code,
|
||||||
)
|
)
|
||||||
|
|
||||||
guard !trainNumbers.isEmpty else {
|
guard !trainNumbers.isEmpty else {
|
||||||
errorMessage = "Station \(code) has no trains listed right now"
|
errorMessage = "Station \(code) has no trains listed right now"
|
||||||
rows = []
|
rows = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var newRows: [TrainStatusRow] = []
|
var newRows: [TrainStatusRow] = []
|
||||||
for number in trainNumbers {
|
for number in trainNumbers {
|
||||||
// let trains = await client.fet
|
let trains = try await client.fetchAllTrainStatus(num: number)
|
||||||
|
for train in trains {
|
||||||
|
let platform = train.stations?.first(
|
||||||
|
where: { $0.code == code
|
||||||
|
},
|
||||||
|
)?.platform
|
||||||
|
newRows.append(TrainStatusRow(
|
||||||
|
trainID: train.trainID,
|
||||||
|
trainNum: train.trainNum,
|
||||||
|
routeName: train.routeName,
|
||||||
|
onTime: train.trainTimely.isEmpty ? "" : train.trainTimely,
|
||||||
|
platform: (
|
||||||
|
platform?.isEmpty == false,
|
||||||
|
) ? platform! : "",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
rows = newRows
|
||||||
} catch AmtrakError.stationNotFound(let code)
|
} catch let AmtrakError.stationNotFound(code) {
|
||||||
{
|
|
||||||
errorMessage = "Station \(code) not foudn"
|
errorMessage = "Station \(code) not foudn"
|
||||||
rows = []
|
rows = []
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,9 @@ typealias TrainResponse = [String: [Train]]
|
||||||
typealias StationResponse = [String: StationMeta]
|
typealias StationResponse = [String: StationMeta]
|
||||||
|
|
||||||
struct TrainStatusRow: Identifiable {
|
struct TrainStatusRow: Identifiable {
|
||||||
var id: String { trainID }
|
var id: String {
|
||||||
|
trainID
|
||||||
|
}
|
||||||
|
|
||||||
var trainID: String
|
var trainID: String
|
||||||
var trainNum: String
|
var trainNum: String
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue