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>
122 lines
3.6 KiB
Swift
122 lines
3.6 KiB
Swift
//
|
|
// 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
|
|
}
|
|
}
|
|
}
|