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:
@@ -124,23 +124,23 @@ struct TripDetailView: View {
|
||||
.animation(.easeInOut(duration: 0.3), value: exportProgress?.percentComplete)
|
||||
|
||||
Image(systemName: "doc.fill")
|
||||
.font(.system(size: 24))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
|
||||
VStack(spacing: Theme.Spacing.xs) {
|
||||
Text("Creating PDF")
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .semibold))
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text(exportProgress?.currentStep ?? "Preparing...")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
if let progress = exportProgress {
|
||||
Text("\(Int(progress.percentComplete * 100))%")
|
||||
.font(.system(size: Theme.FontSize.micro, weight: .medium, design: .monospaced))
|
||||
.font(.caption.monospaced())
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -177,7 +177,7 @@ struct TripDetailView: View {
|
||||
toggleSaved()
|
||||
} label: {
|
||||
Image(systemName: isSaved ? "heart.fill" : "heart")
|
||||
.font(.system(size: 22, weight: .medium))
|
||||
.font(.title3)
|
||||
.foregroundStyle(isSaved ? .red : .white)
|
||||
.padding(12)
|
||||
.background(.ultraThinMaterial)
|
||||
@@ -214,7 +214,7 @@ struct TripDetailView: View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
// Date range
|
||||
Text(trip.formattedDateRange)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
|
||||
// Route preview
|
||||
@@ -226,9 +226,9 @@ struct TripDetailView: View {
|
||||
ForEach(Array(trip.uniqueSports), id: \.self) { sport in
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: sport.iconName)
|
||||
.font(.system(size: 10))
|
||||
.font(.caption2)
|
||||
Text(sport.rawValue)
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.font(.caption2)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 5)
|
||||
@@ -261,11 +261,11 @@ struct TripDetailView: View {
|
||||
VStack(spacing: Theme.Spacing.md) {
|
||||
HStack {
|
||||
Text("Trip Score")
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .semibold))
|
||||
.font(.headline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Spacer()
|
||||
Text(score.scoreGrade)
|
||||
.font(.system(size: 32, weight: .bold, design: .rounded))
|
||||
.font(.largeTitle)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
.glowEffect(color: Theme.warmOrange, radius: 8)
|
||||
}
|
||||
@@ -283,10 +283,10 @@ struct TripDetailView: View {
|
||||
private func scoreItem(label: String, value: Double, color: Color) -> some View {
|
||||
VStack(spacing: 4) {
|
||||
Text(String(format: "%.0f", value))
|
||||
.font(.system(size: Theme.FontSize.cardTitle, weight: .bold))
|
||||
.font(.headline)
|
||||
.foregroundStyle(color)
|
||||
Text(label)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ struct TripDetailView: View {
|
||||
private var itinerarySection: some View {
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
|
||||
Text("Itinerary")
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
ForEach(Array(itinerarySections.enumerated()), id: \.offset) { index, section in
|
||||
@@ -604,11 +604,11 @@ struct DaySection: View {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Day \(dayNumber)")
|
||||
.font(.system(size: Theme.FontSize.sectionTitle, weight: .bold, design: .rounded))
|
||||
.font(.title2)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Text(formattedDate)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ struct DaySection: View {
|
||||
// City label
|
||||
if let city = gameCity {
|
||||
Label(city, systemImage: "mappin")
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -656,20 +656,20 @@ struct GameRow: View {
|
||||
// Sport icon and name
|
||||
HStack(spacing: 3) {
|
||||
Image(systemName: game.game.sport.iconName)
|
||||
.font(.system(size: 10))
|
||||
.font(.caption2)
|
||||
Text(game.game.sport.rawValue)
|
||||
.font(.system(size: 10, weight: .medium))
|
||||
.font(.caption2)
|
||||
}
|
||||
.foregroundStyle(game.game.sport.themeColor)
|
||||
|
||||
// Matchup
|
||||
HStack(spacing: 4) {
|
||||
Text(game.awayTeam.abbreviation)
|
||||
.font(.system(size: Theme.FontSize.body, weight: .bold))
|
||||
.font(.body)
|
||||
Text("@")
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
Text(game.homeTeam.abbreviation)
|
||||
.font(.system(size: Theme.FontSize.body, weight: .bold))
|
||||
.font(.body)
|
||||
}
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
@@ -677,9 +677,9 @@ struct GameRow: View {
|
||||
// Stadium
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "building.2")
|
||||
.font(.system(size: 10))
|
||||
.font(.caption2)
|
||||
Text(game.stadium.name)
|
||||
.font(.system(size: Theme.FontSize.caption))
|
||||
.font(.subheadline)
|
||||
}
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
@@ -688,7 +688,7 @@ struct GameRow: View {
|
||||
|
||||
// Time
|
||||
Text(game.game.gameTime)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .semibold))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
.padding(Theme.Spacing.sm)
|
||||
@@ -731,11 +731,11 @@ struct TravelSection: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Travel")
|
||||
.font(.system(size: Theme.FontSize.micro, weight: .semibold))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
Text("\(segment.fromLocation.name) → \(segment.toLocation.name)")
|
||||
.font(.system(size: Theme.FontSize.body, weight: .medium))
|
||||
.font(.body)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -743,10 +743,10 @@ struct TravelSection: View {
|
||||
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
Text(segment.formattedDistance)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .semibold))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
Text(segment.formattedDuration)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -765,16 +765,16 @@ struct TravelSection: View {
|
||||
HStack(spacing: Theme.Spacing.sm) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.foregroundStyle(.green)
|
||||
.font(.system(size: 12))
|
||||
.font(.caption)
|
||||
|
||||
Text("\(segment.evChargingStops.count) EV Charger\(segment.evChargingStops.count > 1 ? "s" : "") Along Route")
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: showEVChargers ? "chevron.up" : "chevron.down")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
}
|
||||
.padding(.horizontal, Theme.Spacing.md)
|
||||
@@ -834,7 +834,7 @@ struct EVChargerRow: View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack(spacing: Theme.Spacing.xs) {
|
||||
Text(charger.name)
|
||||
.font(.system(size: Theme.FontSize.caption, weight: .medium))
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.lineLimit(1)
|
||||
|
||||
@@ -844,7 +844,7 @@ struct EVChargerRow: View {
|
||||
HStack(spacing: Theme.Spacing.xs) {
|
||||
if let address = charger.location.address {
|
||||
Text(address)
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
|
||||
@@ -852,7 +852,7 @@ struct EVChargerRow: View {
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
|
||||
Text("~\(charger.formattedChargeTime) charge")
|
||||
.font(.system(size: Theme.FontSize.micro))
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textSecondary(colorScheme))
|
||||
}
|
||||
}
|
||||
@@ -866,7 +866,7 @@ struct EVChargerRow: View {
|
||||
private var chargerTypeBadge: some View {
|
||||
let (text, color) = chargerTypeInfo
|
||||
Text(text)
|
||||
.font(.system(size: 9, weight: .semibold))
|
||||
.font(.caption2)
|
||||
.foregroundStyle(color)
|
||||
.padding(.horizontal, 6)
|
||||
.padding(.vertical, 2)
|
||||
|
||||
Reference in New Issue
Block a user