// // 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 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: - Derived Theme Properties /// Glass panel fill — textColor at low opacity var surfaceColor: Color { textColor.opacity(0.08) } /// Panel border — textColor at medium-low opacity var borderColor: Color { textColor.opacity(0.15) } /// Glow effect color — accentColor at medium opacity var glowColor: Color { accentColor.opacity(0.4) } /// Highlight gradient for accent elements var highlightGradient: [Color] { [accentColor, accentColor.opacity(0.6)] } /// Mid-tone color derived from gradient endpoints for richer backgrounds var midGradientColor: Color { gradientColors.count >= 2 ? gradientColors[0].blendedWith(gradientColors[1], fraction: 0.5) : gradientColors.first ?? .black } } // MARK: - Color Blending Helper extension Color { /// Simple blend between two colors at a given fraction (0 = self, 1 = other) func blendedWith(_ other: Color, fraction: Double) -> Color { let f = max(0, min(1, fraction)) let c1 = UIColor(self).rgbaComponents let c2 = UIColor(other).rgbaComponents return Color( red: c1.r + (c2.r - c1.r) * f, green: c1.g + (c2.g - c1.g) * f, blue: c1.b + (c2.b - c1.b) * f, opacity: c1.a + (c2.a - c1.a) * f ) } } private extension UIColor { var rgbaComponents: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) { var r: CGFloat = 0 var g: CGFloat = 0 var b: CGFloat = 0 var a: CGFloat = 0 if getRed(&r, green: &g, blue: &b, alpha: &a) { return (r, g, b, a) } var white: CGFloat = 0 if getWhite(&white, alpha: &a) { return (white, white, white, a) } return (0, 0, 0, 1) } } // 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: 960, height: 480) // Must fit within cardSize.width - 2*padding static let routeMapSize = CGSize(width: 960, height: 576) // Must fit within cardSize.width - 2*padding static let padding: CGFloat = 60 static let headerHeight: CGFloat = 120 static let footerHeight: CGFloat = 100 }