Files
Sportstime/docs/plans/2026-01-13-sharing-overhaul-design.md
Trey t c989b7b1d1 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>
2026-01-13 22:28:21 -06:00

431 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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