Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: look up nearby stop IDs in global response #107

Merged
merged 4 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions iosApp/iosApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ struct ContentView: View {
prompt: "Find nearby transit"
).onAppear {
socketProvider.socket.connect()
Task {
try await globalFetcher.getGlobalData()
}
}.onChange(of: scenePhase) { newPhase in
if newPhase == .active {
socketProvider.socket.connect()
Expand All @@ -90,6 +93,7 @@ struct ContentView: View {
GeometryReader { proxy in
NearbyTransitPageView(
currentLocation: locationDataManager.currentLocation?.coordinate,
globalFetcher: globalFetcher,
nearbyFetcher: nearbyFetcher,
scheduleFetcher: scheduleFetcher,
predictionsFetcher: predictionsFetcher,
Expand Down
8 changes: 4 additions & 4 deletions iosApp/iosApp/Fetchers/Backend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation
import shared

protocol BackendProtocol {
func getGlobalData() async throws -> StopAndRoutePatternResponse
func getNearby(latitude: Double, longitude: Double) async throws -> StopAndRoutePatternResponse
func getGlobalData() async throws -> GlobalResponse
func getNearby(latitude: Double, longitude: Double) async throws -> NearbyResponse
func getSchedule(stopIds: [String]) async throws -> ScheduleResponse
func getSearchResults(query: String) async throws -> SearchResponse
func getRailRouteShapes() async throws -> RouteResponse
Expand All @@ -26,11 +26,11 @@ struct IdleBackend: BackendProtocol {
}
}

func getGlobalData() async throws -> StopAndRoutePatternResponse {
func getGlobalData() async throws -> GlobalResponse {
await hang()
}

func getNearby(latitude _: Double, longitude _: Double) async throws -> StopAndRoutePatternResponse {
func getNearby(latitude _: Double, longitude _: Double) async throws -> NearbyResponse {
await hang()
}

Expand Down
6 changes: 3 additions & 3 deletions iosApp/iosApp/Fetchers/GlobalFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import Foundation
import shared

class GlobalFetcher: ObservableObject {
var response: StopAndRoutePatternResponse?
@Published var stops: [Stop]
var response: GlobalResponse?
@Published var stops: [String: Stop]
@Published var routes: [String: Route]
let backend: any BackendProtocol

init(backend: any BackendProtocol, stops: [Stop] = [], routes: [String: Route] = [:]) {
init(backend: any BackendProtocol, stops: [String: Stop] = [:], routes: [String: Route] = [:]) {
self.backend = backend
self.stops = stops
self.routes = routes
Expand Down
6 changes: 3 additions & 3 deletions iosApp/iosApp/Fetchers/NearbyFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class NearbyFetcher: ObservableObject {
@Published var errorText: Text?
@Published var loadedLocation: CLLocationCoordinate2D?
@Published var loading: Bool = false
@Published var nearby: StopAndRoutePatternResponse?
@Published var nearby: NearbyResponse?
@Published var nearbyByRouteAndStop: NearbyStaticData?
let backend: any BackendProtocol

Expand All @@ -26,7 +26,7 @@ class NearbyFetcher: ObservableObject {
self.backend = backend
}

func getNearby(location: CLLocationCoordinate2D) async {
func getNearby(global: GlobalResponse, location: CLLocationCoordinate2D) async {
if location.isRoughlyEqualTo(loadedLocation) { return }
if loading, currentTask != nil, !currentTask!.isCancelled {
currentTask?.cancel()
Expand All @@ -42,7 +42,7 @@ class NearbyFetcher: ObservableObject {
)
try Task.checkCancellation()
self.nearby = response
self.nearbyByRouteAndStop = NearbyStaticData(response: response)
self.nearbyByRouteAndStop = NearbyStaticData(global: global, nearby: response)
self.loadedLocation = location
self.error = nil
self.errorText = nil
Expand Down
30 changes: 12 additions & 18 deletions iosApp/iosApp/Pages/Map/HomeMapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import SwiftUI

struct HomeMapView: View {
private let cameraDebouncer = Debouncer(delay: 0.25)
private let layerInitDispatchGroup = DispatchGroup()

@ObservedObject var globalFetcher: GlobalFetcher
@ObservedObject var nearbyFetcher: NearbyFetcher
Expand Down Expand Up @@ -68,6 +67,8 @@ struct HomeMapView: View {
.additionalSafeAreaInsets(.bottom, sheetHeight)
.accessibilityIdentifier("transitMap")
.onAppear { handleAppear(location: proxy.location, map: proxy.map) }
.onChange(of: globalFetcher.response) { _ in handleTryLayerInit(map: proxy.map) }
.onChange(of: railRouteShapeFetcher.response) { _ in handleTryLayerInit(map: proxy.map) }
.onChange(of: locationDataManager.authorizationStatus) { status in
if status == .authorizedAlways || status == .authorizedWhenInUse {
Task { viewportProvider.follow(animation: .easeInOut(duration: 0)) }
Expand All @@ -83,24 +84,9 @@ struct HomeMapView: View {

var didAppear: ((Self) -> Void)?

func handleAppear(location: LocationManager?, map: MapboxMap?) {
// Wait for routes and stops to both load before initializing layers
layerInitDispatchGroup.enter()
Task {
try await globalFetcher.getGlobalData()
layerInitDispatchGroup.leave()
}
layerInitDispatchGroup.enter()
func handleAppear(location: LocationManager?, map _: MapboxMap?) {
Task {
try await railRouteShapeFetcher.getRailRouteShapes()
layerInitDispatchGroup.leave()
}
layerInitDispatchGroup.notify(queue: .main) {
guard let map,
let globalResponse = globalFetcher.response,
let routeResponse = railRouteShapeFetcher.response
else { return }
handleLayerInit(map, globalResponse.stops, routeResponse)
}

// Set MapBox to use the current location to display puck
Expand Down Expand Up @@ -128,7 +114,15 @@ struct HomeMapView: View {
}
}

func handleLayerInit(_ map: MapboxMap, _ stops: [Stop], _ routeResponse: RouteResponse) {
func handleTryLayerInit(map: MapboxMap?) {
guard let map,
let globalResponse = globalFetcher.response,
let routeResponse = railRouteShapeFetcher.response
else { return }
handleLayerInit(map, globalResponse.stops, routeResponse)
}

func handleLayerInit(_ map: MapboxMap, _ stops: [String: Stop], _ routeResponse: RouteResponse) {
let layerManager = MapLayerManager(map: map)

let routeSourceGenerator = RouteSourceGenerator(routeData: routeResponse)
Expand Down
12 changes: 5 additions & 7 deletions iosApp/iosApp/Pages/Map/StopSourceGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ struct StopFeatureData {
}

class StopSourceGenerator {
let stops: [Stop]
let stops: [String: Stop]
let routeSourceDetails: [RouteSourceData]?

var stopSources: [GeoJSONSource] = []

private var stopsById: [String: Stop]
private var stopFeatures: [StopFeatureData] = []
private var touchedStopIds: Set<String> = []

Expand All @@ -30,11 +29,10 @@ class StopSourceGenerator {
"\(stopSourceId)-\(locationType.name)"
}

init(stops: [Stop], routeSourceDetails: [RouteSourceData]? = nil) {
init(stops: [String: Stop], routeSourceDetails: [RouteSourceData]? = nil) {
self.stops = stops
self.routeSourceDetails = routeSourceDetails

stopsById = stops.reduce(into: [String: Stop]()) { map, stop in map[stop.id] = stop }
stopFeatures = generateStopFeatures()
stopSources = generateStopSources()
}
Expand All @@ -48,8 +46,8 @@ class StopSourceGenerator {
return routeSourceDetails.flatMap { routeSource in
routeSource.lines.flatMap { lineData in
lineData.stopIds.compactMap { childStopId in
guard let stopOnRoute = stopsById[childStopId] else { return nil }
guard let stop = stopOnRoute.resolveParent(stopsById) else { return nil }
guard let stopOnRoute = stops[childStopId],
let stop = stopOnRoute.resolveParent(stops) else { return nil }

if touchedStopIds.contains(stop.id) { return nil }

Expand All @@ -65,7 +63,7 @@ class StopSourceGenerator {
}

func generateRemainingStops() -> [StopFeatureData] {
stops.compactMap { stop in
stops.values.compactMap { stop in
if touchedStopIds.contains(stop.id) { return nil }
if stop.parentStationId != nil { return nil }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import SwiftUI

struct NearbyTransitPageView: View {
let currentLocation: CLLocationCoordinate2D?
@ObservedObject var globalFetcher: GlobalFetcher
@ObservedObject var nearbyFetcher: NearbyFetcher
@ObservedObject var scheduleFetcher: ScheduleFetcher
@ObservedObject var predictionsFetcher: PredictionsFetcher
Expand All @@ -26,13 +27,15 @@ struct NearbyTransitPageView: View {

init(
currentLocation: CLLocationCoordinate2D?,
globalFetcher: GlobalFetcher,
nearbyFetcher: NearbyFetcher,
scheduleFetcher: ScheduleFetcher,
predictionsFetcher: PredictionsFetcher,
viewportProvider: ViewportProvider,
alertsFetcher: AlertsFetcher
) {
self.currentLocation = currentLocation
self.globalFetcher = globalFetcher
self.nearbyFetcher = nearbyFetcher
self.scheduleFetcher = scheduleFetcher
self.predictionsFetcher = predictionsFetcher
Expand All @@ -50,6 +53,7 @@ struct NearbyTransitPageView: View {
var body: some View {
NearbyTransitView(
locationProvider: locationProvider,
globalFetcher: globalFetcher,
nearbyFetcher: nearbyFetcher,
scheduleFetcher: scheduleFetcher,
predictionsFetcher: predictionsFetcher,
Expand Down
9 changes: 8 additions & 1 deletion iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SwiftUI
struct NearbyTransitView: View {
@Environment(\.scenePhase) private var scenePhase
@ObservedObject var locationProvider: NearbyTransitLocationProvider
@ObservedObject var globalFetcher: GlobalFetcher
@ObservedObject var nearbyFetcher: NearbyFetcher
@ObservedObject var scheduleFetcher: ScheduleFetcher
@ObservedObject var predictionsFetcher: PredictionsFetcher
Expand Down Expand Up @@ -47,6 +48,9 @@ struct NearbyTransitView: View {
joinPredictions()
didAppear?(self)
}
.onChange(of: globalFetcher.response) { _ in
getNearby(location: locationProvider.location)
}
.onChange(of: locationProvider.location) { newLocation in
getNearby(location: newLocation)
}
Expand Down Expand Up @@ -79,7 +83,10 @@ struct NearbyTransitView: View {
var didAppear: ((Self) -> Void)?

func getNearby(location: CLLocationCoordinate2D) {
Task { await nearbyFetcher.getNearby(location: location) }
Task {
guard let globalData = globalFetcher.response else { return }
await nearbyFetcher.getNearby(global: globalData, location: location)
}
}

func getSchedule() {
Expand Down
12 changes: 6 additions & 6 deletions iosApp/iosAppTests/Pages/Map/StopSourceGeneratorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
stop.parentStationId = "place-andrw"
}

let stopSourceGenerator = StopSourceGenerator(stops: [stop1, stop2, stop3, stop4, stop5, stop6])
let stopSourceGenerator = StopSourceGenerator(stops: [stop1.id: stop1, stop2.id: stop2, stop3.id: stop3, stop4.id: stop4, stop5.id: stop5, stop6.id: stop6])
let sources = stopSourceGenerator.stopSources
XCTAssertEqual(sources.count, 2)

Expand All @@ -75,7 +75,7 @@
XCTAssertNotNil(stationSource)
if case let .featureCollection(collection) = stationSource!.data.unsafelyUnwrapped {
XCTAssertEqual(collection.features.count, 3)
XCTAssertEqual(collection.features[0].geometry, .point(Point(stop1.coordinate)))
XCTAssertTrue(collection.features.contains(where: { $0.geometry == .point(Point(stop1.coordinate)) }))
} else {
XCTFail("Station source had no features")
}
Expand All @@ -84,7 +84,7 @@
XCTAssertNotNil(stopSource)
if case let .featureCollection(collection) = stopSource!.data.unsafelyUnwrapped {
XCTAssertEqual(collection.features.count, 2)
XCTAssertEqual(collection.features[0].geometry, .point(Point(stop4.coordinate)))
XCTAssertTrue(collection.features.contains(where: { $0.geometry == .point(Point(stop4.coordinate)) }))
} else {
XCTFail("Stop source had no features")
}
Expand All @@ -94,22 +94,22 @@
let objects = MapTestDataHelper.objects

let stops = [
objects.stop { stop in
"70061": objects.stop { stop in
stop.id = "70061"
stop.name = "Alewife"
stop.latitude = 42.396158
stop.longitude = -71.139971
stop.locationType = .stop
stop.parentStationId = "place-alfcl"
},
objects.stop { stop in
"place-alfcl": objects.stop { stop in
stop.id = "place-alfcl"
stop.name = "Alewife"
stop.latitude = 42.39583
stop.longitude = -71.141287
stop.locationType = .station
},
objects.stop { stop in
"place-astao": objects.stop { stop in
stop.id = "place-astao"
stop.name = "Assembly"
stop.latitude = 42.392811
Expand Down Expand Up @@ -161,7 +161,7 @@
shape.polyline = "}nwaG~|eqLGyNIqAAc@S_CAEWu@g@}@u@k@u@Wu@OMGIMISQkAOcAGw@SoDFkCf@sUXcJJuERwHPkENqCJmB^mDn@}D??D[TeANy@\\iAt@qB`AwBl@cAl@m@b@Yn@QrBEtCKxQ_ApMT??R?`m@hD`Np@jAF|@C`B_@hBi@n@s@d@gA`@}@Z_@RMZIl@@fBFlB\\tAP??~@L^?HCLKJWJ_@vC{NDGLQvG}HdCiD`@e@Xc@b@oAjEcPrBeGfAsCvMqVl@sA??jByD`DoGd@cAj@cBJkAHqBNiGXeHVmJr@kR~@q^HsB@U??NgDr@gJTcH`@aMFyCF}AL}DN}GL}CXkILaD@QFmA@[??DaAFiBDu@BkA@UB]Fc@Jo@BGJ_@Lc@\\}@vJ_OrCyDj@iAb@_AvBuF`@gA`@aAv@qBVo@Xu@??bDgI??Tm@~IsQj@cAr@wBp@kBj@kB??HWtDcN`@g@POl@UhASh@Eb@?t@FXHl@Px@b@he@h[pCC??bnAm@h@T??xF|BpBp@^PLBXAz@Yl@]l@e@|B}CT[p@iA|A}BZi@zDuF\\c@n@s@VObAw@^Sl@Yj@U\\O|@WdAUxAQRCt@E??xAGrBQZAhAGlAEv@Et@E~@AdAAbCGpCA|BEjCMr@?nBDvANlARdBb@nDbA~@XnBp@\\JRH??|Al@`AZbA^jA^lA\\h@P|@TxAZ|@J~@LN?fBXxHhApDt@b@JXFtAVhALx@FbADtAC`B?z@BHBH@|@f@RN^^T\\h@hANb@HZH`@H^LpADlA@dD@jD@x@@b@Bp@HdAFd@Ll@F^??n@rDBRl@vD^pATp@Rb@b@z@\\l@`@j@p@t@j@h@n@h@n@`@hAh@n@\\t@PzANpAApBGtE}@xBa@??xB_@nOmB`OgBb@IrC[p@MbEmARCV@d@LH?tDyAXM"
}

let stops = [

Check notice on line 164 in iosApp/iosAppTests/Pages/Map/StopSourceGeneratorTests.swift

View check run for this annotation

Xcode Cloud / mbtaapp | PR Branch Workflow | Test - iOS

iosApp/iosAppTests/Pages/Map/StopSourceGeneratorTests.swift#L164

Initialization of immutable value 'stops' was never used; consider replacing with assignment to '_' or removing it
objects.stop { stop in
stop.id = "70061"
stop.name = "Alewife"
Expand Down
47 changes: 47 additions & 0 deletions iosApp/iosAppTests/Views/ContentViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import ViewInspector
import XCTest

final class ContentViewTests: XCTestCase {
struct NotUnderTestError: Error {}

override func setUp() {
executionTimeAllowance = 60
}
Expand Down Expand Up @@ -75,6 +77,51 @@ final class ContentViewTests: XCTestCase {
wait(for: [connectedExpectation], timeout: 1)
}

func testFetchesGlobalData() throws {
struct FakeGlobalFetcherBackend: BackendProtocol {
let expectation: XCTestExpectation
let idle = IdleBackend()

func getGlobalData() async throws -> GlobalResponse {
expectation.fulfill()
throw NotUnderTestError()
}

func getNearby(latitude _: Double, longitude _: Double) async throws -> NearbyResponse {
throw NotUnderTestError()
}

func getSchedule(stopIds _: [String]) async throws -> ScheduleResponse {
throw NotUnderTestError()
}

func getSearchResults(query _: String) async throws -> SearchResponse {
throw NotUnderTestError()
}

func getRailRouteShapes() async throws -> RouteResponse {
throw NotUnderTestError()
}
}

let fetchesGlobalData = expectation(description: "fetches global data")

let sut = ContentView()
.environmentObject(LocationDataManager(locationFetcher: MockLocationFetcher()))
.environmentObject(AlertsFetcher(socket: FakeSocket()))
.environmentObject(GlobalFetcher(backend: FakeGlobalFetcherBackend(expectation: fetchesGlobalData)))
.environmentObject(NearbyFetcher(backend: IdleBackend()))
.environmentObject(PredictionsFetcher(socket: FakeSocket()))
.environmentObject(RailRouteShapeFetcher(backend: IdleBackend()))
.environmentObject(ScheduleFetcher(backend: IdleBackend()))
.environmentObject(SearchResultFetcher(backend: IdleBackend()))
.environmentObject(SocketProvider(socket: FakeSocket()))
.environmentObject(ViewportProvider())

ViewHosting.host(view: sut)
wait(for: [fetchesGlobalData], timeout: 1)
}

class FakeSocket: MockSocket {
let connectedExpecation: XCTestExpectation?
let disconnectedExpectation: XCTestExpectation?
Expand Down
Loading