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

feat(iOS): Combined stop details VoiceOver pass #639

Merged
merged 10 commits into from
Jan 13, 2025
2 changes: 1 addition & 1 deletion iosApp/iosApp/ComponentViews/LineCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct LineCard<Content: View>: View {
TransitCard(header: {
LineHeader(line: line, routes: routes) {
PinButton(pinned: pinned, action: { onPin(line.id) })
}
}.accessibilityAddTraits(.isButton)
}, content: content)
}
}
2 changes: 1 addition & 1 deletion iosApp/iosApp/ComponentViews/LineHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ struct LineHeader<Content: View>: View {
if let route = routes.first {
TransitHeader(
name: line.longName,
routeType: route.type,
backgroundColor: Color(hex: line.color),
textColor: Color(hex: line.textColor),
modeIcon: routeIcon(route),
rightContent: rightContent
)
.accessibilityElement(children: .combine)
Expand Down
2 changes: 1 addition & 1 deletion iosApp/iosApp/ComponentViews/RouteCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct RouteCard<Content: View>: View {
TransitCard(header: {
RouteHeader(route: route) {
PinButton(pinned: pinned, action: { onPin(route.id) })
}
}.accessibilityAddTraits(.isButton)
}, content: content)
}
}
2 changes: 1 addition & 1 deletion iosApp/iosApp/ComponentViews/RouteHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ struct RouteHeader<Content: View>: View {
var body: some View {
TransitHeader(
name: route.label,
routeType: route.type,
backgroundColor: route.uiColor,
textColor: route.uiTextColor,
modeIcon: routeIcon(route),
rightContent: rightContent
)
.accessibilityElement(children: .combine)
Expand Down
1 change: 0 additions & 1 deletion iosApp/iosApp/ComponentViews/RoutePill.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ struct RoutePill: View {
.modifier(ColorModifier(pill: self))
.modifier(ClipShapeModifier(spec: spec))
.accessibilityElement()
.accessibilityAddTraits(isActive ? [.isSelected] : [])
.accessibilityLabel(
"\(route?.label ?? line?.longName ?? "") \(route?.type.typeText(isOnly: true) ?? "")"
)
Expand Down
5 changes: 3 additions & 2 deletions iosApp/iosApp/ComponentViews/TransitHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import SwiftUI

struct TransitHeader<Content: View>: View {
let name: String
let routeType: RouteType
let backgroundColor: Color
let textColor: Color
let modeIcon: Image
var rightContent: () -> Content?

@ScaledMetric private var modeIconHeight: CGFloat = 24
Expand All @@ -27,10 +27,11 @@ struct TransitHeader<Content: View>: View {
.foregroundStyle(textColor)
.textCase(.none)
.frame(maxWidth: .infinity, maxHeight: modeIconHeight, alignment: .leading)
.accessibilityLabel(Text("\(name) \(routeType.typeText(isOnly: true))"))
rightContent()
.foregroundStyle(textColor)
} icon: {
modeIcon
routeIcon(routeType)
.resizable()
.aspectRatio(contentMode: .fit)
.scaledToFit()
Expand Down
61 changes: 58 additions & 3 deletions iosApp/iosApp/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@
}
},
"%@ %@ %@" : {
"comment" : "VoiceOver text for the vehicle status on the trip details page,\nex '[train] [approaching] [Alewife]' or '[bus] [now at] [Harvard]'\nPossible values for the vehicle status are \"Approaching\", \"Next stop\", or \"Now at\"",
"comment" : "Screen reader text for the vehicle status on the trip details page,\nex '[train] [approaching] [Alewife]' or '[bus] [now at] [Harvard]'\nPossible values for the vehicle status are \"Approaching\", \"Next stop\", or \"Now at\"\nVoiceOver text for the vehicle status on the trip details page,\nex '[train] [approaching] [Alewife]' or '[bus] [now at] [Harvard]'\nPossible values for the vehicle status are \"Approaching\", \"Next stop\", or \"Now at\"",
"localizations" : {
"en" : {
"stringUnit" : {
Expand Down Expand Up @@ -320,6 +320,17 @@
}
}
},
"%@ %@ %@, selected stop" : {
"comment" : "Screen reader text for the vehicle status on the trip details page when the stop is selected,\nex '[train] [approaching] [Alewife]' or '[bus] [now at] [Harvard], selected stop'\nPossible values for the vehicle status are \"Approaching\", \"Next stop\", or \"Now at\"",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$@ %2$@ %3$@, selected stop"
}
}
}
},
"%@ %@ at %@" : {
"comment" : "VoiceOver text for the stop details page header,\ndescribes the selected route and and stop, ex '[Red Line] [train] at [Porter]'",
"localizations" : {
Expand Down Expand Up @@ -860,7 +871,7 @@
}
},
"%@ scheduled to depart %@" : {
"comment" : "VoiceOver text for the departure status on the trip details page,\nex '[train] scheduled to depart [Alewife]' or '[bus] scheduled to depart [Harvard]'",
"comment" : "Screen reader text for the departure status on the trip details page,\nex '[train] scheduled to depart [Alewife]' or '[bus] scheduled to depart [Harvard]'",
"localizations" : {
"en" : {
"stringUnit" : {
Expand Down Expand Up @@ -906,6 +917,17 @@
}
}
},
"%@ scheduled to depart %@, selected stop" : {
"comment" : "Screen reader text for the departure status on the trip details page when the stop is selected,\nex '[train] scheduled to depart [Alewife]' or '[bus] scheduled to depart [Harvard], selected stop'",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "%1$@ scheduled to depart %2$@, selected stop"
}
}
}
},
"%@ to" : {
"comment" : "Label the direction a list of arrivals is for.\nPossible values include Northbound, Southbound, Inbound, Outbound, Eastbound, Westbound.\nFor example, \"[Northbound] to [Alewife]",
"localizations" : {
Expand Down Expand Up @@ -947,6 +969,21 @@
}
}
},
"%@, first stop" : {
"comment" : "Screen reader text for a stop name on the stop details page when that stop is the first stop on the line"
},
"%@, selected stop" : {
"comment" : "Screen reader text for a stop name on the stop details page when that stop is the selected one"
},
"%@, selected stop, first stop" : {
"comment" : "Screen reader text for a stop name on the stop details page when that stop is both selected and first"
},
"%1$@ has departed %2$@" : {
"comment" : "Screen reader text that is announced when a trip disappears from the screen.,\nin the format \"[train/bus/ferry] to [destination] has departed [stop name]\",\nex. \"[train] has departed [Central]\", \"[bus] has departed [Harvard]\""
},
"%1$@ to %2$@ has departed %3$@" : {
"comment" : "Screen reader text that is announced when a trip disappears from the screen.,\nin the format \"[train/bus/ferry] to [destination] has departed [stop name]\",\nex. \"[train] to [Alewife] has departed [Central]\", \"[bus] to [Nubian] has departed [Harvard]\""
},
"%ld stops away" : {
"comment" : "How many stops away the vehicle is from the target stop",
"localizations" : {
Expand Down Expand Up @@ -3146,6 +3183,12 @@
}
}
},
"displays more information" : {
"comment" : "Screen reader hint for tapping on the trip details header on the stop page"
},
"displays more information about this trip" : {
"comment" : "Screen reader hint for tapping a departure card in stop details"
},
"Dock Closure" : {
"comment" : "Possible alert effect",
"localizations" : {
Expand Down Expand Up @@ -4169,6 +4212,9 @@
}
}
},
"Hides remaining stops" : {
"comment" : "Screen reader hint explaining what happens when 'x stops away'\nis selected when it's already open (closes the accordion listing those stops)"
},
"High Winds" : {
"comment" : "Possible alert cause",
"localizations" : {
Expand Down Expand Up @@ -4544,6 +4590,9 @@
}
}
},
"Lists remaining stops" : {
"comment" : "Screen reader hint explaining what happens when 'x stops away'\nis selected (open an accordion listing those stops)"
},
"Live" : {
"comment" : "Indicates that data is being updated in real-time",
"localizations" : {
Expand Down Expand Up @@ -6908,8 +6957,11 @@
}
}
},
"Service is running, but predicted arrival times aren’t available." : {
"comment" : "Explanation under the 'Predictions unavailable' header in stop details."
},
"Service is running, but predicted arrival times aren’t available. The map shows where %@ on this route currently are." : {
"comment" : "Explanation under the 'Predictions unavailable' header in stop details.\nThe interpolated value can be \"buses\" or \"trains\".",
"comment" : "Explanation under the 'Predictions unavailable' header in stop details when maps are enabled.\nThe interpolated value can be \"buses\" or \"trains\".",
"localizations" : {
"es" : {
"stringUnit" : {
Expand Down Expand Up @@ -8181,6 +8233,9 @@
}
}
},
"switches direction" : {
"comment" : "Screen reader hint for the direction toggle action"
},
"Technical Problem" : {
"comment" : "Possible alert cause",
"localizations" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ struct StopDetailsFilterPills: View {
type: .flex,
isActive: filter == nil || filter?.routeId == route.id
)
.accessibilityAddTraits(filter?.routeId == route.id ? [.isSelected] : [])
.accessibilityAddTraits(.isButton)
.accessibilityHint(routePillHint)
.frame(minWidth: 44, minHeight: 44, alignment: .center)
.onTapGesture { tapRoutePill(filterBy) }
Expand All @@ -52,6 +54,8 @@ struct StopDetailsFilterPills: View {
type: .flex,
isActive: filter == nil || filter?.routeId == line.id
)
.accessibilityAddTraits(filter?.routeId == line.id ? [.isSelected] : [])
.accessibilityAddTraits(.isButton)
.accessibilityHint(routePillHint)
.frame(minWidth: 44, minHeight: 44, alignment: .center)
.onTapGesture { tapRoutePill(filterBy) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct SearchResultsContainer: View {

func handleStopTap(stopId: String) {
guard let stop = searchVM.getStopFor(id: stopId) else { return }
nearbyVM.navigationStack.append(.legacyStopDetails(stop, nil))
nearbyVM.pushNavEntry(.legacyStopDetails(stop, nil))
}

var body: some View {
Expand Down
6 changes: 6 additions & 0 deletions iosApp/iosApp/Pages/StopDetails/DepartureTile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ struct DepartureTile: View {
.clipShape(.rect(cornerRadius: 8))
.padding(1)
.overlay(RoundedRectangle(cornerRadius: 8).stroke(isSelected ? Color.halo : Color.clear, lineWidth: 2))
.accessibilityAddTraits(isSelected ? [.isHeader, .isSelected, .updatesFrequently] : [])
.accessibilityHeading(isSelected ? .h3 : .unspecified)
.accessibilityHint(isSelected ? "" : NSLocalizedString(
"displays more information about this trip",
comment: "Screen reader hint for tapping a departure card in stop details"
))
}

private func deselectedBackgroundColor(_ route: Route) -> Color {
Expand Down
11 changes: 10 additions & 1 deletion iosApp/iosApp/Pages/StopDetails/DirectionPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ struct DirectionPicker: View {
.padding(8)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
.accessibilityAddTraits(isSelected ? [.isSelected] : [])
.accessibilityAddTraits(isSelected ? [.isSelected, .isHeader] : [])
.accessibilityHeading(isSelected ? .h2 : .unspecified)
.accessibilitySortPriority(isSelected ? 1 : 0)
.accessibilityHint(isSelected ? "" : NSLocalizedString(
"switches direction",
comment: "Screen reader hint for the direction toggle action"
))
.background(isSelected ? route.uiColor : deselectedBackroundColor)
.foregroundStyle(isSelected ? route.uiTextColor : .deselectedToggleText)
.clipShape(.rect(cornerRadius: 6))
Expand All @@ -56,6 +62,9 @@ struct DirectionPicker: View {
.foregroundStyle(route.uiTextColor)
.padding(8)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.accessibilityElement(children: .combine)
.accessibilityAddTraits(.isHeader)
.accessibilityHeading(.h2)
}
}

Expand Down
11 changes: 9 additions & 2 deletions iosApp/iosApp/Pages/StopDetails/ExplainerPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ struct ExplainerPage: View {
VStack(alignment: .leading, spacing: 0) {
header
VStack(alignment: .leading, spacing: 24) {
explanationHeadline.font(Typography.title2Bold)
explanationHeadline
.font(Typography.title2Bold)
.accessibilityHeading(.h2)
.accessibilityAddTraits(.isHeader)
explanationImage
explanationText.font(Typography.body)
Spacer()
Expand All @@ -50,8 +53,12 @@ struct ExplainerPage: View {
.aspectRatio(contentMode: .fit)
.scaledToFit()
.frame(maxHeight: modeIconHeight, alignment: .topLeading)
.accessibilityHidden(true)

Text("Details", comment: "Header on the general explainer details page").font(Typography.headline)
Text("Details", comment: "Header on the general explainer details page")
.font(Typography.headline)
.accessibilityHeading(.h1)
.accessibilityAddTraits(.isHeader)
Spacer()
ActionButton(kind: .close) { onClose() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ struct StopDetailsFilteredDepartureDetails: View {
}
}

@AccessibilityFocusState private var selectedDepartureFocus: String?
private let cardFocusId = "_card"

var body: some View {
ZStack(alignment: .top) {
routeColor.ignoresSafeArea(.all)
Expand Down Expand Up @@ -89,8 +92,11 @@ struct StopDetailsFilteredDepartureDetails: View {
StopDetailsNoTripCard(
status: noPredictionsStatus,
accentColor: routeColor,
routeType: routeType
routeType: routeType,
hideMaps: stopDetailsVM.hideMaps
)
.accessibilityHeading(.h3)
.accessibilityFocused($selectedDepartureFocus, equals: cardFocusId)
} else if selectedTripIsCancelled {
StopDetailsIconCard(
accentColor: routeColor,
Expand All @@ -104,6 +110,8 @@ struct StopDetailsFilteredDepartureDetails: View {
),
icon: routeSlashIcon(routeType)
)
.accessibilityHeading(.h4)
.accessibilityFocused($selectedDepartureFocus, equals: cardFocusId)
} else {
TripDetailsView(
tripFilter: tripFilter,
Expand All @@ -121,6 +129,9 @@ struct StopDetailsFilteredDepartureDetails: View {
.onAppear { handleViewportForStatus(noPredictionsStatus) }
.onChange(of: noPredictionsStatus) { status in handleViewportForStatus(status) }
.onChange(of: selectedTripIsCancelled) { if $0 { setViewportToStop() } }
.onChange(of: tripFilter) { tripFilter in
selectedDepartureFocus = tiles.first { $0.upcoming?.trip.id == tripFilter?.tripId }?.id ?? cardFocusId
}
.ignoresSafeArea(.all)
}

Expand Down Expand Up @@ -170,7 +181,9 @@ struct StopDetailsFilteredDepartureDetails: View {
.line != nil ? .onPrediction(route: tileData.route) : .none,
showHeadsign: showTileHeadsigns,
isSelected: tileData.upcoming?.trip.id == tripFilter?.tripId
).padding(.horizontal, 4)
)
.accessibilityFocused($selectedDepartureFocus, equals: tileData.id)
.padding(.horizontal, 4)
}
}
.padding(.horizontal, 12)
Expand Down
16 changes: 4 additions & 12 deletions iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,20 @@ struct StopDetailsFilteredView: View {
func toggledPinnedRoute() {
Task {
if let routeId = patternsByStop?.routeIdentifier {
do {
let pinned = try await stopDetailsVM.togglePinnedUsecase.execute(route: routeId).boolValue
analytics.toggledPinnedRouteAtStop(pinned: pinned, routeId: routeId)
stopDetailsVM.loadPinnedRoutes()
} catch is CancellationError {
// do nothing on cancellation
} catch {
// execute shouldn't actually fail
debugPrint(error)
}
let pinned = await stopDetailsVM.togglePinnedRoute(routeId)
analytics.toggledPinnedRouteAtStop(pinned: pinned, routeId: routeId)
stopDetailsVM.loadPinnedRoutes()
}
}
}

var body: some View {
VStack(spacing: 0) {
ZStack {
Color.fill2
Color.fill2.ignoresSafeArea(.all)
header
}
.fixedSize(horizontal: false, vertical: true)
.ignoresSafeArea(.all)

if let patternsByStop {
StopDetailsFilteredDepartureDetails(
Expand Down
2 changes: 2 additions & 0 deletions iosApp/iosApp/Pages/StopDetails/StopDetailsIconCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ struct StopDetailsIconCard<Header: View, Details: View>: View {
.frame(width: 35, height: 35)
.frame(width: 48, height: 48)
.foregroundStyle(accentColor)
.accessibilityHidden(true)
header
.font(Typography.title2Bold)
.foregroundStyle(Color.text)
.accessibilityAddTraits(.isHeader)
}.frame(maxWidth: .infinity, alignment: .leading)

if let details {
Expand Down
Loading
Loading