Files
GIFCollector/GIFCollectorShare/ShareViewController.swift
Joshua Higgins 7fc60e939e Fixed App Groups
2025-06-05 00:06:17 -04:00

355 lines
11 KiB
Swift

//
// ShareViewController.swift
// GIFCollectorShare
//
import MobileCoreServices
import Social
import UIKit
import UniformTypeIdentifiers
class ShareViewController: SLComposeServiceViewController {
private var receivedURL: URL?
private var gifData: Data?
private var isProcessing = false
private var originalURL: String = ""
private var debugMessages: [String] = []
// Use the same constants as in the main app
private let appGroupID = "group."+Bundle.main.bundleIdentifier.unsafelyUnwrapped
private let pendingGIFsKey = "pendingGIFs"
override func viewDidLoad() {
super.viewDidLoad()
title = "Save to GIF Collector"
placeholder = "Add a note (optional)"
// Add debug button in development
#if DEBUG
let debugButton = UIBarButtonItem(
title: "Debug", style: .plain, target: self, action: #selector(showDebugInfo))
navigationItem.leftBarButtonItem = debugButton
#endif
// Start processing the shared item
processSharedItem()
addDebugMessage("ShareViewController loaded")
}
override func isContentValid() -> Bool {
// No validation needed for the text field, we just want the GIF
return true
}
override func didSelectPost() {
// Show loading
isProcessing = true
navigationController?.navigationBar.isUserInteractionEnabled = false
// If we have a URL but no data, we need to download it
if let receivedURL = receivedURL, gifData == nil {
downloadGIF(from: receivedURL) { [weak self] data, error in
guard let self = self else { return }
if let data = data {
self.gifData = data
self.saveGIF()
} else {
self.showError(
message: "Could not download GIF: \(error?.localizedDescription ?? "Unknown error")")
}
}
} else if let gifData = gifData {
// We already have the data, save it directly
saveGIF()
} else {
// No valid GIF was found
showError(message: "No valid GIF found in the shared content")
}
}
override func didSelectCancel() {
// User canceled, close the extension
completeRequest()
}
// MARK: - Private Methods
private func processSharedItem() {
addDebugMessage("Starting to process shared item")
// Log extension context info
if let context = extensionContext {
addDebugMessage("Extension context exists")
addDebugMessage("Input items count: \(context.inputItems.count)")
} else {
addDebugMessage("Extension context is nil")
}
guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else {
addDebugMessage("No extension item found")
showError(message: "No shared content found")
return
}
guard let attachments = extensionItem.attachments else {
addDebugMessage("No attachments found")
showError(message: "No attachments found")
return
}
addDebugMessage("Found \(attachments.count) attachments")
// Process each attachment
for attachment in attachments {
// Log attachment types
let typeIdentifiers = attachment.registeredTypeIdentifiers
addDebugMessage("Attachment types: \(typeIdentifiers.joined(separator: ", "))")
// Check for URLs first (for web links to GIFs)
if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
addDebugMessage("Processing URL type attachment")
attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) {
[weak self] item, error in
guard let self = self else { return }
if let url = item as? URL, url.absoluteString.lowercased().hasSuffix(".gif") {
DispatchQueue.main.async {
self.receivedURL = url
self.originalURL = url.absoluteString
self.updateUI()
}
} else if let url = item as? URL {
// It's a URL but not directly to a GIF, might be a webpage containing a GIF
DispatchQueue.main.async {
self.originalURL = url.absoluteString
self.receivedURL = url
self.updateUI()
self.addDebugMessage("URL processed: \(url.absoluteString)")
}
}
}
continue
}
// Check for GIF files
if attachment.hasItemConformingToTypeIdentifier("com.compuserve.gif")
|| attachment.hasItemConformingToTypeIdentifier("public.gif")
{
addDebugMessage("Processing GIF type attachment")
attachment.loadItem(forTypeIdentifier: "com.compuserve.gif", options: nil) {
[weak self] item, error in
guard let self = self else { return }
if let url = item as? URL {
DispatchQueue.main.async {
self.receivedURL = url
self.originalURL = url.absoluteString
self.loadGIFData(from: url)
self.updateUI()
}
} else if let data = item as? Data {
DispatchQueue.main.async {
self.gifData = data
self.updateUI()
}
}
}
continue
}
// Check for images that might be GIFs
if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
attachment.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) {
[weak self] item, error in
guard let self = self else { return }
if let url = item as? URL, url.pathExtension.lowercased() == "gif" {
DispatchQueue.main.async {
self.receivedURL = url
self.originalURL = url.absoluteString
self.loadGIFData(from: url)
self.updateUI()
}
} else if let data = item as? Data, self.isGIFData(data) {
DispatchQueue.main.async {
self.gifData = data
self.updateUI()
}
}
}
}
}
}
private func updateUI() {
// Enable the Post button if we have a URL or data
navigationItem.rightBarButtonItem?.isEnabled = (receivedURL != nil || gifData != nil)
// Update content area
if let url = receivedURL {
let displayText = "GIF from: \(url.host ?? "URL")"
if contentText.isEmpty {
}
}
}
private func loadGIFData(from url: URL) {
do {
let data = try Data(contentsOf: url)
if isGIFData(data) {
gifData = data
}
} catch {
print("Error loading GIF data: \(error)")
}
}
private func isGIFData(_ data: Data) -> Bool {
// Simple check for GIF file signature: "GIF87a" or "GIF89a"
guard data.count > 6 else { return false }
let header = data.prefix(6)
let gif87a = Data([0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) // "GIF87a"
let gif89a = Data([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]) // "GIF89a"
return header == gif87a || header == gif89a
}
private func downloadGIF(from url: URL, completion: @escaping (Data?, Error?) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
DispatchQueue.main.async {
if let data = data, self.isGIFData(data) {
completion(data, nil)
} else {
completion(
nil,
error
?? NSError(
domain: "GIFShare", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Invalid GIF data"]))
}
}
}.resume()
}
private func saveGIF() {
guard let gifData = gifData else {
showError(message: "No GIF data to save")
return
}
addDebugMessage("Starting to save GIF, size: \(gifData.count) bytes")
// Save the GIF using the shared App Group container
let userDefaults = UserDefaults(suiteName: appGroupID)
let fileManager = FileManager.default
// IMPORTANT: Use the same path conventions as GIFFileManager
guard
let containerURL = fileManager.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)
else {
showError(message: "Could not access shared container")
return
}
// Use the same folder name as in GIFFileManager
let gifsFolder = containerURL.appendingPathComponent("SharedGIFs", isDirectory: true)
// Create GIFs directory if needed
if !fileManager.fileExists(atPath: gifsFolder.path) {
do {
try fileManager.createDirectory(at: gifsFolder, withIntermediateDirectories: true)
addDebugMessage("Created shared GIFs directory")
} catch {
showError(message: "Could not create GIFs directory: \(error.localizedDescription)")
return
}
}
// Create a unique filename using the same pattern as GIFFileManager
let urlHash = originalURL.hashValue
let timestamp = Int(Date().timeIntervalSince1970)
let filename = "gif_\(urlHash)_\(timestamp).gif"
let fileURL = gifsFolder.appendingPathComponent(filename)
// Save the GIF file
do {
try gifData.write(to: fileURL)
addDebugMessage("GIF saved to: \(fileURL.path)")
// Create a UUID that will be used consistently across the app
let gifId = UUID()
// Store GIF entry in UserDefaults to notify main app
let gifInfo: [String: Any] = [
"localFilePath": fileURL.path,
"originalURL": originalURL,
"createdAt": Date().timeIntervalSince1970,
"id": gifId.uuidString,
]
// Add to pending GIFs list (using the same key as in GIFStorageService)
var pendingGIFs = userDefaults?.array(forKey: pendingGIFsKey) as? [[String: Any]] ?? []
pendingGIFs.append(gifInfo)
userDefaults?.set(pendingGIFs, forKey: pendingGIFsKey)
// Force synchronize to ensure changes are visible immediately
userDefaults?.synchronize()
addDebugMessage("GIF info saved to UserDefaults")
// Success
completeRequest()
} catch {
showError(message: "Error saving GIF: \(error.localizedDescription)")
}
}
private func addDebugMessage(_ message: String) {
let timestamp = DateFormatter.localizedString(
from: Date(), dateStyle: .none, timeStyle: .medium)
debugMessages.append("[\(timestamp)] \(message)")
print("GIFCollector Debug: \(message)")
}
@objc private func showDebugInfo() {
let debugText = debugMessages.joined(separator: "\n\n")
let alert = UIAlertController(
title: "Debug Information", message: debugText, preferredStyle: .alert)
alert.addAction(
UIAlertAction(title: "Copy", style: .default) { _ in
UIPasteboard.general.string = debugText
})
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
present(alert, animated: true)
}
private func showError(message: String) {
addDebugMessage("ERROR: \(message)")
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(
UIAlertAction(title: "OK", style: .default) { [weak self] _ in
self?.completeRequest()
})
// Add debug info button
#if DEBUG
alert.addAction(
UIAlertAction(title: "Debug Info", style: .default) { [weak self] _ in
self?.showDebugInfo()
})
#endif
present(alert, animated: true)
}
private func completeRequest() {
addDebugMessage("Completing extension request")
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}