diff --git a/app/Common/Utils/networkUtils.swift b/app/Common/Utils/networkUtils.swift index 65d10d51..65ccda75 100644 --- a/app/Common/Utils/networkUtils.swift +++ b/app/Common/Utils/networkUtils.swift @@ -18,50 +18,69 @@ enum FetchError: case InvalidaData } -typealias DeparturesByGtfsIDs = [String: [ApiDeparture]] +typealias GroupedDepartures = [String: [ApiDeparture]] -func getStationDepartures(station: String) async throws -> [ApiDeparture] { - let endpoint = "\(METRO_NOW_API)/metro?station=\(station)&ungrouped=true" +enum GroupBy: String { + case platform + case heading +} + +func getDepartures(stations: [String] = [], gtfsIDs: [String] = [], groupBy: GroupBy) async throws -> GroupedDepartures { + if stations.count == 0, gtfsIDs.count == 0 { + print("No station & No GtfsID") + return [:] + } + + let platformParam = (gtfsIDs.map { "platform=\($0)" }).joined(separator: "&") + let stationParam = (stations.map { "station=\($0)" }).joined(separator: "&") + let endpoint = "\(METRO_NOW_API)/metro?\(platformParam)&\(stationParam)&groupBy=\(groupBy.rawValue)" + print(endpoint) guard let url = URL(string: endpoint) else { throw FetchError.InvalidURL } let (data, response) = try await URLSession.shared.data(from: url) guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { print("Response not 200") - return [] + return [:] } do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .iso8601 - return try decoder.decode([ApiDeparture].self, from: data) + + return try decoder.decode(GroupedDepartures.self, from: data) + } catch { throw FetchError.InvalidaData } } -func getDeparturesByGtfsID(gtfsIDs: [String]) async throws -> DeparturesByGtfsIDs { - let params = (gtfsIDs.map { "platform=\($0)" }).joined(separator: "&") - let endpoint = "\(METRO_NOW_API)/metro?\(params)" +func getDepartures(stations: [String] = [], gtfsIDs: [String] = []) async throws -> [ApiDeparture] { + if stations.count == 0, gtfsIDs.count == 0 { + print("No station & No GtfsID") + return [] + } + let platformParam = (gtfsIDs.map { "platform=\($0)" }).joined(separator: "&") + let stationParam = (stations.map { "station=\($0)" }).joined(separator: "&") + let endpoint = "\(METRO_NOW_API)/metro?\(platformParam)&\(stationParam)" + print(endpoint) guard let url = URL(string: endpoint) else { throw FetchError.InvalidURL } + let (data, response) = try await URLSession.shared.data(from: url) guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { print("Response not 200") - return [:] + return [] } do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .iso8601 - return try decoder.decode(DeparturesByGtfsIDs.self, from: data) + + return try decoder.decode([ApiDeparture].self, from: data) + } catch { throw FetchError.InvalidaData } } - -func getDeparturesByGtfsID(_ gtfsID: String) async throws -> [ApiDeparture]? { - let res = try await (getDeparturesByGtfsID(gtfsIDs: [gtfsID])) - return res[gtfsID] -} diff --git a/app/metro-now-widgets/Core/Provider.swift b/app/metro-now-widgets/Core/Provider.swift index 5922042b..707c8855 100644 --- a/app/metro-now-widgets/Core/Provider.swift +++ b/app/metro-now-widgets/Core/Provider.swift @@ -19,7 +19,7 @@ struct Provider: TimelineProvider { Task { let gtfsIDs = closestStation.properties.platforms.map(\.gtfsId) - let departures = try! await getDeparturesByGtfsID(gtfsIDs: gtfsIDs) + let departures = await (try! getDepartures(gtfsIDs: gtfsIDs, groupBy: .platform)) var parsedDepartures: [WidgetEntryDeparture] = [] @@ -66,7 +66,7 @@ struct Provider: TimelineProvider { Task { let gtfsIDs = closestStation.properties.platforms.map(\.gtfsId) - let departures = try! await getDeparturesByGtfsID(gtfsIDs: gtfsIDs) + let departures = await (try! getDepartures(gtfsIDs: gtfsIDs, groupBy: .platform)) var entries: [WidgetEntry] = [] var parsedDepartures: [WidgetEntryDeparture] = [] diff --git a/app/metro-now/Core/Map/StationLocationMapView.swift b/app/metro-now/Core/Map/StationLocationMapView.swift index 13f4260d..e7ae704e 100644 --- a/app/metro-now/Core/Map/StationLocationMapView.swift +++ b/app/metro-now/Core/Map/StationLocationMapView.swift @@ -9,7 +9,7 @@ struct StationLocationMapView: View { let mapUrl: URL @StateObject private var locationModel = LocationModel() @State var distance: Double = -1 - @State private var departures: [ApiDeparture] = [] + @State private var departures: GroupedDepartures = [:] @State private var errorMessage: String? @State private var cameraPosition: MapCameraPosition @@ -101,14 +101,16 @@ struct StationLocationMapView: View { Spacer() } - ForEach(departures, id: \.departure) { departure in - let departureDates = [departure.departure] + ForEach(Array(departures.keys), id: \.self) { k in + let d = departures[k]! - PlatformListItemView( - direction: departure.heading, - departureDates: departureDates, - metroLine: departure.line - ) + if d.count > 0 { + PlatformListItemView( + direction: d[0].heading, + departureDates: d.map(\.departure), + metroLine: d[0].line + ) + } } } } @@ -119,7 +121,6 @@ struct StationLocationMapView: View { .toolbarBackground(.thinMaterial, for: .navigationBar) .toolbarBackground(.automatic, for: .navigationBar) .onReceive(locationModel.$location) { location in - guard let location else { print("Unknown location") return @@ -137,7 +138,7 @@ struct StationLocationMapView: View { distance = userLocation.distance(from: stationLocation) }.refreshable { do { - departures = try await getStationDepartures(station: stationName) + departures = try await getDepartures(stations: [stationName], groupBy: .heading) } catch { errorMessage = "Failed to fetch departures: \(error)" } @@ -145,7 +146,8 @@ struct StationLocationMapView: View { .onAppear { Task { do { - departures = try await getStationDepartures(station: stationName) + departures = try await (getDepartures(stations: [stationName], groupBy: .heading)) + } catch { errorMessage = "Failed to fetch departures: \(error)" } diff --git a/app/metro-now/Core/PlatformDetail/PlatformDetailView.swift b/app/metro-now/Core/PlatformDetail/PlatformDetailView.swift index a811f17c..7c98be97 100644 --- a/app/metro-now/Core/PlatformDetail/PlatformDetailView.swift +++ b/app/metro-now/Core/PlatformDetail/PlatformDetailView.swift @@ -42,7 +42,7 @@ struct PlatformDetailView: View { .padding(.top, 50) .task { do { - departures = try await getDeparturesByGtfsID(gtfsID) + departures = try await (getDepartures(gtfsIDs: [gtfsID], groupBy: .platform))[gtfsID] } catch { print(error) @@ -50,7 +50,7 @@ struct PlatformDetailView: View { } .refreshable { do { - departures = try await getDeparturesByGtfsID(gtfsID) + departures = try await (getDepartures(gtfsIDs: [gtfsID], groupBy: .platform))[gtfsID] } catch { print(error) diff --git a/app/metro-now/Core/PlatformList/PlatformListView.swift b/app/metro-now/Core/PlatformList/PlatformListView.swift index 73a3e7df..05ad3942 100644 --- a/app/metro-now/Core/PlatformList/PlatformListView.swift +++ b/app/metro-now/Core/PlatformList/PlatformListView.swift @@ -14,8 +14,8 @@ struct Departure { struct PlatformsListView: View { var station: MetroStationsGeoJSONFeature? -// @StateObject private var viewModel = PlatformListViewModel() - @State private var departuresByGtfsID: DeparturesByGtfsIDs? + // @StateObject private var viewModel = PlatformListViewModel() + @State private var departuresByGtfsID: GroupedDepartures? var body: some View { NavigationStack { @@ -24,61 +24,63 @@ struct PlatformsListView: View { if let station, departuresByGtfsID != nil { ForEach(station.properties.platforms, id: \.gtfsId) { platform in PlatformListItemView( - direction: platform.direction, departureDates: [], metroLine: platform.name + direction: platform.direction, + departureDates: [], + metroLine: platform.name ) // if departuresByGtfsID?.contains(k -> k == platform.gtfsId) { -// PlatformListItemView( -// direction: "platform.direction", -// departureDates: Date.now() , // departuresByGtfsID[platform.gtfsId].map(\.departureTimestamp.predicted) as! [Date], -// metroLine: "A" // platform.name -// ) -// } -// else { -// Text("No departures for this platform") -// .foregroundColor(.red) -// .font(.headline) -// } -// let platformDepartures = departuresByGtfsID?[platform.gtfsId] -// let platformDepartures = departuresByGtfsID?[platform.gtfsId] -// let direction = platformDepartures.first?.heading ?? platform.direction + // PlatformListItemView( + // direction: "platform.direction", + // departureDates: Date.now() , // departuresByGtfsID[platform.gtfsId].map(\.departureTimestamp.predicted) as! [Date], + // metroLine: "A" // platform.name + // ) + // } + // else { + // Text("No departures for this platform") + // .foregroundColor(.red) + // .font(.headline) + // } + // let platformDepartures = departuresByGtfsID?[platform.gtfsId] + // let platformDepartures = departuresByGtfsID?[platform.gtfsId] + // let direction = platformDepartures.first?.heading ?? platform.direction -// NavigationLink { -// PlatformDetailView( -// defaultDirection: platform.direction, -// gtfsID: platform.gtfsId -// ) -// } -// label: { -// PlatformListItemView( -// direction: platform.direction, -// departureDates: platformDepartures.map(\.departureTimestamp.predicted) as! [Date], -// metroLine: "A" // platform.name -// ) -// } + // NavigationLink { + // PlatformDetailView( + // defaultDirection: platform.direction, + // gtfsID: platform.gtfsId + // ) + // } + // label: { + // PlatformListItemView( + // direction: platform.direction, + // departureDates: platformDepartures.map(\.departureTimestamp.predicted) as! [Date], + // metroLine: "A" // platform.name + // ) + // } } } -// if let station, departuresByGtfsID != nil { -// ForEach(station.properties.platforms, id: \.gtfsId) { platform in -// let platformDepartures = departuresByGtfsID?[platform.gtfsId] ?? [] -// -// let direction = platformDepartures.first?.heading ?? platform.direction -// -// NavigationLink { -// PlatformDetailView( -// defaultDirection: direction, -// gtfsID: platform.gtfsId -// ) -// } -// label: { -// PlatformListItemView( -// direction: direction, -// departureDates: platformDepartures.map(\.departureTimestamp.predicted) as! [Date], -// metroLine: "A" // platform.name -// ) -// } -// } -// -// } + // if let station, departuresByGtfsID != nil { + // ForEach(station.properties.platforms, id: \.gtfsId) { platform in + // let platformDepartures = departuresByGtfsID?[platform.gtfsId] ?? [] + // + // let direction = platformDepartures.first?.heading ?? platform.direction + // + // NavigationLink { + // PlatformDetailView( + // defaultDirection: direction, + // gtfsID: platform.gtfsId + // ) + // } + // label: { + // PlatformListItemView( + // direction: direction, + // departureDates: platformDepartures.map(\.departureTimestamp.predicted) as! [Date], + // metroLine: "A" // platform.name + // ) + // } + // } + // + // } } .padding(10) } @@ -91,8 +93,7 @@ struct PlatformsListView: View { do { let gtfsIDs = station.properties.platforms.map(\.gtfsId) - departuresByGtfsID = try await getDeparturesByGtfsID(gtfsIDs: gtfsIDs) - print(departuresByGtfsID) + departuresByGtfsID = try await (getDepartures(gtfsIDs: gtfsIDs, groupBy: .platform)) } catch { print(error) } @@ -104,69 +105,80 @@ struct PlatformsListView: View { do { let gtfsIDs = station.properties.platforms.map(\.gtfsId) - departuresByGtfsID = try await getDeparturesByGtfsID(gtfsIDs: gtfsIDs) - print(departuresByGtfsID) + departuresByGtfsID = try await (getDepartures(gtfsIDs: gtfsIDs, groupBy: .platform)) } catch { print(error) } } -// .onAppear { -// viewModel.$departuresByGtfsID -// // guard let station else { + // .onAppear { + // viewModel.$departuresByGtfsID + // // guard let station else { //// return //// } //// //// do { //// try await viewModel.getData(gtfsIDs: station.properties.platforms.map(\.gtfsId)) //// } catch {} -// } -// .refreshable { -// guard let station else { -// return -// } -// -// do { -// try await viewModel.getData(gtfsIDs: station.properties.platforms.map(\.gtfsId)) -// } catch { -// print(error) -// }// -// } + // } + // .refreshable { + // guard let station else { + // return + // } + // + // do { + // try await viewModel.getData(gtfsIDs: station.properties.platforms.map(\.gtfsId)) + // } catch { + // print(error) + // }// + // } } } } #Preview("Muzeum") { - PlatformsListView(station: getClosestStationFromGeoJSON( - location: MUZEUM_COORDINATES - )) + PlatformsListView( + station: getClosestStationFromGeoJSON( + location: MUZEUM_COORDINATES + ) + ) } #Preview("Florenc") { - PlatformsListView(station: getClosestStationFromGeoJSON( - location: FLORENC_COORDINATES - )) + PlatformsListView( + station: getClosestStationFromGeoJSON( + location: FLORENC_COORDINATES + ) + ) } #Preview("Můstek") { - PlatformsListView(station: getClosestStationFromGeoJSON( - location: MUSTEK_COORDINATES - )) + PlatformsListView( + station: getClosestStationFromGeoJSON( + location: MUSTEK_COORDINATES + ) + ) } #Preview("Dejvická") { - PlatformsListView(station: getClosestStationFromGeoJSON( - location: DEJVICKA_COORDINATES - )) + PlatformsListView( + station: getClosestStationFromGeoJSON( + location: DEJVICKA_COORDINATES + ) + ) } #Preview("Hlavní nádraží") { - PlatformsListView(station: getClosestStationFromGeoJSON( - location: HLAVNI_NADRAZI_COORDINATES - )) + PlatformsListView( + station: getClosestStationFromGeoJSON( + location: HLAVNI_NADRAZI_COORDINATES + ) + ) } #Preview("Černý Most") { - PlatformsListView(station: getClosestStationFromGeoJSON( - location: CERNY_MOST_COORDINATES - )) + PlatformsListView( + station: getClosestStationFromGeoJSON( + location: CERNY_MOST_COORDINATES + ) + ) } diff --git a/app/metro-now/Core/TabBar/MainTabView.swift b/app/metro-now/Core/TabBar/MainTabView.swift index 4bb4e6d2..a8ed27f7 100644 --- a/app/metro-now/Core/TabBar/MainTabView.swift +++ b/app/metro-now/Core/TabBar/MainTabView.swift @@ -18,7 +18,7 @@ struct MainTabView: View { if let closestStation { PlatformsListView( station: closestStation) .tabItem { - Label("Near me", systemImage: "location.circle") + Label("Near me", systemImage: "tram.fill.tunnel") } } @@ -44,3 +44,7 @@ struct MainTabView: View { } } } + +#Preview { + MainTabView() +}