diff options
author | Hailey <me@haileyok.com> | 2024-02-27 15:22:03 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-27 15:22:03 -0800 |
commit | d451f82f54974b7b3da1477a7e1f221628860f62 (patch) | |
tree | 631d60b9d9ef47129529068753b10029fb99c34f /modules/Share-with-Bluesky/ShareViewController.swift | |
parent | ac726497a475f7492ee0269851979817b17d98c2 (diff) | |
download | voidsky-d451f82f54974b7b3da1477a7e1f221628860f62.tar.zst |
Share Extension/Intents (#2587)
* add native ios code outside of ios project * helper script * going to be a lot of these commits to squash...backing up * save * start of an expo plugin * create info.plist * copy the view controller * maybe working * working * wait working now * working plugin * use current scheme * update intent path * use better params * support text in uri * build * use better encoding * handle images * cleanup ios plugin * android * move bash script to /scripts * handle cases where loaded data is uiimage rather than uri * remove unnecessary logic, allow more than 4 images and just take first 4 * android build plugin * limit images to four on android * use js for plugins, no need to build * revert changes to app config * use correct scheme on android * android readme * move ios extension to /modules * remove unnecessary event * revert typo * plugin readme * scripts readme * add configurable scheme to .env, default to `bluesky` * remove debug * revert .gitignore change * add comment about updating .env to app.config.js for those modifying scheme * modify .env * update android module to use the proper url * update ios extension * remove comment * parse and validate incoming image uris * fix types * rm oops * fix a few typos
Diffstat (limited to 'modules/Share-with-Bluesky/ShareViewController.swift')
-rw-r--r-- | modules/Share-with-Bluesky/ShareViewController.swift | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/modules/Share-with-Bluesky/ShareViewController.swift b/modules/Share-with-Bluesky/ShareViewController.swift new file mode 100644 index 000000000..a16a290bf --- /dev/null +++ b/modules/Share-with-Bluesky/ShareViewController.swift @@ -0,0 +1,153 @@ +import UIKit + +class ShareViewController: UIViewController { + // This allows other forks to use this extension while also changing their + // scheme. + let appScheme = Bundle.main.object(forInfoDictionaryKey: "MainAppScheme") as? String ?? "bluesky" + + // + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, + let attachments = extensionItem.attachments, + let firstAttachment = extensionItem.attachments?.first + else { + self.completeRequest() + return + } + + Task { + if firstAttachment.hasItemConformingToTypeIdentifier("public.text") { + await self.handleText(item: firstAttachment) + } else if firstAttachment.hasItemConformingToTypeIdentifier("public.url") { + await self.handleUrl(item: firstAttachment) + } else if firstAttachment.hasItemConformingToTypeIdentifier("public.image") { + await self.handleImages(items: attachments) + } else { + self.completeRequest() + } + } + } + + private func handleText(item: NSItemProvider) async -> Void { + do { + if let data = try await item.loadItem(forTypeIdentifier: "public.text") as? String { + if let encoded = data.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let url = URL(string: "\(self.appScheme)://intent/compose?text=\(encoded)") + { + _ = self.openURL(url) + } + } + self.completeRequest() + } catch { + self.completeRequest() + } + } + + private func handleUrl(item: NSItemProvider) async -> Void { + do { + if let data = try await item.loadItem(forTypeIdentifier: "public.url") as? URL { + if let encoded = data.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let url = URL(string: "\(self.appScheme)://intent/compose?text=\(encoded)") + { + _ = self.openURL(url) + } + } + self.completeRequest() + } catch { + self.completeRequest() + } + } + + private func handleImages(items: [NSItemProvider]) async -> Void { + let firstFourItems: [NSItemProvider] + if items.count < 4 { + firstFourItems = items + } else { + firstFourItems = Array(items[0...3]) + } + + var valid = true + var imageUris = "" + + for (index, item) in firstFourItems.enumerated() { + var imageUriInfo: String? = nil + + do { + if let dataUri = try await item.loadItem(forTypeIdentifier: "public.image") as? URL { + // We need to duplicate this image, since we don't have access to the outgoing temp directory + // We also will get the image dimensions here, sinze RN makes it difficult to get those dimensions for local files + let data = try Data(contentsOf: dataUri) + let image = UIImage(data: data) + imageUriInfo = self.saveImageWithInfo(image) + } else if let image = try await item.loadItem(forTypeIdentifier: "public.image") as? UIImage { + imageUriInfo = self.saveImageWithInfo(image) + } + } catch { + valid = false + } + + if let imageUriInfo = imageUriInfo { + imageUris.append(imageUriInfo) + if index < items.count - 1 { + imageUris.append(",") + } + } else { + valid = false + } + } + + if valid, + let encoded = imageUris.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), + let url = URL(string: "\(self.appScheme)://intent/compose?imageUris=\(encoded)") + { + _ = self.openURL(url) + } + + self.completeRequest() + } + + private func saveImageWithInfo(_ image: UIImage?) -> String? { + guard let image = image else { + return nil + } + + do { + // Saving this file to the bundle group's directory lets us access it from + // inside of the app. Otherwise, we wouldn't have access even though the + // extension does. + if let dir = FileManager() + .containerURL( + forSecurityApplicationGroupIdentifier: "group.\(Bundle.main.bundleIdentifier?.replacingOccurrences(of: ".Share-with-Bluesky", with: "") ?? "")") + { + let filePath = "\(dir.absoluteString)\(ProcessInfo.processInfo.globallyUniqueString).jpeg" + + if let newUri = URL(string: filePath), + let jpegData = image.jpegData(compressionQuality: 1) + { + try jpegData.write(to: newUri) + return "\(newUri.absoluteString)|\(image.size.width)|\(image.size.height)" + } + } + return nil + } catch { + return nil + } + } + + private func completeRequest() -> Void { + self.extensionContext?.completeRequest(returningItems: nil) + } + + @objc func openURL(_ url: URL) -> Bool { + var responder: UIResponder? = self + while responder != nil { + if let application = responder as? UIApplication { + return application.perform(#selector(openURL(_:)), with: url) != nil + } + responder = responder?.next + } + return false + } +} |