diff --git a/Package.swift b/Package.swift index 9a6cf1f..493d355 100644 --- a/Package.swift +++ b/Package.swift @@ -8,7 +8,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/moreSwift/swift-cross-ui", - .upToNextMinor(from: "0.7.0") + .upToNextMinor(from: "0.7.0"), ), ], targets: [ @@ -17,7 +17,7 @@ let package = Package( dependencies: [ .product(name: "SwiftCrossUI", package: "swift-cross-ui"), .product(name: "DefaultBackend", package: "swift-cross-ui"), - ] + ], ), - ] + ], ) diff --git a/Sources/AmtrakStatus/AmtrakClient.swift b/Sources/AmtrakStatus/AmtrakClient.swift index 70fbb70..93cef6a 100644 --- a/Sources/AmtrakStatus/AmtrakClient.swift +++ b/Sources/AmtrakStatus/AmtrakClient.swift @@ -9,56 +9,60 @@ enum AmtrakError: Error { struct AmtrakClient { var decoder = JSONDecoder() - + 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" - + func fetchTrain(number trainid: String) async throws -> TrainResponse { try await get("\(trainsBaseURL)/\(trainid)") } - + func fetchAllTrains() async throws -> TrainResponse { 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 { try await get("\(stationBaseURL)/\(stationid)") } - + func fetchAllStations() async throws -> StationResponse { try await get(stationBaseURL) } - + func trainNumber(fromTrainId trainId: String) -> String { trainId.split(separator: "-").first.map(String.init) ?? trainId } - + func fetchStationTrainNumbers(stationCode code: String) async throws -> [String] { let reponse = try await fetchStation(code: code) - + guard let meta = reponse[code] else { throw AmtrakError.stationNotFound(code) } - + return Array(Set(meta.trains.map(trainNumber(fromTrainId:)))) } - + func get(_ urlString: String) async throws -> T { - guard let url = URL(string: urlString) else { throw AmtrakError.invalidURL(urlString) } - + let (data, reponse) = try await URLSession.shared.data(from: url) guard let http = reponse as? HTTPURLResponse else { throw AmtrakError.invalidResponse } - - guard (200..<300).contains(http.statusCode) else { + + guard (200 ..< 300).contains(http.statusCode) else { throw AmtrakError.httpStatus(http.statusCode) } - + return try decoder.decode(T.self, from: data) } } diff --git a/Sources/AmtrakStatus/AmtrakStatusApp.swift b/Sources/AmtrakStatus/AmtrakStatusApp.swift index 0c42f21..18c1871 100644 --- a/Sources/AmtrakStatus/AmtrakStatusApp.swift +++ b/Sources/AmtrakStatus/AmtrakStatusApp.swift @@ -1,5 +1,5 @@ -import SwiftCrossUI import DefaultBackend +import SwiftCrossUI @main struct AmtrakStatusApp: App { diff --git a/Sources/AmtrakStatus/ContentView.swift b/Sources/AmtrakStatus/ContentView.swift index 0137798..2e52857 100644 --- a/Sources/AmtrakStatus/ContentView.swift +++ b/Sources/AmtrakStatus/ContentView.swift @@ -2,12 +2,12 @@ import SwiftCrossUI struct ContentView: View { @State var client = AmtrakClient() - + @State var stationCode = "ASD" // Ashland, VA @State var rows: [TrainStatusRow] = [] @State var isLoading = false @State var errorMessage: String? - + var body: some View { VStack { // TODO: Add pre-selected options in the form of buttons like the original @@ -15,28 +15,25 @@ struct ContentView: View { Text("Station Code:") TextField("e.g. ASD", text: $stationCode) .frame(width: 100) - - Button("Refresh") - { + + Button("Refresh") { Task { await loadTrainStatus(forStationCode: normalizedStationCode) } }.disabled(normalizedStationCode.isEmpty || isLoading) }.padding(10) - + if let errorMessage { Text(errorMessage).padding(10) } - - Table(rows) - { + + Table(rows) { TableColumn("Number", value: \TrainStatusRow.trainNum) TableColumn("Number", value: \TrainStatusRow.routeName) TableColumn("On Time", value: \TrainStatusRow.onTime) TableColumn("Platform", value: \TrainStatusRow.platform) - - }.overlay(alignment: .bottomTrailing) - { + + }.overlay(alignment: .bottomTrailing) { if isLoading { ProgressView() .padding(10) @@ -49,39 +46,53 @@ struct ContentView: View { } } } - + var normalizedStationCode: String { stationCode.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() } - + func loadTrainStatus(forStationCode code: String) async { guard !code.isEmpty else { return } - + isLoading = true errorMessage = nil - + defer { isLoading = false } - + do { let trainNumbers = try await client.fetchStationTrainNumbers( - stationCode: code + stationCode: code, ) - + guard !trainNumbers.isEmpty else { errorMessage = "Station \(code) has no trains listed right now" rows = [] return } - + var newRows: [TrainStatusRow] = [] 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! : "", + )) + } } - - } catch AmtrakError.stationNotFound(let code) - { + rows = newRows + } catch let AmtrakError.stationNotFound(code) { errorMessage = "Station \(code) not foudn" rows = [] } catch { diff --git a/Sources/AmtrakStatus/Models.swift b/Sources/AmtrakStatus/Models.swift index ef626ef..bc86c84 100644 --- a/Sources/AmtrakStatus/Models.swift +++ b/Sources/AmtrakStatus/Models.swift @@ -66,7 +66,9 @@ typealias TrainResponse = [String: [Train]] typealias StationResponse = [String: StationMeta] struct TrainStatusRow: Identifiable { - var id: String { trainID } + var id: String { + trainID + } var trainID: String var trainNum: String