fix: 14 audit fixes — concurrency, memory, performance, accessibility
Second audit round addressing data races, task stacking, unbounded caches, and VoiceOver gaps across 7 files. Concurrency: - Move NSItemProvider @State access into MainActor block (3 drop handlers) - Cancel stale ScheduleViewModel tasks on rapid filter changes Memory: - Replace unbounded image dict with LRUCache(countLimit: 50) - Replace demo-mode asyncAfter with cancellable Task Performance: - Wrap debug NBA print() in #if DEBUG - Cache visitsById via @State + onChange instead of per-render computed - Pre-compute sportProgressFractions in ProgressViewModel - Replace allGames computed property with hasGames bool check - Cache sortedTrips via @State + onChange in SavedTripsListView Accessibility: - Add combined VoiceOver label to progress ring - Combine away/home team text into single readable phrase - Hide decorative StadiumDetailSheet icon from VoiceOver - Add explicit accessibilityLabel to SportFilterChip - Add combined accessibilityLabel to GameRowView Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,17 +11,17 @@ struct ScheduleListView: View {
|
||||
@State private var showDatePicker = false
|
||||
@State private var showDiagnostics = false
|
||||
|
||||
private var allGames: [RichGame] {
|
||||
viewModel.gamesBySport.flatMap(\.games)
|
||||
private var hasGames: Bool {
|
||||
!viewModel.gamesBySport.isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if viewModel.isLoading && allGames.isEmpty {
|
||||
if viewModel.isLoading && !hasGames {
|
||||
loadingView
|
||||
} else if let errorMessage = viewModel.errorMessage {
|
||||
errorView(message: errorMessage)
|
||||
} else if allGames.isEmpty {
|
||||
} else if !hasGames {
|
||||
emptyView
|
||||
} else {
|
||||
gamesList
|
||||
@@ -224,6 +224,7 @@ struct SportFilterChip: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityIdentifier("schedule.sport.\(sport.rawValue.lowercased())")
|
||||
.accessibilityLabel(sport.rawValue)
|
||||
.accessibilityValue(isSelected ? "Selected" : "Not selected")
|
||||
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||
}
|
||||
@@ -313,6 +314,18 @@ struct GameRowView: View {
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
.listRowBackground(isToday ? Color.orange.opacity(0.1) : nil)
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel(gameAccessibilityLabel)
|
||||
}
|
||||
|
||||
private var gameAccessibilityLabel: String {
|
||||
var parts = ["\(game.awayTeam.name) at \(game.homeTeam.name)"]
|
||||
parts.append(game.stadium.name)
|
||||
parts.append(game.localGameTimeShort)
|
||||
if showDate {
|
||||
parts.append(Self.dateFormatter.string(from: game.game.dateTime))
|
||||
}
|
||||
return parts.joined(separator: ", ")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user