Loading System Redesign
Overview
A complete overhaul of all loading views, progress indicators, and spinners to achieve visual consistency, a fresh premium aesthetic, and Apple-level polish.
Design Decisions
| Decision |
Choice |
| Visual direction |
Minimal + premium (Apple-level polish, no gimmicks) |
| Animation style |
Apple-style indeterminate (smooth arcs, gentle opacity) |
| Loading text |
Context-aware only (static labels, no clever messages) |
| Colors |
Theme-aware using existing Theme.warmOrange |
| Skeleton animation |
Gentle opacity pulse (no directional shimmer) |
Component Hierarchy
| Tier |
Component |
Use Case |
| Inline |
LoadingSpinner |
Buttons, small areas, inline waits |
| Content |
LoadingPlaceholder |
Skeleton shapes replacing content |
| Blocking |
LoadingSheet |
Full-screen modal operations |
Animation Principles
- 1-second cycle duration (spinners), 1.2-second (skeletons)
- Ease-in-out timing curves
- Opacity range: 0.3 → 0.6 (subtle, never harsh)
- No bouncing, no scaling, no playfulness
- All shapes in a skeleton group pulse together (synchronized)
Text Rules
- Static, context-specific labels only
- Format: "Loading [noun]..." (e.g., "Loading games...", "Loading schedule...")
- No rotating messages, no AI-generated text
- Text optional for spinners under 2 seconds
Component: LoadingSpinner
Replaces ThemedSpinner, ThemedSpinnerCompact, and all native ProgressView() usages.
API
Sizes
| Size |
Diameter |
Stroke |
Use Case |
.small |
16pt |
2pt |
Inline with text, buttons |
.medium |
24pt |
3pt |
Default, standalone |
.large |
40pt |
4pt |
Prominent loading states |
Visual Design
- Single arc (270°) rotating smoothly
- Background track at 15% opacity of accent color
- Foreground arc at 100% accent color (
Theme.warmOrange)
- 1-second full rotation, linear timing
- Round line caps
Optional Label
- Appears to the right of spinner (horizontal layout)
- Uses
Theme.textSecondary
- Font:
.subheadline for small/medium, .body for large
Replacements
| File |
Line |
New Code |
SportsStep.swift |
32 |
LoadingSpinner(size: .medium) |
ReviewStep.swift |
50 |
LoadingSpinner(size: .medium) |
StadiumVisitHistoryView.swift |
17 |
LoadingSpinner(size: .medium) |
GamesHistoryView.swift |
17 |
LoadingSpinner(size: .medium, label: "Loading games...") |
StadiumVisitSheet.swift |
235 |
LoadingSpinner(size: .small) |
Component: LoadingPlaceholder
Replaces PlaceholderCard and shimmer rectangles in LoadingTripsView.
API
Animation
- Gentle opacity pulse: 0.3 → 0.5 → 0.3
- 1.2-second cycle, ease-in-out
- All shapes in a group pulse together (synchronized)
- No directional movement, no shimmer sweep
Colors
- Light mode:
Theme.textMuted.opacity(0.2) base
- Dark mode:
Theme.cardBackgroundElevated base
- Pulse brightens by ~0.15 opacity at peak
Card Skeleton Layout
Component: LoadingSheet
Replaces LoadingOverlay and PlanningProgressView.
API
Visual Design
Specifications
- Background:
Color.black.opacity(0.5)
- Card:
Theme.cardBackground with Theme.CornerRadius.large
- Spinner:
LoadingSpinner(size: .large) centered
- Label:
.headline weight, Theme.textPrimary
- Detail (optional):
.subheadline, Theme.textSecondary
- Card padding:
Theme.Spacing.xl all sides
- Shadow:
radius: 16, y: 8
Static Labels by Context
| Context |
Label |
| Trip planning |
"Planning trip" |
| PDF export |
"Exporting PDF" |
| Data sync |
"Syncing data" |
| Generic |
"Loading" |
Migration Plan
Components to Deprecate
| Current |
Action |
Replacement |
ThemedSpinner |
Remove |
LoadingSpinner |
ThemedSpinnerCompact |
Remove |
LoadingSpinner(size: .small) |
LoadingDots |
Remove |
Not replaced |
PlaceholderCard |
Remove |
LoadingPlaceholder.card |
PlanningProgressView |
Remove |
LoadingSheet |
LoadingOverlay |
Remove |
LoadingSheet |
LoadingTripsView |
Refactor |
Use new skeletons |
LoadingTextGenerator |
Remove |
Static labels only |
Components to Keep
| Component |
Reason |
AnimatedRouteGraphic |
Used elsewhere, not a loading state |
PulsingDot |
Used in maps, not loading |
RoutePreviewStrip |
Display component, not loading |
StatPill |
Display component, not loading |
EmptyStateView |
Empty state, not loading |
New File Structure
Summary
- 3 new files created
- 6 components deprecated/removed
- 5 native
ProgressView() usages replaced
LoadingTextGenerator.swift deleted
LoadingTripsView.swift refactored to use new components