355 lines
11 KiB
Swift
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.replacing(".Share", with:"")
|
|
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)
|
|
}
|
|
}
|