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,11 +38,27 @@ struct AchievementsListView: View {
.navigationTitle("Achievements")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
if !earnedAchievements.isEmpty {
// Share only achievements matching current filter
let achievementsToShare = selectedSport != nil
? filteredAchievements.filter { $0.isEarned }
: earnedAchievements
// Collect sports for background: single sport if filtered, all sports from achievements if "All"
let sportsForBackground: Set<Sport> = {
if let sport = selectedSport {
return [sport]
}
// Extract all unique sports from earned achievements
return Set(achievementsToShare.compactMap { $0.definition.sport })
}()
if !achievementsToShare.isEmpty {
ShareButton(
content: AchievementCollectionContent(
achievements: earnedAchievements,
year: Calendar.current.component(.year, from: Date())
achievements: achievementsToShare,
year: Calendar.current.component(.year, from: Date()),
sports: sportsForBackground,
filterSport: selectedSport
),
style: .icon
)
@@ -573,6 +589,16 @@ struct AchievementDetailSheet: View {
ToolbarItem(placement: .cancellationAction) {
Button("Done") { dismiss() }
}
if achievement.isEarned {
ToolbarItem(placement: .primaryAction) {
ShareButton(
content: AchievementSpotlightContent(achievement: achievement),
style: .icon
)
.foregroundStyle(completedGold)
}
}
}
}
}