Files
Sportstime/SportsTime/Export/Sharing/ProgressCardGenerator.swift
Trey t 3d4952e5ff 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>
2026-01-14 12:02:57 -06:00

113 lines
3.2 KiB
Swift

//
// ProgressCardGenerator.swift
// SportsTime
//
// Generates shareable stadium progress cards.
//
import SwiftUI
import UIKit
// MARK: - Progress Share Content
struct ProgressShareContent: ShareableContent {
let progress: LeagueProgress
let tripCount: Int
var cardType: ShareCardType { .stadiumProgress }
@MainActor
func render(theme: ShareTheme) async throws -> UIImage {
let mapGenerator = ShareMapSnapshotGenerator()
let mapSnapshot = await mapGenerator.generateProgressMap(
visited: progress.stadiumsVisited,
remaining: progress.stadiumsRemaining,
theme: theme
)
let cardView = ProgressCardView(
progress: progress,
tripCount: tripCount,
theme: theme,
mapSnapshot: mapSnapshot
)
let renderer = ImageRenderer(content: cardView)
renderer.scale = 3.0
guard let image = renderer.uiImage else {
throw ShareError.renderingFailed
}
return image
}
}
// MARK: - Progress Card View
private struct ProgressCardView: View {
let progress: LeagueProgress
let tripCount: Int
let theme: ShareTheme
let mapSnapshot: UIImage?
var body: some View {
ZStack {
ShareCardBackground(theme: theme, sports: [progress.sport])
VStack(spacing: 40) {
ShareCardHeader(
title: "\(progress.sport.displayName) Stadium Quest",
sport: progress.sport,
theme: theme
)
Spacer()
// Progress ring
ShareProgressRing(
current: progress.visitedStadiums,
total: progress.totalStadiums,
theme: theme
)
Text("\(Int(progress.completionPercentage))% Complete")
.font(.system(size: 28, weight: .medium))
.foregroundStyle(theme.secondaryTextColor)
// Stats row
ShareStatsRow(
stats: [
(value: "\(progress.visitedStadiums)", label: "visited"),
(value: "\(progress.totalStadiums - progress.visitedStadiums)", label: "remain"),
(value: "\(tripCount)", label: "trips")
],
theme: theme
)
// Map
if let snapshot = mapSnapshot {
Image(uiImage: snapshot)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 960)
.clipShape(RoundedRectangle(cornerRadius: 20))
.overlay {
RoundedRectangle(cornerRadius: 20)
.stroke(theme.accentColor.opacity(0.3), lineWidth: 2)
}
}
Spacer()
ShareCardFooter(theme: theme)
}
.padding(ShareCardDimensions.padding)
}
.frame(
width: ShareCardDimensions.cardSize.width,
height: ShareCardDimensions.cardSize.height
)
}
}