1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
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 if firstAttachment.hasItemConformingToTypeIdentifier("public.video") {
await self.handleVideos(items: attachments)
} else {
self.completeRequest()
}
}
}
private func handleText(item: NSItemProvider) async {
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()
}
private func handleUrl(item: NSItemProvider) async {
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()
}
private func handleImages(items: [NSItemProvider]) async {
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?
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 handleVideos(items: [NSItemProvider]) async {
let firstItem = items.first
if let dataUri = try? await firstItem?.loadItem(forTypeIdentifier: "public.movie") as? URL {
let ext = String(dataUri.lastPathComponent.split(separator: ".").last ?? "mp4")
if let tempUrl = getTempUrl(ext: ext) {
let data = try? Data(contentsOf: dataUri)
try? data?.write(to: tempUrl)
if let encoded = dataUri.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
let url = URL(string: "\(self.appScheme)://intent/compose?videoUri=\(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 tempUrl = getTempUrl(ext: "jpeg"),
let jpegData = image.jpegData(compressionQuality: 1) {
try jpegData.write(to: tempUrl)
return "\(tempUrl.absoluteString)|\(image.size.width)|\(image.size.height)"
}
} catch {}
return nil
}
private func completeRequest() {
self.extensionContext?.completeRequest(returningItems: nil)
}
private func getTempUrl(ext: String) -> URL? {
if let dir = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "group.app.bsky") {
return URL(string: "\(dir.absoluteString)\(ProcessInfo.processInfo.globallyUniqueString).\(ext)")!
}
return 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
}
}
|