// // ShareViewController.swift // Haystack // // Created by Rio Keefe on 03/05/2025. // import UIKit import Social import MobileCoreServices // For kUTTypeImage class ShareViewController: SLComposeServiceViewController { let appGroupName = "group.com.haystack.app" // Replace with your actual App Group identifier let tokenKey = "sharedAuthToken" let uploadURLBase = URL(string: "https://haystack.johncosta.tech/image/")! // Base URL var bearerToken: String? private var imageItemProvider: NSItemProvider? // Store a base name, extension will be determined during item loading private var baseImageName: String = "SharedImage" // A more descriptive default override func viewDidLoad() { super.viewDidLoad() if let sharedDefaults = UserDefaults(suiteName: appGroupName) { bearerToken = sharedDefaults.string(forKey: tokenKey) print("Retrieved bearer token: \(bearerToken ?? "nil")") } else { print("Error accessing App Group UserDefaults.") // Invalidate content if token is crucial and missing // This will be caught by isContentValid() } guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, let provider = extensionItem.attachments?.first else { print("No attachments found.") // Invalidate content if no provider self.imageItemProvider = nil // Ensure it's nil so isContentValid fails return } if provider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) { self.imageItemProvider = provider // Attempt to get a suggested name early if available, and clean it. // This will be our default base name if the item itself doesn't provide a better one. if let suggested = provider.suggestedName, !suggested.isEmpty { if let dotRange = suggested.range(of: ".", options: .backwards) { self.baseImageName = String(suggested[..:") self.baseImageName = self.baseImageName.components(separatedBy: anInvalidCharacters).joined(separator: "_") if self.baseImageName.isEmpty { self.baseImageName = "SharedImage" } // Ensure not empty } else { print("Attachment is not an image.") self.imageItemProvider = nil // Ensure it's nil so isContentValid fails } } override func isContentValid() -> Bool { // Content is valid only if we have an item provider for an image AND a bearer token let isValid = imageItemProvider != nil && bearerToken != nil if imageItemProvider == nil { print("isContentValid: imageItemProvider is nil") } if bearerToken == nil { print("isContentValid: bearerToken is nil") } return isValid } override func didSelectPost() { guard let provider = imageItemProvider else { print("Error: No image item provider found when posting.") informUserAndCancel(message: "No image found to share.") return } guard let token = bearerToken else { print("Error: Bearer token is missing when posting.") informUserAndCancel(message: "Authentication error. Please try again.") return } // Start activity indicator or similar UI feedback // For SLComposeServiceViewController, the system provides some UI provider.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil) { [weak self] (item, error) in guard let self = self else { return } if let error = error { print("Error loading image data for upload: \(error.localizedDescription)") self.informUserAndCancel(message: "Could not load image: \(error.localizedDescription)") return } var imageData: Data? var finalImageNameWithExtension: String var mimeType: String = "application/octet-stream" // Default MIME type // Determine base name (without extension) var currentBaseName = self.baseImageName // Use the one prepared in viewDidLoad if let suggested = provider.suggestedName, !suggested.isEmpty { if let dotRange = suggested.range(of: ".", options: .backwards) { currentBaseName = String(suggested[..:") currentBaseName = currentBaseName.components(separatedBy: anInvalidCharacters).joined(separator: "_") if currentBaseName.isEmpty { currentBaseName = "DefaultImageName" } } if let url = item as? URL { print("Image provided as URL: \(url)") finalImageNameWithExtension = url.lastPathComponent // Includes extension // Ensure baseName is updated if URL provides a different one if let dotRange = finalImageNameWithExtension.range(of:".", options: .backwards) { currentBaseName = String(finalImageNameWithExtension[.. [Any]! { // No configuration items needed for this simple image uploader. return [] } // Helper to inform user and cancel request private func informUserAndCancel(message: String) { let error = NSError(domain: "com.haystack.ShareExtension", code: 0, userInfo: [NSLocalizedDescriptionKey: message]) print("Informing user: \(message)") // You could present an alert here if SLComposeServiceViewController allows easy alert presentation. // For now, just cancel the request. The system might show a generic error. self.extensionContext!.cancelRequest(withError: error) } // Helper to get MIME type from path extension private func mimeType(forPathExtension pathExtension: String) -> String { let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue() if let uti = uti { let mimeType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() if let mimeType = mimeType { return mimeType as String } } // Fallback for common types if UTType fails or for robustness switch pathExtension.lowercased() { // case "jpg", "jpeg": return "image/jpeg" // case "png": return "image/png" // case "gif": return "image/gif" // case "bmp": return "image/bmp" // case "tiff", "tif": return "image/tiff" default: return "application/octet-stream" // Generic fallback } } }