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:
@@ -19,6 +19,7 @@ struct ReviewStep: View {
|
||||
let mustStopLocations: [LocationInput]
|
||||
let isPlanning: Bool
|
||||
let canPlanTrip: Bool
|
||||
let fieldValidation: FieldValidation
|
||||
let onPlan: () -> Void
|
||||
|
||||
var body: some View {
|
||||
@@ -30,11 +31,31 @@ struct ReviewStep: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: Theme.Spacing.sm) {
|
||||
ReviewRow(label: "Mode", value: planningMode.displayName)
|
||||
ReviewRow(label: "Sports", value: selectedSports.map(\.rawValue).sorted().joined(separator: ", "))
|
||||
ReviewRow(label: "Dates", value: dateRangeText)
|
||||
ReviewRow(label: "Regions", value: selectedRegions.map(\.shortName).sorted().joined(separator: ", "))
|
||||
ReviewRow(label: "Route", value: routePreference.displayName)
|
||||
ReviewRow(label: "Repeat cities", value: allowRepeatCities ? "Yes" : "No")
|
||||
ReviewRow(
|
||||
label: "Sports",
|
||||
value: selectedSports.isEmpty ? "Not selected" : selectedSports.map(\.rawValue).sorted().joined(separator: ", "),
|
||||
isMissing: fieldValidation.sports == .missing
|
||||
)
|
||||
ReviewRow(
|
||||
label: "Dates",
|
||||
value: dateRangeText,
|
||||
isMissing: fieldValidation.dates == .missing
|
||||
)
|
||||
ReviewRow(
|
||||
label: "Regions",
|
||||
value: selectedRegions.isEmpty ? "Not selected" : selectedRegions.map(\.shortName).sorted().joined(separator: ", "),
|
||||
isMissing: fieldValidation.regions == .missing
|
||||
)
|
||||
ReviewRow(
|
||||
label: "Route",
|
||||
value: routePreference.displayName,
|
||||
isMissing: fieldValidation.routePreference == .missing
|
||||
)
|
||||
ReviewRow(
|
||||
label: "Repeat cities",
|
||||
value: allowRepeatCities ? "Yes" : "No",
|
||||
isMissing: fieldValidation.repeatCities == .missing
|
||||
)
|
||||
|
||||
if !mustStopLocations.isEmpty {
|
||||
ReviewRow(label: "Must-stops", value: mustStopLocations.map(\.name).joined(separator: ", "))
|
||||
@@ -83,26 +104,33 @@ private struct ReviewRow: View {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
let label: String
|
||||
let value: String
|
||||
var isMissing: Bool = false
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
Text(label)
|
||||
.font(.caption)
|
||||
.foregroundStyle(Theme.textMuted(colorScheme))
|
||||
.foregroundStyle(isMissing ? .red : Theme.textMuted(colorScheme))
|
||||
.frame(width: 80, alignment: .leading)
|
||||
|
||||
Text(value)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(Theme.textPrimary(colorScheme))
|
||||
.foregroundStyle(isMissing ? .red : Theme.textPrimary(colorScheme))
|
||||
|
||||
Spacer()
|
||||
|
||||
if isMissing {
|
||||
Image(systemName: "exclamationmark.circle.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
#Preview("All Valid") {
|
||||
ReviewStep(
|
||||
planningMode: .dateRange,
|
||||
selectedSports: [.mlb, .nba],
|
||||
@@ -114,6 +142,38 @@ private struct ReviewRow: View {
|
||||
mustStopLocations: [],
|
||||
isPlanning: false,
|
||||
canPlanTrip: true,
|
||||
fieldValidation: FieldValidation(
|
||||
sports: .valid,
|
||||
dates: .valid,
|
||||
regions: .valid,
|
||||
routePreference: .valid,
|
||||
repeatCities: .valid
|
||||
),
|
||||
onPlan: {}
|
||||
)
|
||||
.padding()
|
||||
.themedBackground()
|
||||
}
|
||||
|
||||
#Preview("Missing Fields") {
|
||||
ReviewStep(
|
||||
planningMode: .dateRange,
|
||||
selectedSports: [],
|
||||
startDate: Date(),
|
||||
endDate: Date().addingTimeInterval(86400 * 7),
|
||||
selectedRegions: [],
|
||||
routePreference: .balanced,
|
||||
allowRepeatCities: false,
|
||||
mustStopLocations: [],
|
||||
isPlanning: false,
|
||||
canPlanTrip: false,
|
||||
fieldValidation: FieldValidation(
|
||||
sports: .missing,
|
||||
dates: .valid,
|
||||
regions: .missing,
|
||||
routePreference: .valid,
|
||||
repeatCities: .missing
|
||||
),
|
||||
onPlan: {}
|
||||
)
|
||||
.padding()
|
||||
|
||||
Reference in New Issue
Block a user