import Foundation import SwiftUI import UIKit import CoreImage.CIFilterBuiltins class GoalShareService { static let shared = GoalShareService() /// App Store URL - auto-redirects to user's country static let appStoreURL = URL(string: "https://apps.apple.com/app/portfolio-journal/id6744983373")! /// Website URL - has Open Graph tags for social media previews static let websiteURL = URL(string: "https://portfoliojournal.app")! /// Share page base URL - parameters will be appended static let sharePageBaseURL = "https://portfoliojournal.app/share" private init() {} /// Build share URL with goal parameters for dynamic OG tags static func buildShareURL(goalName: String, progressPercent: Int) -> URL { var components = URLComponents(string: sharePageBaseURL)! components.queryItems = [ URLQueryItem(name: "goal", value: goalName), URLQueryItem(name: "progress", value: String(progressPercent)) ] return components.url ?? URL(string: sharePageBaseURL)! } /// Generate a QR code image for the App Store URL static func generateQRCode(for url: URL, size: CGFloat = 60) -> UIImage? { let context = CIContext() let filter = CIFilter.qrCodeGenerator() filter.message = Data(url.absoluteString.utf8) filter.correctionLevel = "M" guard let outputImage = filter.outputImage else { return nil } // Scale up the QR code let scale = size / outputImage.extent.size.width let scaledImage = outputImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) guard let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) else { return nil } return UIImage(cgImage: cgImage) } @MainActor func shareGoal( name: String, progress: Double, currentValue: Decimal, targetValue: Decimal, targetDate: Date? = nil, estimatedCompletionDate: Date? = nil, privacyMode: Bool = false ) { guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let viewController = windowScene.windows.first?.rootViewController else { return } // Generate QR code for the card let qrCodeImage = Self.generateQRCode(for: Self.appStoreURL, size: 200) let card = GoalShareCardView( name: name, progress: progress, currentValue: currentValue, targetValue: targetValue, targetDate: targetDate, estimatedCompletionDate: estimatedCompletionDate, privacyMode: privacyMode, qrCodeImage: qrCodeImage ) let progressPercent = Int(progress * 100) let shareURL = Self.buildShareURL(goalName: name, progressPercent: progressPercent) if #available(iOS 16.0, *) { let renderer = ImageRenderer(content: card) let scale = viewController.view.window?.windowScene?.screen.scale ?? viewController.traitCollection.displayScale renderer.scale = scale if let image = renderer.uiImage { // Use combined item provider for text + image together let shareItem = GoalShareItem( image: image, goalName: name, progressPercent: progressPercent, shareURL: shareURL ) presentShareSheet(items: [shareItem], from: viewController) } else { // Fallback to text only let text = buildShareText(goalName: name, progressPercent: progressPercent, shareURL: shareURL) presentShareSheet(items: [text], from: viewController) } } else { // iOS 15 fallback - text only let text = buildShareText(goalName: name, progressPercent: progressPercent, shareURL: shareURL) presentShareSheet(items: [text], from: viewController) } } private func buildShareText(goalName: String, progressPercent: Int, shareURL: URL) -> String { return """ I'm \(progressPercent)% towards my "\(goalName)" goal! 🎯 Track your investment goals with Portfolio Journal. \(shareURL.absoluteString) """ } private func presentShareSheet(items: [Any], from viewController: UIViewController) { let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil) // Exclude some activities that don't make sense for goal sharing activityVC.excludedActivityTypes = [ .addToReadingList, .assignToContact, .openInIBooks ] // iPad support - prevent crash by setting popover source 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 ) popover.permittedArrowDirections = [] } viewController.present(activityVC, animated: true) } } /// Combined share item that provides both image and text together private class GoalShareItem: NSObject, UIActivityItemSource { let image: UIImage let goalName: String let progressPercent: Int let shareURL: URL init(image: UIImage, goalName: String, progressPercent: Int, shareURL: URL) { self.image = image self.goalName = goalName self.progressPercent = progressPercent self.shareURL = shareURL super.init() } func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { return image } func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { // For most activities, return the image // The text will be provided via LPLinkMetadata or as a separate item return image } func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { return "My Investment Goal Progress - Portfolio Journal" } func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? { let metadata = LPLinkMetadata() metadata.title = "I'm \(progressPercent)% towards my \"\(goalName)\" goal! 🎯" metadata.originalURL = shareURL metadata.url = shareURL metadata.imageProvider = NSItemProvider(object: image) // Set icon if let appIcon = UIImage(named: "BrandMark") { metadata.iconProvider = NSItemProvider(object: appIcon) } return metadata } } import LinkPresentation