Skip to content

Commit

Permalink
feat: Display route pills on the stop page (#133)
Browse files Browse the repository at this point in the history
* feat: Display route pills on the stop page

* fix: Update clear filter test

* fix: Remove unnecessary @ViewBuilder annotations
  • Loading branch information
EmmaSimon authored Apr 18, 2024
1 parent bf2d08c commit 97d3d25
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ struct StopDetailsFilteredRouteView: View {
}

var body: some View {
Button(action: { filter = nil }, label: { Text("Clear Filter") })
List {
RoutePillSection(route: patternsByStop.route) {
ForEach(rows, id: \.tripId) { row in
Expand Down
80 changes: 63 additions & 17 deletions iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// Copyright © 2024 MBTA. All rights reserved.
//

import Foundation
import shared
import SwiftPhoenixClient
import SwiftUI
Expand All @@ -20,6 +19,8 @@ struct StopDetailsPage: View {
var stop: Stop
@Binding var filter: StopDetailsFilter?
@State var now = Date.now
@State var departures: StopDetailsDepartures?
@State var servedRoutes: [Route] = []

let inspection = Inspection<Self>()
let timer = Timer.publish(every: 5, on: .main, in: .common).autoconnect()
Expand All @@ -42,36 +43,61 @@ struct StopDetailsPage: View {

var body: some View {
VStack {
if predictionsFetcher.predictions != nil {
Text("Live departures")
} else if scheduleFetcher.schedules != nil {
Text("Scheduled departures")
} else {
Text("Departures")
}
if let globalResponse = globalFetcher.response {
StopDetailsRoutesView(departures: StopDetailsDepartures(
stop: stop,
global: globalResponse,
schedules: scheduleFetcher.schedules,
predictions: predictionsFetcher.predictions,
filterAtTime: now.toKotlinInstant()
), now: now.toKotlinInstant(), filter: $filter)
routePills
clearFilterButton
departureHeader
if let departures {
StopDetailsRoutesView(departures: departures, now: now.toKotlinInstant(), filter: $filter)
} else {
ProgressView()
}
}
.navigationTitle(stop.name)
.onAppear { changeStop(stop) }
.onChange(of: stop) { nextStop in changeStop(nextStop) }
.onChange(of: globalFetcher.response) { _ in updateDepartures() }
.onChange(of: predictionsFetcher.predictions) { _ in updateDepartures() }
.onChange(of: scheduleFetcher.schedules) { _ in updateDepartures() }
.onReceive(inspection.notice) { inspection.visit(self, $0) }
.onReceive(timer) { input in now = input }
.onReceive(timer) { input in
now = input
updateDepartures()
}
.onDisappear { leavePredictions() }
}

private var routePills: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(servedRoutes, id: \.id) { route in
RoutePill(route: route)
}
}
.padding(.horizontal, 15)
}
}

@ViewBuilder
private var clearFilterButton: some View {
if filter != nil {
Button(action: { filter = nil }, label: { Text("Clear Filter") })
}
}

private var departureHeader: some View {
if predictionsFetcher.predictions != nil {
Text("Live departures")
} else if scheduleFetcher.schedules != nil {
Text("Scheduled departures")
} else {
Text("Departures")
}
}

func changeStop(_ stop: Stop) {
getSchedule(stop)
joinPredictions(stop)
updateDepartures(stop)
viewportProvider.animateTo(coordinates: stop.coordinate)
}

Expand All @@ -92,4 +118,24 @@ struct StopDetailsPage: View {
predictionsFetcher.leave()
}
}

func updateDepartures(_ stop: Stop? = nil) {
let stop = stop ?? self.stop
servedRoutes = []
departures = if let globalResponse = globalFetcher.response {
StopDetailsDepartures(
stop: stop,
global: globalResponse,
schedules: scheduleFetcher.schedules,
predictions: predictionsFetcher.predictions,
filterAtTime: now.toKotlinInstant()
)
} else {
nil
}
if let departures {
servedRoutes = Set(departures.routes.map { pattern in pattern.route })
.sorted { $0.sortOrder < $1.sortOrder }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,4 @@ final class StopDetailsFilteredRouteViewTests: XCTestCase {
XCTAssertNotNil(try sut.inspect().find(text: "North"))
XCTAssertNil(try? sut.inspect().find(text: "South"))
}

func testClearsFilter() throws {
let (departures: departures, routeId: routeId, now: now) = testData()

let filter = Binding<StopDetailsFilter?>(wrappedValue: .init(routeId: routeId, directionId: 1))

let sut = StopDetailsFilteredRouteView(departures: departures, now: now, filter: filter)

try sut.inspect().find(button: "Clear Filter").tap()

XCTAssertNil(filter.wrappedValue)
}
}
22 changes: 22 additions & 0 deletions iosApp/iosAppTests/Pages/StopDetails/StopDetailsPageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,26 @@ final class StopDetailsPageTests: XCTestCase {
ViewHosting.host(view: sut)
wait(for: [exp], timeout: 2)
}

func testClearsFilter() throws {
let objects = ObjectCollectionBuilder()
let route = objects.route()
let stop = objects.stop { _ in }
let routePattern = objects.routePattern(route: route) { _ in }

let viewportProvider: ViewportProvider = .init(viewport: .followPuck(zoom: 1))
let filter: Binding<StopDetailsFilter?> = .init(wrappedValue: .init(routeId: route.id, directionId: routePattern.directionId))

let sut = StopDetailsPage(
backend: IdleBackend(),
socket: MockSocket(),
globalFetcher: .init(backend: IdleBackend()),
viewportProvider: viewportProvider,
stop: stop,
filter: filter
)

try sut.inspect().find(button: "Clear Filter").tap()
XCTAssertNil(filter.wrappedValue)
}
}

0 comments on commit 97d3d25

Please sign in to comment.