ultisuite-client/mobile/native/ios/ShareExtension/ShareViewController.swift
R3D347HR4Y d6d18f911b
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
Lots of stuff and mobile app
2026-06-17 00:13:28 +02:00

92 lines
3.7 KiB
Swift

// iOS Share Extension scaffold.
//
// Add this as a new "Share Extension" target in gen/apple/ after
// `pnpm tauri ios init`. The extension writes the shared payload into the
// shared App Group container, then opens the host app via its URL scheme so the
// JS layer (lib/native/share.ts -> share_take_pending) can drain it.
//
// Requirements (Apple Developer): App Group `group.space.ulti.suite` enabled on
// BOTH the host app and the extension target.
import UIKit
import Social
import UniformTypeIdentifiers
final class ShareViewController: UIViewController {
private let appGroup = "group.space.ulti.suite"
private let hostScheme = "ultimail" // per app: ultidrive / ultiagenda / ...
override func viewDidLoad() {
super.viewDidLoad()
handleShare()
}
private func handleShare() {
guard
let item = extensionContext?.inputItems.first as? NSExtensionItem,
let provider = item.attachments?.first
else { return complete() }
// Text / URL
if provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
provider.loadItem(forTypeIdentifier: UTType.url.identifier) { [weak self] data, _ in
let url = (data as? URL)?.absoluteString
self?.enqueue(kind: "url", text: nil, url: url, files: [])
}
} else if provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
provider.loadItem(forTypeIdentifier: UTType.plainText.identifier) { [weak self] data, _ in
self?.enqueue(kind: "text", text: data as? String, url: nil, files: [])
}
} else {
// File: copy into the shared container, pass its path.
provider.loadItem(forTypeIdentifier: UTType.item.identifier) { [weak self] data, _ in
guard let self, let src = data as? URL else { self?.complete(); return }
let dest = self.copyIntoAppGroup(src)
self.enqueue(kind: "file", text: nil, url: nil, files: dest.map { [$0] } ?? [])
}
}
}
private func copyIntoAppGroup(_ src: URL) -> String? {
guard let container = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: appGroup)?
.appendingPathComponent("shared-inbox", isDirectory: true)
else { return nil }
try? FileManager.default.createDirectory(at: container, withIntermediateDirectories: true)
let dest = container.appendingPathComponent(src.lastPathComponent)
try? FileManager.default.removeItem(at: dest)
try? FileManager.default.copyItem(at: src, to: dest)
return dest.path
}
private func enqueue(kind: String, text: String?, url: String?, files: [String]) {
let payload: [String: Any] = [
"kind": kind, "text": text as Any, "url": url as Any, "files": files,
]
if let defaults = UserDefaults(suiteName: appGroup) {
var queue = defaults.array(forKey: "ulti.share.queue") as? [[String: Any]] ?? []
queue.append(payload)
defaults.set(queue, forKey: "ulti.share.queue")
}
openHostApp()
}
private func openHostApp() {
if let url = URL(string: "\(hostScheme)://go/share") {
var responder: UIResponder? = self
while let r = responder {
if let app = r as? UIApplication {
app.open(url, options: [:], completionHandler: nil)
break
}
responder = r.next
}
}
complete()
}
private func complete() {
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
}
}