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:
121
SportsTime/Export/Sharing/ShareService.swift
Normal file
121
SportsTime/Export/Sharing/ShareService.swift
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// ShareService.swift
|
||||
// SportsTime
|
||||
//
|
||||
// Handles Instagram direct share and fallback to system share sheet.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
final class ShareService {
|
||||
|
||||
static let shared = ShareService()
|
||||
|
||||
private init() {}
|
||||
|
||||
// MARK: - Share to Instagram
|
||||
|
||||
func shareToInstagram(image: UIImage) -> Bool {
|
||||
guard let imageData = image.pngData() else { return false }
|
||||
|
||||
// Check if Instagram is installed
|
||||
guard let instagramURL = URL(string: "instagram-stories://share"),
|
||||
UIApplication.shared.canOpenURL(instagramURL) else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Set up pasteboard with image
|
||||
let pasteboardItems: [String: Any] = [
|
||||
"com.instagram.sharedSticker.backgroundImage": imageData
|
||||
]
|
||||
|
||||
UIPasteboard.general.setItems(
|
||||
[pasteboardItems],
|
||||
options: [.expirationDate: Date().addingTimeInterval(300)]
|
||||
)
|
||||
|
||||
// Open Instagram Stories
|
||||
let urlString = "instagram-stories://share?source_application=com.sportstime.app"
|
||||
if let url = URL(string: urlString) {
|
||||
UIApplication.shared.open(url)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// MARK: - Copy to Clipboard
|
||||
|
||||
func copyToClipboard(image: UIImage) {
|
||||
UIPasteboard.general.image = image
|
||||
}
|
||||
|
||||
// MARK: - System Share Sheet
|
||||
|
||||
func presentShareSheet(image: UIImage, from viewController: UIViewController) {
|
||||
let activityVC = UIActivityViewController(
|
||||
activityItems: [image],
|
||||
applicationActivities: nil
|
||||
)
|
||||
|
||||
// iPad support
|
||||
if let popover = activityVC.popoverPresentationController {
|
||||
popover.sourceView = viewController.view
|
||||
popover.sourceRect = CGRect(
|
||||
x: viewController.view.bounds.midX,
|
||||
y: viewController.view.bounds.midY,
|
||||
width: 0,
|
||||
height: 0
|
||||
)
|
||||
}
|
||||
|
||||
viewController.present(activityVC, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Theme Persistence
|
||||
|
||||
enum ShareThemePreferences {
|
||||
private static let tripKey = "shareTheme.trip"
|
||||
private static let achievementKey = "shareTheme.achievement"
|
||||
private static let progressKey = "shareTheme.progress"
|
||||
|
||||
static var tripTheme: ShareTheme {
|
||||
get { ShareTheme.theme(byId: UserDefaults.standard.string(forKey: tripKey) ?? "dark") }
|
||||
set { UserDefaults.standard.set(newValue.id, forKey: tripKey) }
|
||||
}
|
||||
|
||||
static var achievementTheme: ShareTheme {
|
||||
get { ShareTheme.theme(byId: UserDefaults.standard.string(forKey: achievementKey) ?? "dark") }
|
||||
set { UserDefaults.standard.set(newValue.id, forKey: achievementKey) }
|
||||
}
|
||||
|
||||
static var progressTheme: ShareTheme {
|
||||
get { ShareTheme.theme(byId: UserDefaults.standard.string(forKey: progressKey) ?? "dark") }
|
||||
set { UserDefaults.standard.set(newValue.id, forKey: progressKey) }
|
||||
}
|
||||
|
||||
static func theme(for cardType: ShareCardType) -> ShareTheme {
|
||||
switch cardType {
|
||||
case .tripSummary:
|
||||
return tripTheme
|
||||
case .achievementSpotlight, .achievementCollection, .achievementMilestone, .achievementContext:
|
||||
return achievementTheme
|
||||
case .stadiumProgress:
|
||||
return progressTheme
|
||||
}
|
||||
}
|
||||
|
||||
static func setTheme(_ theme: ShareTheme, for cardType: ShareCardType) {
|
||||
switch cardType {
|
||||
case .tripSummary:
|
||||
tripTheme = theme
|
||||
case .achievementSpotlight, .achievementCollection, .achievementMilestone, .achievementContext:
|
||||
achievementTheme = theme
|
||||
case .stadiumProgress:
|
||||
progressTheme = theme
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user