feat(sharing): implement unified sharing system for social media
Replace old ProgressCardGenerator with protocol-based sharing architecture supporting trips, achievements, and stadium progress. Features 8 color themes, Instagram Stories optimization (1080x1920), and reusable card components with map snapshots. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
157
SportsTime/Export/Sharing/ShareableContent.swift
Normal file
157
SportsTime/Export/Sharing/ShareableContent.swift
Normal file
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// ShareableContent.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Protocol for shareable content and theme definitions.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
// MARK: - Shareable Content Protocol
|
||||
|
||||
protocol ShareableContent {
|
||||
var cardType: ShareCardType { get }
|
||||
func render(theme: ShareTheme) async throws -> UIImage
|
||||
}
|
||||
|
||||
// MARK: - Card Types
|
||||
|
||||
enum ShareCardType: String, CaseIterable {
|
||||
case tripSummary
|
||||
case achievementSpotlight
|
||||
case achievementCollection
|
||||
case achievementMilestone
|
||||
case achievementContext
|
||||
case stadiumProgress
|
||||
}
|
||||
|
||||
// MARK: - Share Theme
|
||||
|
||||
struct ShareTheme: Identifiable, Hashable {
|
||||
let id: String
|
||||
let name: String
|
||||
let gradientColors: [Color]
|
||||
let accentColor: Color
|
||||
let textColor: Color
|
||||
let secondaryTextColor: Color
|
||||
let useDarkMap: Bool
|
||||
|
||||
// MARK: - Preset Themes
|
||||
|
||||
static let dark = ShareTheme(
|
||||
id: "dark",
|
||||
name: "Dark",
|
||||
gradientColors: [Color(hex: "1A1A2E"), Color(hex: "16213E")],
|
||||
accentColor: Color(hex: "FF6B35"),
|
||||
textColor: .white,
|
||||
secondaryTextColor: Color(hex: "B8B8D1"),
|
||||
useDarkMap: true
|
||||
)
|
||||
|
||||
static let light = ShareTheme(
|
||||
id: "light",
|
||||
name: "Light",
|
||||
gradientColors: [.white, Color(hex: "F5F5F5")],
|
||||
accentColor: Color(hex: "FF6B35"),
|
||||
textColor: Color(hex: "1A1A2E"),
|
||||
secondaryTextColor: Color(hex: "666666"),
|
||||
useDarkMap: false
|
||||
)
|
||||
|
||||
static let midnight = ShareTheme(
|
||||
id: "midnight",
|
||||
name: "Midnight",
|
||||
gradientColors: [Color(hex: "0D1B2A"), Color(hex: "1B263B")],
|
||||
accentColor: Color(hex: "00D4FF"),
|
||||
textColor: .white,
|
||||
secondaryTextColor: Color(hex: "A0AEC0"),
|
||||
useDarkMap: true
|
||||
)
|
||||
|
||||
static let forest = ShareTheme(
|
||||
id: "forest",
|
||||
name: "Forest",
|
||||
gradientColors: [Color(hex: "1B4332"), Color(hex: "2D6A4F")],
|
||||
accentColor: Color(hex: "95D5B2"),
|
||||
textColor: .white,
|
||||
secondaryTextColor: Color(hex: "B7E4C7"),
|
||||
useDarkMap: false
|
||||
)
|
||||
|
||||
static let sunset = ShareTheme(
|
||||
id: "sunset",
|
||||
name: "Sunset",
|
||||
gradientColors: [Color(hex: "FF6B35"), Color(hex: "F7931E")],
|
||||
accentColor: .white,
|
||||
textColor: .white,
|
||||
secondaryTextColor: Color(hex: "FFE5D9"),
|
||||
useDarkMap: false
|
||||
)
|
||||
|
||||
static let berry = ShareTheme(
|
||||
id: "berry",
|
||||
name: "Berry",
|
||||
gradientColors: [Color(hex: "4A0E4E"), Color(hex: "81267E")],
|
||||
accentColor: Color(hex: "FF85A1"),
|
||||
textColor: .white,
|
||||
secondaryTextColor: Color(hex: "E0B0FF"),
|
||||
useDarkMap: true
|
||||
)
|
||||
|
||||
static let ocean = ShareTheme(
|
||||
id: "ocean",
|
||||
name: "Ocean",
|
||||
gradientColors: [Color(hex: "023E8A"), Color(hex: "0077B6")],
|
||||
accentColor: Color(hex: "90E0EF"),
|
||||
textColor: .white,
|
||||
secondaryTextColor: Color(hex: "CAF0F8"),
|
||||
useDarkMap: true
|
||||
)
|
||||
|
||||
static let slate = ShareTheme(
|
||||
id: "slate",
|
||||
name: "Slate",
|
||||
gradientColors: [Color(hex: "2B2D42"), Color(hex: "3D405B")],
|
||||
accentColor: Color(hex: "F4A261"),
|
||||
textColor: Color(hex: "EDF2F4"),
|
||||
secondaryTextColor: Color(hex: "8D99AE"),
|
||||
useDarkMap: true
|
||||
)
|
||||
|
||||
static let all: [ShareTheme] = [.dark, .light, .midnight, .forest, .sunset, .berry, .ocean, .slate]
|
||||
|
||||
static func theme(byId id: String) -> ShareTheme {
|
||||
all.first { $0.id == id } ?? .dark
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Share Errors
|
||||
|
||||
enum ShareError: Error, LocalizedError {
|
||||
case renderingFailed
|
||||
case mapSnapshotFailed
|
||||
case instagramNotInstalled
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .renderingFailed:
|
||||
return "Failed to render share card"
|
||||
case .mapSnapshotFailed:
|
||||
return "Failed to generate map snapshot"
|
||||
case .instagramNotInstalled:
|
||||
return "Instagram is not installed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Card Dimensions
|
||||
|
||||
enum ShareCardDimensions {
|
||||
static let cardSize = CGSize(width: 1080, height: 1920)
|
||||
static let mapSnapshotSize = CGSize(width: 1000, height: 500)
|
||||
static let routeMapSize = CGSize(width: 1000, height: 600)
|
||||
static let padding: CGFloat = 60
|
||||
static let headerHeight: CGFloat = 120
|
||||
static let footerHeight: CGFloat = 100
|
||||
}
|
||||
Reference in New Issue
Block a user