feat(ui): add sport backgrounds to share cards, achievement filtering, and wizard validation

- Add ShareCardSportBackground with floating sport icons for share cards
- Share cards now show sport-specific backgrounds (single or multiple sports)
- Achievement collection share respects sport filter selection
- Add ability to share individual achievements from detail sheet
- Trip wizard ReviewStep highlights missing required fields in red
- Add FieldValidation model to TripWizardViewModel

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-14 12:02:57 -06:00
parent 1e26cfebc8
commit 3d4952e5ff
9 changed files with 255 additions and 23 deletions

View File

@@ -38,6 +38,8 @@ struct AchievementSpotlightContent: ShareableContent {
struct AchievementCollectionContent: ShareableContent {
let achievements: [AchievementProgress]
let year: Int
var sports: Set<Sport> = [] // Sports for background icons
var filterSport: Sport? = nil // The sport filter applied (for header title)
var cardType: ShareCardType { .achievementCollection }
@@ -46,6 +48,8 @@ struct AchievementCollectionContent: ShareableContent {
let cardView = AchievementCollectionView(
achievements: achievements,
year: year,
sports: sports,
filterSport: filterSport,
theme: theme
)
@@ -120,9 +124,16 @@ private struct AchievementSpotlightView: View {
let achievement: AchievementProgress
let theme: ShareTheme
private var sports: Set<Sport> {
if let sport = achievement.definition.sport {
return [sport]
}
return []
}
var body: some View {
ZStack {
ShareCardBackground(theme: theme)
ShareCardBackground(theme: theme, sports: sports)
VStack(spacing: 50) {
Spacer()
@@ -176,6 +187,8 @@ private struct AchievementSpotlightView: View {
private struct AchievementCollectionView: View {
let achievements: [AchievementProgress]
let year: Int
let sports: Set<Sport>
let filterSport: Sport?
let theme: ShareTheme
private let columns = [
@@ -184,13 +197,20 @@ private struct AchievementCollectionView: View {
GridItem(.flexible(), spacing: 30)
]
private var headerTitle: String {
if let sport = filterSport {
return "My \(String(year)) \(sport.rawValue) Achievements"
}
return "My \(String(year)) Achievements"
}
var body: some View {
ZStack {
ShareCardBackground(theme: theme)
ShareCardBackground(theme: theme, sports: sports)
VStack(spacing: 40) {
// Header
Text("My \(String(year)) Achievements")
Text(headerTitle)
.font(.system(size: 48, weight: .bold, design: .rounded))
.foregroundStyle(theme.textColor)
@@ -241,9 +261,16 @@ private struct AchievementMilestoneView: View {
private let goldColor = Color(hex: "FFD700")
private var sports: Set<Sport> {
if let sport = achievement.definition.sport {
return [sport]
}
return []
}
var body: some View {
ZStack {
ShareCardBackground(theme: theme)
ShareCardBackground(theme: theme, sports: sports)
// Confetti burst pattern
ConfettiBurst()
@@ -304,9 +331,16 @@ private struct AchievementContextView: View {
let mapSnapshot: UIImage?
let theme: ShareTheme
private var sports: Set<Sport> {
if let sport = achievement.definition.sport {
return [sport]
}
return []
}
var body: some View {
ZStack {
ShareCardBackground(theme: theme)
ShareCardBackground(theme: theme, sports: sports)
VStack(spacing: 40) {
// Header with badge and name