docs: add sharing system overhaul design

Complete redesign of sharing to support:
- Trip summary cards with route maps
- 4 achievement card types (spotlight, collection, milestone, context)
- Stadium progress cards
- 8 user-customizable color themes
- Instagram Stories as primary target (1080×1920)
- Contextual share buttons throughout app

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-13 22:28:21 -06:00
parent 16514a40ac
commit c989b7b1d1

View File

@@ -0,0 +1,430 @@
# Sharing System Overhaul
*Design finalized: January 13, 2026*
## Overview
Complete overhaul of the sharing system to support shareable cards for trip summaries, achievements, and stadium progress. Primary target is Instagram Stories (1080×1920 vertical cards) with user-customizable color themes.
## Goals
- Share trip summaries as visual cards (not just deep links)
- Share achievements (single, collection, milestone, contextual)
- Share stadium progress with map visualization
- User-customizable color themes (8 presets)
- Contextual share buttons throughout the app (low friction)
- Instagram Stories as primary destination
## Architecture
### Protocol-Based Design
All shareable content types conform to a common protocol:
```swift
protocol ShareableContent {
var cardType: ShareCardType { get }
func render(theme: ShareTheme) async throws -> UIImage
}
enum ShareCardType {
case tripSummary
case achievementSpotlight
case achievementCollection
case achievementMilestone
case achievementContext
case stadiumProgress
}
```
### Component Hierarchy
```
ShareableContent (protocol)
├── TripShareContent → TripCardGenerator
├── AchievementShareContent → AchievementCardGenerator
├── ProgressShareContent → ProgressCardGenerator
└── CollectionShareContent → CollectionCardGenerator
```
### Unified Components
- `ShareTheme` - Color preset definitions
- `ShareCardFooter` - Reusable SportsTime branding footer
- `ShareCardHeader` - Sport icon + title treatment
- `ShareService` - Instagram URL scheme, image export, fallback share sheet
## Theme System
### 8 Color Presets
| Theme | Background Gradient | Accent | Text | Use Case |
|-------|---------------------|--------|------|----------|
| **Dark** | #1A1A2E#16213E | #FF6B35 | White | Default, premium feel |
| **Light** | #FFFFFF#F5F5F5 | #FF6B35 | #1A1A2E | Clean, minimal |
| **Midnight** | #0D1B2A#1B263B | #00D4FF | White | Night game energy |
| **Forest** | #1B4332#2D6A4F | #95D5B2 | White | Baseball/outdoor |
| **Sunset** | #FF6B35#F7931E | #FFFFFF | White | Bold, attention-grabbing |
| **Berry** | #4A0E4E#81267E | #FF85A1 | White | Unique, stands out |
| **Ocean** | #023E8A#0077B6 | #90E0EF | White | Cool, professional |
| **Slate** | #2B2D42#3D405B | #F4A261 | #EDF2F4 | Sophisticated neutral |
### Theme Definition
```swift
struct ShareTheme: Identifiable, Hashable {
let id: String
let name: String
let gradientColors: [Color]
let accentColor: Color
let textColor: Color
let secondaryTextColor: Color
let mapStyle: MapStyle // .light or .dark
static let all: [ShareTheme] = [.dark, .light, .midnight, .forest, .sunset, .berry, .ocean, .slate]
}
```
Theme preference is persisted per content type via UserDefaults.
## Card Designs
### Card Dimensions
All cards use Instagram Story dimensions: **1080 × 1920 pixels** (9:16 aspect ratio).
### Trip Summary Card
```
┌─────────────────────────────────┐
│ [Sport Icon] │ ← Header: 120px
│ "My Baseball Road Trip" │
├─────────────────────────────────┤
│ │
│ ┌─────────────────────┐ │
│ │ │ │
│ │ MAP SNAPSHOT │ │ ← Hero map: 600px
│ │ with route line │ │ Route drawn in accent color
│ │ + city markers │ │ Visited cities = filled dots
│ │ │ │
│ └─────────────────────┘ │
│ │
├─────────────────────────────────┤
│ "Jun 15 - Jun 22, 2026" │ ← Date range
├─────────────────────────────────┤
│ │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 1,847 │ │ 5 │ │ 4 │ │ ← Stats row
│ │ miles │ │ games │ │cities │ │
│ └───────┘ └───────┘ └───────┘ │
│ │
├─────────────────────────────────┤
│ Chicago → Milwaukee → Detroit │ ← City trail
│ → Cleveland → Pittsburgh │
├─────────────────────────────────┤
│ [SportsTime logo] │ ← Footer: 100px
│ "Plan your stadium adventure" │
└─────────────────────────────────┘
```
**Map Features:**
- Route line drawn using MKPolyline in accent color
- City markers: filled circles with abbreviation labels
- Map style matches theme (muted standard for dark themes)
### Achievement Cards
#### Type 1: Single Spotlight
```
┌─────────────────────────────────┐
│ │
│ ┌───────────┐ │
│ │ │ │
│ │ BADGE │ │ ← Large badge: 400×400px
│ │ GRAPHIC │ │
│ │ │ │
│ └───────────┘ │
│ │
│ "ALL NL WEST" │ ← Achievement name
│ │
│ "Visited all 5 stadiums │ ← Description
│ in the National League │
│ West division" │
│ │
│ ✓ Unlocked Jan 12, 2026 │ ← Unlock date
│ │
│ [SportsTime logo + tagline] │
└─────────────────────────────────┘
```
#### Type 2: Collection Grid
```
┌─────────────────────────────────┐
│ "My 2026 Achievements" │ ← Header with year
├─────────────────────────────────┤
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Badge│ │Badge│ │Badge│ │ ← 3×3 or 3×4 grid
│ │ #1 │ │ #2 │ │ #3 │ │
│ └─────┘ └─────┘ └─────┘ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Badge│ │Badge│ │Badge│ │
│ │ #4 │ │ #5 │ │ #6 │ │
│ └─────┘ └─────┘ └─────┘ │
├─────────────────────────────────┤
│ "12 achievements unlocked" │ ← Summary count
│ [SportsTime logo + tagline] │
└─────────────────────────────────┘
```
#### Type 3: Milestone Celebration
Same as Single Spotlight with:
- Animated particle/confetti burst pattern (static for image)
- Larger badge (500×500px)
- "MILESTONE" label above badge
- Gold accent elements regardless of theme
#### Type 4: Achievement + Context
```
┌─────────────────────────────────┐
│ ┌─────────┐ │
│ │ BADGE │ "Coast to Coast" │ ← Badge + name side by side
│ └─────────┘ Unlocked! │
├─────────────────────────────────┤
│ │
│ ┌─────────────────────┐ │
│ │ TRIP MAP or │ │ ← Context: trip/visit
│ │ STADIUM PHOTO │ │ that unlocked it
│ └─────────────────────┘ │
│ │
│ "Unlocked during my │
│ Summer 2026 Road Trip" │ ← Trip name or visit date
│ │
│ [SportsTime logo + tagline] │
└─────────────────────────────────┘
```
### Stadium Progress Card
```
┌─────────────────────────────────┐
│ [Sport Icon] │
│ "MLB Stadium Quest" │ ← Header
├─────────────────────────────────┤
│ │
│ ┌───────────┐ │
╲ │
│ │ 18 │ │ ← Progress ring: 320px
│ │ ──── │ │ Accent color fill
│ │ of 30 │ │
│ ╲
│ └───────────┘ │
│ │
│ 60% Complete │ ← Percentage
│ │
├─────────────────────────────────┤
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ 18 │ │ 12 │ │ 4 │ │ ← Stats row
│ │visited│ │remain │ │trips │ │
│ └───────┘ └───────┘ └───────┘ │
├─────────────────────────────────┤
│ ┌─────────────────────┐ │
│ │ │ │
│ │ US MAP SNAPSHOT │ │ ← Map: 500px height
│ │ ● visited (accent)│ │
│ │ ○ remaining (gray)│ │
│ │ │ │
│ └─────────────────────┘ │
├─────────────────────────────────┤
│ @username (optional) │ ← Optional username
│ [SportsTime logo + tagline] │
└─────────────────────────────────┘
```
## Share UI/UX
### Contextual Share Buttons
`ShareButton` component appears on:
- Trip cards in saved trips list
- Trip detail view toolbar
- Achievement badges (on tap)
- Progress ring on Progress tab
- Achievement list header (for collection)
```swift
struct ShareButton: View {
let content: ShareableContent
@State private var showPreview = false
var body: some View {
Button {
showPreview = true
} label: {
Image(systemName: "square.and.arrow.up")
}
.sheet(isPresented: $showPreview) {
SharePreviewView(content: content)
}
}
}
```
### SharePreviewView
```
┌─────────────────────────────────┐
│ ← Cancel Share → │ ← Nav bar
├─────────────────────────────────┤
│ │
│ ┌─────────────────────┐ │
│ │ │ │
│ │ LIVE PREVIEW │ │ ← 40% scale preview
│ │ of card │ │ Updates in real-time
│ │ │ │
│ └─────────────────────┘ │
│ │
├─────────────────────────────────┤
│ Theme │
│ [Dark] [Light] [Midnight] ... │ ← Horizontal scroll
├─────────────────────────────────┤
│ ☑ Include username │ ← Toggle (progress only)
│ [@yourhandle____________] │
├─────────────────────────────────┤
│ │
│ ┌─────────────────────────┐ │
│ │ Share to Instagram │ │ ← Primary CTA
│ └─────────────────────────┘ │
│ │
│ [Copy Image] [More Options] │ ← Secondary actions
│ │
└─────────────────────────────────┘
```
### Instagram Integration
```swift
func shareToInstagram(image: UIImage) {
guard let imageData = image.pngData() else { return }
// Write to shared container for Instagram
let pasteboardItems: [String: Any] = [
"com.instagram.sharedSticker.backgroundImage": imageData
]
UIPasteboard.general.setItems([pasteboardItems])
// Open Instagram Stories
if let url = URL(string: "instagram-stories://share?source_application=com.sportstime.app") {
UIApplication.shared.open(url)
}
}
```
Falls back to standard `UIActivityViewController` if Instagram not installed.
## File Structure
### Files to Delete
| File | Lines |
|------|-------|
| `SportsTime/Export/Services/ProgressCardGenerator.swift` | 607 |
### New Files
| File | Purpose | ~Lines |
|------|---------|--------|
| `Export/Sharing/ShareableContent.swift` | Protocol + ShareTheme | 120 |
| `Export/Sharing/ShareCardComponents.swift` | Header, footer, stats, map | 200 |
| `Export/Sharing/TripCardGenerator.swift` | Trip summary cards | 180 |
| `Export/Sharing/AchievementCardGenerator.swift` | All 4 achievement types | 350 |
| `Export/Sharing/ProgressCardGenerator.swift` | Stadium progress cards | 180 |
| `Export/Sharing/ShareService.swift` | Instagram, export, share sheet | 150 |
| `Export/Views/SharePreviewView.swift` | Unified preview UI | 250 |
| `Export/Views/ShareButton.swift` | Contextual button | 40 |
### Files to Modify
| File | Changes |
|------|---------|
| `TripDetailView.swift` | Replace `shareTrip()` with `ShareButton`, remove old `ShareSheet` |
| `ProgressTabView.swift` | Add `ShareButton` to progress rings |
| Achievement views | Add `ShareButton` to badges |
## Implementation Notes
### Map Snapshot Generation
Reuse pattern from existing code but extract to `ShareCardComponents`:
```swift
func generateRouteMapSnapshot(
stops: [TripStop],
theme: ShareTheme
) async -> UIImage? {
// Calculate bounding region
// Configure MKMapSnapshotter with theme-appropriate style
// Draw route polyline in accent color
// Draw city markers
}
func generateProgressMapSnapshot(
visited: [Stadium],
remaining: [Stadium],
theme: ShareTheme
) async -> UIImage? {
// Calculate US-wide region
// Draw visited markers in accent color
// Draw remaining markers in gray
}
```
### Image Rendering
Use SwiftUI `ImageRenderer` at 3x scale for high-res output:
```swift
func render(theme: ShareTheme) async throws -> UIImage {
let cardView = TripCardView(trip: trip, theme: theme)
let renderer = ImageRenderer(content: cardView)
renderer.scale = 3.0
guard let image = renderer.uiImage else {
throw ShareError.renderingFailed
}
return image
}
```
### Theme Persistence
```swift
@AppStorage("shareTheme.trip") private var tripThemeId: String = "dark"
@AppStorage("shareTheme.achievement") private var achievementThemeId: String = "dark"
@AppStorage("shareTheme.progress") private var progressThemeId: String = "dark"
```
## Scope Summary
| Category | Count |
|----------|-------|
| New lines | ~1,470 |
| Deleted lines | ~607 |
| Modified lines | ~50 |
| New files | 8 |
| Deleted files | 1 |
| Modified files | 3+ |
## Success Criteria
- [ ] All 3 content types shareable (trips, achievements, progress)
- [ ] All 8 theme presets working
- [ ] Instagram Stories direct share working
- [ ] Fallback to share sheet when Instagram not installed
- [ ] Share buttons appear contextually throughout app
- [ ] Theme preference persisted per content type
- [ ] Cards render at 1080×1920 with proper scaling