import UIKit import UniformTypeIdentifiers @objc(ShareViewController) class ShareViewController: UIViewController { private let appGroupId = "group.com.magicive.marks" private let configKey = "serverConfig" override func viewDidLoad() { super.viewDidLoad() let blur = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) blur.frame = view.bounds blur.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(blur) extractURLAndSave() } private func extractURLAndSave() { guard let item = extensionContext?.inputItems.first as? NSExtensionItem, let attachments = item.attachments else { showResult(success: false, message: "Nothing to save") return } func load(typeId: String) -> Bool { guard let attachment = attachments.first(where: { $0.hasItemConformingToTypeIdentifier(typeId) }) else { return false } attachment.loadItem(forTypeIdentifier: typeId, options: nil) { [weak self] item, _ in let urlString = typeId == UTType.url.identifier ? ((item as? URL)?.absoluteString ?? (item as? String)) : (item as? String) DispatchQueue.main.async { if let url = urlString { self?.save(url: url) } else { self?.showResult(success: false, message: "No URL found") } } } return true } if !load(typeId: UTType.url.identifier) && !load(typeId: UTType.plainText.identifier) { showResult(success: false, message: "No URL found") } } private func save(url: String) { guard let groupDefaults = UserDefaults(suiteName: appGroupId), let data = groupDefaults.data(forKey: configKey), let config = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let host = config["host"] as? String, let token = config["token"] as? String, let useHttps = config["useHttps"] as? Bool else { showResult(success: false, message: "Open Marks first") return } var base = (useHttps ? "https" : "http") + "://" + host if let port = config["port"] as? Int { base += ":\(port)" } if let path = config["path"] as? String, !path.isEmpty { base += path } guard let apiUrl = URL(string: "\(base)/api/bookmarks/") else { showResult(success: false, message: "Bad server URL") return } var req = URLRequest(url: apiUrl) req.httpMethod = "POST" req.setValue("Token \(token)", forHTTPHeaderField: "Authorization") req.setValue("application/json", forHTTPHeaderField: "Content-Type") req.httpBody = try? JSONSerialization.data(withJSONObject: [ "url": url, "is_archived": false, "unread": false, "shared": false ]) URLSession.shared.dataTask(with: req) { [weak self] _, response, error in DispatchQueue.main.async { let code = (response as? HTTPURLResponse)?.statusCode ?? 0 let ok = error == nil && (code == 200 || code == 201) self?.showResult(success: ok, message: ok ? "Bookmarked" : "Failed to save") } }.resume() } private func showResult(success: Bool, message: String) { let card = UIView() card.backgroundColor = success ? .systemGreen : .systemRed card.layer.cornerRadius = 16 card.layer.cornerCurve = .continuous card.translatesAutoresizingMaskIntoConstraints = false card.alpha = 0 let stack = UIStackView() stack.axis = .horizontal stack.spacing = 10 stack.alignment = .center stack.translatesAutoresizingMaskIntoConstraints = false let icon = UIImageView(image: UIImage(systemName: success ? "checkmark.circle.fill" : "xmark.circle.fill")) icon.tintColor = .white icon.preferredSymbolConfiguration = .init(pointSize: 22, weight: .semibold) let label = UILabel() label.text = message label.textColor = .white label.font = .systemFont(ofSize: 17, weight: .semibold) stack.addArrangedSubview(icon) stack.addArrangedSubview(label) card.addSubview(stack) view.addSubview(card) NSLayoutConstraint.activate([ stack.centerXAnchor.constraint(equalTo: card.centerXAnchor), stack.centerYAnchor.constraint(equalTo: card.centerYAnchor), stack.leadingAnchor.constraint(greaterThanOrEqualTo: card.leadingAnchor, constant: 24), card.centerXAnchor.constraint(equalTo: view.centerXAnchor), card.centerYAnchor.constraint(equalTo: view.centerYAnchor), card.heightAnchor.constraint(equalToConstant: 56), card.widthAnchor.constraint(greaterThanOrEqualToConstant: 180), ]) UIView.animate(withDuration: 0.2) { card.alpha = 1 } DispatchQueue.main.asyncAfter(deadline: .now() + (success ? 0.8 : 2.0)) { [weak self] in UIView.animate(withDuration: 0.15, animations: { card.alpha = 0 }) { _ in self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } } } }