feat: add WCAG AA accessibility app-wide, fix CloudKit container config, remove debug logs

- Add VoiceOver labels, hints, and element grouping across all 60+ views
- Add Reduce Motion support (Theme.Animation.prefersReducedMotion) to all animations
- Replace fixed font sizes with semantic Dynamic Type styles
- Hide decorative elements from VoiceOver with .accessibilityHidden(true)
- Add .minimumHitTarget() modifier ensuring 44pt touch targets
- Add AccessibilityAnnouncer utility for VoiceOver announcements
- Improve color contrast values in Theme.swift for WCAG AA compliance
- Extract CloudKitContainerConfig for explicit container identity
- Remove PostHog debug console log from AnalyticsManager

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-11 09:27:23 -06:00
parent e9c15d70b1
commit d63d311cab
77 changed files with 982 additions and 263 deletions

View File

@@ -96,7 +96,7 @@ struct SettingsView: View {
Section {
ForEach(AppearanceMode.allCases) { mode in
Button {
withAnimation(.easeInOut(duration: 0.2)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
AppearanceManager.shared.currentMode = mode
AnalyticsManager.shared.track(.appearanceChanged(mode: mode.displayName))
}
@@ -109,8 +109,9 @@ struct SettingsView: View {
.frame(width: 32, height: 32)
Image(systemName: mode.iconName)
.font(.system(size: 16))
.font(.body)
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
}
VStack(alignment: .leading, spacing: 2) {
@@ -128,11 +129,13 @@ struct SettingsView: View {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.font(.title3)
.accessibilityHidden(true)
}
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityAddTraits(AppearanceManager.shared.currentMode == mode ? .isSelected : [])
}
} header: {
Text("Appearance")
@@ -148,7 +151,7 @@ struct SettingsView: View {
Section {
ForEach(AppTheme.allCases) { theme in
Button {
withAnimation(.easeInOut(duration: 0.2)) {
Theme.Animation.withMotion(.easeInOut(duration: 0.2)) {
viewModel.selectedTheme = theme
}
} label: {
@@ -181,11 +184,13 @@ struct SettingsView: View {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(Theme.warmOrange)
.font(.title3)
.accessibilityHidden(true)
}
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityAddTraits(viewModel.selectedTheme == theme ? .isSelected : [])
}
} header: {
Text("Theme")
@@ -218,6 +223,7 @@ struct SettingsView: View {
} icon: {
Image(systemName: "sparkles")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
}
}
} header: {
@@ -306,6 +312,7 @@ struct SettingsView: View {
} icon: {
Image(systemName: "chart.bar.xaxis")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
}
}
} header: {
@@ -328,15 +335,30 @@ struct SettingsView: View {
}
Link(destination: URL(string: "https://sportstime.88oakapps.com/privacy.html")!) {
Label("Privacy Policy", systemImage: "hand.raised")
Label {
Text("Privacy Policy")
} icon: {
Image(systemName: "hand.raised")
.accessibilityHidden(true)
}
}
Link(destination: URL(string: "https://sportstime.88oakapps.com/eula.html")!) {
Label("EULA", systemImage: "doc.text")
Label {
Text("EULA")
} icon: {
Image(systemName: "doc.text")
.accessibilityHidden(true)
}
}
Link(destination: URL(string: "mailto:support@88oakapps.com")!) {
Label("Contact Support", systemImage: "envelope")
Label {
Text("Contact Support")
} icon: {
Image(systemName: "envelope")
.accessibilityHidden(true)
}
}
} header: {
Text("About")
@@ -365,6 +387,7 @@ struct SettingsView: View {
} icon: {
Image(systemName: "photo.badge.plus")
.foregroundStyle(Theme.warmOrange)
.accessibilityHidden(true)
}
}
} header: {
@@ -380,7 +403,12 @@ struct SettingsView: View {
Button(role: .destructive) {
showResetConfirmation = true
} label: {
Label("Reset to Defaults", systemImage: "arrow.counterclockwise")
Label {
Text("Reset to Defaults")
} icon: {
Image(systemName: "arrow.counterclockwise")
.accessibilityHidden(true)
}
}
}
.listRowBackground(Theme.cardBackground(colorScheme))
@@ -682,6 +710,7 @@ struct SettingsView: View {
Image(systemName: "checkmark.circle.fill")
.foregroundStyle(.green)
.accessibilityHidden(true)
}
Button {
@@ -714,6 +743,7 @@ struct SettingsView: View {
Image(systemName: "chevron.right")
.foregroundStyle(Theme.textMuted(colorScheme))
.accessibilityHidden(true)
}
}
.buttonStyle(.plain)
@@ -741,6 +771,7 @@ struct SettingsView: View {
guard !isSyncActionInProgress else { return }
isSyncActionInProgress = true
AccessibilityAnnouncer.announce("Manual sync started.")
Task {
defer { isSyncActionInProgress = false }
@@ -748,8 +779,10 @@ struct SettingsView: View {
do {
let result = try await BackgroundSyncManager.shared.triggerManualSync()
syncActionMessage = "Sync complete. Updated \(result.totalUpdated) records."
AccessibilityAnnouncer.announce(syncActionMessage ?? "")
} catch {
syncActionMessage = "Sync failed: \(error.localizedDescription)"
AccessibilityAnnouncer.announce(syncActionMessage ?? "")
}
}
}
@@ -779,13 +812,13 @@ struct SyncStatusRow: View {
// Status indicator
Image(systemName: statusIcon)
.foregroundStyle(statusColor)
.font(.system(size: 14))
.font(.subheadline)
.frame(width: 20)
// Entity icon and name
Image(systemName: status.entityType.iconName)
.foregroundStyle(.secondary)
.font(.system(size: 14))
.font(.subheadline)
.frame(width: 20)
Text(status.entityType.rawValue)
@@ -812,6 +845,7 @@ struct SyncStatusRow: View {
.foregroundStyle(.blue)
}
.buttonStyle(.plain)
.accessibilityLabel("View sync details")
}
}