From c989b7b1d11a1b02f49ec8b57f5636c47f59f4e6 Mon Sep 17 00:00:00 2001 From: Trey t Date: Tue, 13 Jan 2026 22:28:21 -0600 Subject: [PATCH] docs: add sharing system overhaul design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../2026-01-13-sharing-overhaul-design.md | 430 ++++++++++++++++++ 1 file changed, 430 insertions(+) create mode 100644 docs/plans/2026-01-13-sharing-overhaul-design.md diff --git a/docs/plans/2026-01-13-sharing-overhaul-design.md b/docs/plans/2026-01-13-sharing-overhaul-design.md new file mode 100644 index 0000000..a32f04e --- /dev/null +++ b/docs/plans/2026-01-13-sharing-overhaul-design.md @@ -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