Skip to content

Commit

Permalink
Merge branch 'main' into es-map-stop-tap
Browse files Browse the repository at this point in the history
  • Loading branch information
EmmaSimon committed Apr 10, 2024
2 parents 3b507a9 + f7ab56e commit 810b802
Show file tree
Hide file tree
Showing 18 changed files with 484 additions and 35 deletions.
48 changes: 40 additions & 8 deletions iosApp/iosApp.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// NearbyStopRoutePatternView.swift
// HeadsignRowView.swift
// iosApp
//
// Created by Simon, Emma on 3/5/24.
Expand All @@ -9,7 +9,7 @@
import shared
import SwiftUI

struct NearbyStopRoutePatternView: View {
struct HeadsignRowView: View {
let headsign: String
let predictions: PatternsByHeadsign.Format

Expand Down Expand Up @@ -43,16 +43,18 @@ struct NearbyStopRoutePatternView_Previews: PreviewProvider {
prediction.trip = trip
prediction.departureTime = now.addingTimeInterval(5 * 60).toKotlinInstant()
}
NearbyStopRoutePatternView(headsign: "Some", predictions: PatternsByHeadsign.FormatSome(trips: [
.init(trip: .init(trip: trip, prediction: prediction), now: now.toKotlinInstant()),
]))
NearbyStopRoutePatternView(headsign: "None", predictions: PatternsByHeadsign.FormatNone.shared)
NearbyStopRoutePatternView(headsign: "Loading", predictions: PatternsByHeadsign.FormatLoading.shared)
NearbyStopRoutePatternView(headsign: "No Service", predictions: PatternsByHeadsign.FormatNoService(alert:
ObjectCollectionBuilder.Single.shared.alert { alert in
alert.effect = .suspension
}
))
List {
HeadsignRowView(headsign: "Some", predictions: PatternsByHeadsign.FormatSome(trips: [
.init(trip: .init(trip: trip, prediction: prediction), now: now.toKotlinInstant()),
]))
HeadsignRowView(headsign: "None", predictions: PatternsByHeadsign.FormatNone.shared)
HeadsignRowView(headsign: "Loading", predictions: PatternsByHeadsign.FormatLoading.shared)
HeadsignRowView(headsign: "No Service", predictions: PatternsByHeadsign.FormatNoService(
alert: ObjectCollectionBuilder.Single.shared.alert { alert in
alert.effect = .suspension
}
))
}
}
}
}
4 changes: 2 additions & 2 deletions iosApp/iosApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ struct ContentView: View {
.navigationBarHidden(true)
.navigationDestination(for: SheetNavigationStackEntry.self) { entry in
switch entry {
case let .stopDetails(stop, filter):
case let .stopDetails(stop, _):
StopDetailsPage(
backend: backendProvider.backend,
socket: socketProvider.socket,
globalFetcher: globalFetcher,
viewportProvider: viewportProvider,
stop: stop, filter: filter
stop: stop, filter: $navigationStack.lastStopDetailsFilter
)
}
}
Expand Down
3 changes: 3 additions & 0 deletions iosApp/iosApp/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
},
"BRD" : {

},
"Clear Filter" : {

},
"Couldn't load nearby transit, connection was interrupted" : {

Expand Down
2 changes: 1 addition & 1 deletion iosApp/iosApp/Pages/NearbyTransit/NearbyStopView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct NearbyStopView: View {
patternsAtStop.stop,
.init(routeId: patternsAtStop.route.id, directionId: patternsByHeadsign.directionId())
)) {
NearbyStopRoutePatternView(
HeadsignRowView(
headsign: patternsByHeadsign.headsign,
predictions: patternsByHeadsign.format(now: now)
)
Expand Down
65 changes: 65 additions & 0 deletions iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredRouteView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// StopDetailsFilteredRouteView.swift
// iosApp
//
// Created by Horn, Melody on 2024-04-05.
// Copyright © 2024 MBTA. All rights reserved.
//

import Foundation
import shared
import SwiftUI

struct StopDetailsFilteredRouteView: View {
let patternsByStop: PatternsByStop
let now: Instant
@Binding var filter: StopDetailsFilter?

struct RowData {
let tripId: String
let headsign: String
let formatted: PatternsByHeadsign.Format

init?(trip: UpcomingTrip, route: Route, expectedDirection: Int32?, now: Instant) {
if trip.trip.directionId != expectedDirection {
return nil
}

tripId = trip.trip.id
headsign = trip.trip.headsign
formatted = PatternsByHeadsign(
route: route, headsign: headsign, patterns: [], upcomingTrips: [trip], alertsHere: nil
).format(now: now)

if !(formatted is PatternsByHeadsign.FormatSome) {
return nil
}
}
}

let rows: [RowData]

init(departures: StopDetailsDepartures, now: Instant, filter filterBinding: Binding<StopDetailsFilter?>) {
_filter = filterBinding
let filter = filterBinding.wrappedValue
let patternsByStop = departures.routes.first(where: { $0.route.id == filter?.routeId })!
self.patternsByStop = patternsByStop
self.now = now

let expectedDirection: Int32? = filter?.directionId
rows = patternsByStop.allUpcomingTrips().compactMap {
RowData(trip: $0, route: patternsByStop.route, expectedDirection: expectedDirection, now: now)
}
}

var body: some View {
Button(action: { filter = nil }, label: { Text("Clear Filter") })
List {
RoutePillSection(route: patternsByStop.route) {
ForEach(rows, id: \.tripId) { row in
HeadsignRowView(headsign: row.headsign, predictions: row.formatted)
}
}
}
}
}
8 changes: 4 additions & 4 deletions iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct StopDetailsPage: View {
@StateObject var scheduleFetcher: ScheduleFetcher
@StateObject var predictionsFetcher: PredictionsFetcher
var stop: Stop
var filter: StopDetailsFilter?
@Binding var filter: StopDetailsFilter?
@State var now = Date.now

let inspection = Inspection<Self>()
Expand All @@ -30,14 +30,14 @@ struct StopDetailsPage: View {
globalFetcher: GlobalFetcher,
viewportProvider: ViewportProvider,
stop: Stop,
filter: StopDetailsFilter?
filter: Binding<StopDetailsFilter?>
) {
self.globalFetcher = globalFetcher
self.viewportProvider = viewportProvider
_scheduleFetcher = StateObject(wrappedValue: ScheduleFetcher(backend: backend))
_predictionsFetcher = StateObject(wrappedValue: PredictionsFetcher(socket: socket))
self.stop = stop
self.filter = filter
_filter = filter
}

var body: some View {
Expand All @@ -56,7 +56,7 @@ struct StopDetailsPage: View {
schedules: scheduleFetcher.schedules,
predictions: predictionsFetcher.predictions,
filterAtTime: now.toKotlinInstant()
), now: now.toKotlinInstant())
), now: now.toKotlinInstant(), filter: $filter)
} else {
ProgressView()
}
Expand Down
13 changes: 9 additions & 4 deletions iosApp/iosApp/Pages/StopDetails/StopDetailsRouteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ import SwiftUI
struct StopDetailsRouteView: View {
let patternsByStop: PatternsByStop
let now: Instant
@Binding var filter: StopDetailsFilter?

var body: some View {
RoutePillSection(route: patternsByStop.route) {
ForEach(patternsByStop.patternsByHeadsign, id: \.headsign) { patternsByHeadsign in
NearbyStopRoutePatternView(
headsign: patternsByHeadsign.headsign,
predictions: patternsByHeadsign.format(now: now)
)
Button(action: {
filter = .init(routeId: patternsByHeadsign.route.id, directionId: patternsByHeadsign.directionId())
}, label: {
HeadsignRowView(
headsign: patternsByHeadsign.headsign,
predictions: patternsByHeadsign.format(now: now)
)
})
}
}
}
Expand Down
11 changes: 8 additions & 3 deletions iosApp/iosApp/Pages/StopDetails/StopDetailsRoutesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ import SwiftUI
struct StopDetailsRoutesView: View {
let departures: StopDetailsDepartures
let now: Instant
@Binding var filter: StopDetailsFilter?

var body: some View {
List(departures.routes, id: \.route.id) { patternsByStop in
StopDetailsRouteView(patternsByStop: patternsByStop, now: now)
if let filter {
StopDetailsFilteredRouteView(departures: departures, now: now, filter: $filter)
} else {
List(departures.routes, id: \.route.id) { patternsByStop in
StopDetailsRouteView(patternsByStop: patternsByStop, now: now, filter: $filter)
}
}
}
}
Expand Down Expand Up @@ -66,5 +71,5 @@ struct StopDetailsRoutesView: View {
upcomingTrips: [.init(trip: trip2, schedule: schedule2)],
alertsHere: nil),
]),
]), now: Date.now.toKotlinInstant())
]), now: Date.now.toKotlinInstant(), filter: .constant(nil))
}
24 changes: 24 additions & 0 deletions iosApp/iosApp/Utils/SheetNavigationStackEntry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,27 @@ struct StopDetailsFilter: Hashable {
enum SheetNavigationStackEntry: Hashable {
case stopDetails(Stop, StopDetailsFilter?)
}

extension [SheetNavigationStackEntry] {
/// Retrieves and updates the bottom-most ``StopDetailsFilter`` in the navigation stack.
///
/// Implemented as an extension property so that
/// [`Binding.subscript(dynamicMember:)`][binding-subscript]
/// can automatically create a binding to the filter based on the binding to the stack.
///
/// [binding-subscript]: https://developer.apple.com/documentation/swiftui/binding/subscript(dynamicmember:)
var lastStopDetailsFilter: StopDetailsFilter? {
get {
switch self.last {
case let .stopDetails(_, filter): filter
case _: nil
}
}
set {
if case let .stopDetails(stop, _) = self.last {
_ = self.popLast()
self.append(.stopDetails(stop, newValue))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// StopDetailsFilteredRouteViewTests.swift
// iosAppTests
//
// Created by Horn, Melody on 2024-04-09.
// Copyright © 2024 MBTA. All rights reserved.
//

@testable import iosApp
import shared
import SwiftUI
import ViewInspector
import XCTest

final class StopDetailsFilteredRouteViewTests: XCTestCase {
private func testData() -> (departures: StopDetailsDepartures, routeId: String, now: Instant) {
let objects = ObjectCollectionBuilder()
let route = objects.route()
let stop = objects.stop { _ in }

let patternNorth = objects.routePattern(route: route) { pattern in
pattern.directionId = 0
pattern.representativeTrip { $0.headsign = "North" }
}
let patternSouth = objects.routePattern(route: route) { pattern in
pattern.directionId = 1
pattern.representativeTrip { $0.headsign = "South" }
}

let now = Date.now

let tripNorth = objects.trip(routePattern: patternNorth)
let predictionNorth = objects.prediction {
$0.trip = tripNorth
$0.departureTime = now.toKotlinInstant()
}
let tripSouth = objects.trip(routePattern: patternSouth)
let predictionSouth = objects.prediction {
$0.trip = tripSouth
$0.departureTime = now.toKotlinInstant()
}

let patternsByStop = PatternsByStop(route: route, stop: stop, patternsByHeadsign: [
.init(route: route, headsign: "North", patterns: [patternNorth], upcomingTrips: [objects.upcomingTrip(prediction: predictionNorth)], alertsHere: nil),
.init(route: route, headsign: "South", patterns: [patternSouth], upcomingTrips: [objects.upcomingTrip(prediction: predictionSouth)], alertsHere: nil),
])

let departures = StopDetailsDepartures(routes: [patternsByStop])

return (departures: departures, routeId: route.id, now: now.toKotlinInstant())
}

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

let sut = StopDetailsFilteredRouteView(
departures: departures,
now: now,
filter: .constant(.init(routeId: routeId, directionId: 0))
)

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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// StopDetailsRouteViewTests.swift
// iosAppTests
//
// Created by Horn, Melody on 2024-04-09.
// Copyright © 2024 MBTA. All rights reserved.
//

@testable import iosApp
import shared
import SwiftUI
import ViewInspector
import XCTest

final class StopDetailsRouteViewTests: XCTestCase {
func testSetFilter() throws {
let objects = ObjectCollectionBuilder()
let route = objects.route()
let stop = objects.stop { _ in }

let now = Date.now.toKotlinInstant()

let filter = Binding<StopDetailsFilter?>(wrappedValue: nil)

let northPattern = objects.routePattern(route: route) { $0.directionId = 0 }
let southPattern = objects.routePattern(route: route) { $0.directionId = 1 }
let patternsByHeadsignNorth = PatternsByHeadsign(route: route, headsign: "North", patterns: [northPattern], upcomingTrips: nil, alertsHere: nil)
let patternsByHeadsignSouth = PatternsByHeadsign(route: route, headsign: "South", patterns: [southPattern], upcomingTrips: nil, alertsHere: nil)
let patternsByStop = PatternsByStop(route: route, stop: stop, patternsByHeadsign: [patternsByHeadsignNorth, patternsByHeadsignSouth])
let sut = StopDetailsRouteView(patternsByStop: patternsByStop, now: now, filter: filter)

XCTAssertNil(filter.wrappedValue)
try sut.inspect().find(button: "North").tap()
XCTAssertEqual(filter.wrappedValue, .init(routeId: route.id, directionId: 0))
try sut.inspect().find(button: "South").tap()
XCTAssertEqual(filter.wrappedValue, .init(routeId: route.id, directionId: 1))
}
}
Loading

0 comments on commit 810b802

Please sign in to comment.