feat: implement Dynamic Type with Apple text styles
Replace all custom Theme.FontSize values and hardcoded font sizes with Apple's built-in text styles (.largeTitle, .title2, .headline, .body, .subheadline, .caption, .caption2) to support accessibility scaling. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -178,21 +178,21 @@ struct ProgressTabView: View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text("\(progress.visitedStadiums)")
|
||||
.font(.system(size: 24, weight: .bold, design: .rounded))
|
||||
.font(.title3)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Text("/\(progress.totalStadiums)")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.xs) {
|
||||
Text(viewModel.selectedSport.displayName)
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .bold, design: .rounded))
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text("Stadium Quest")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
if progress.isComplete {
|
||||
@@ -200,11 +200,11 @@ struct ProgressTabView: View {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
Text("Complete!")
|
||||
}
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .semibold))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
} else {
|
||||
Text("\(progress.totalStadiums - progress.visitedStadiums) stadiums remaining")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -254,7 +254,7 @@ struct ProgressTabView: View {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundStyle(.green)
|
||||
Text("Visited (\(viewModel.visitedStadiums.count))")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ struct ProgressTabView: View {
|
||||
Image(systemName: "circle.dotted")
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
Text("Not Yet Visited (\(viewModel.unvisitedStadiums.count))")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ struct ProgressTabView: View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
HStack {
|
||||
Text("Achievements")
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
@@ -319,7 +319,7 @@ struct ProgressTabView: View {
|
||||
Text("View All")
|
||||
Image(systemName: "chevron.right")
|
||||
}
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
}
|
||||
@@ -341,11 +341,11 @@ struct ProgressTabView: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Track Your Progress")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text("Earn badges for stadium visits")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ struct ProgressTabView: View {
|
||||
private var recentVisitsSection: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
Text("Recent Visits")
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
ForEach(viewModel.recentVisits) { visitSummary in
|
||||
@@ -422,7 +422,7 @@ struct LeagueSelectorButton: View {
|
||||
}
|
||||
|
||||
Text(sport.rawValue)
|
||||
.font(.system(size: Theme.FontSize.micro, weight: isSelected ? .bold : .medium))
|
||||
.font(.caption)
|
||||
.foregroundStyle(isSelected ? Theme.textPrimary(colorScheme) : Theme.textMuted(colorScheme))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -451,14 +451,14 @@ struct ProgressStatPill: View {
|
||||
VStack(spacing: 4) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
Text(value)
|
||||
.font(.system(size: Theme.FontSize.body, weight: .bold))
|
||||
.font(.body)
|
||||
}
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text(label)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -483,12 +483,12 @@ struct StadiumChip: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(stadium.name)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.lineLimit(1)
|
||||
|
||||
Text(stadium.city)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -524,7 +524,7 @@ struct RecentVisitRow: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(visit.stadium.name)
|
||||
.font(.system(size: Theme.FontSize.body, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
@@ -534,7 +534,7 @@ struct RecentVisitRow: View {
|
||||
Text(matchup)
|
||||
}
|
||||
}
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -545,7 +545,7 @@ struct RecentVisitRow: View {
|
||||
Image(systemName: "photo")
|
||||
Text("\(visit.photoCount)")
|
||||
}
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
|
||||
@@ -585,17 +585,17 @@ struct StadiumDetailSheet: View {
|
||||
.frame(width: 80, height: 80)
|
||||
|
||||
Image(systemName: visitStatus.isVisited ? "checkmark.seal.fill" : sport.iconName)
|
||||
.font(.system(size: 36))
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(visitStatus.isVisited ? .green : sport.themeColor)
|
||||
}
|
||||
|
||||
Text(stadium.name)
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .bold, design: .rounded))
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text(stadium.fullAddress)
|
||||
.font(.system(size: Theme.FontSize.body))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
if visitStatus.isVisited {
|
||||
@@ -604,7 +604,7 @@ struct StadiumDetailSheet: View {
|
||||
.foregroundStyle(.green)
|
||||
Text("Visited \(visitStatus.visitCount) time\(visitStatus.visitCount == 1 ? "" : "s")")
|
||||
}
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
}
|
||||
@@ -613,23 +613,23 @@ struct StadiumDetailSheet: View {
|
||||
if case .visited(let visits) = visitStatus {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
Text("Visit History")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .semibold))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
ForEach(visits.sorted(by: { $0.visitDate > $1.visitDate })) { visit in
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(visit.shortDateDescription)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
if let matchup = visit.matchup {
|
||||
Text(matchup)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
Text(visit.visitType.displayName)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
.padding(Theme.Spacing.sm)
|
||||
|
||||
Reference in New Issue
Block a user