Files
AFFiNE-Mirror/packages/frontend/apps/ios/App/ShareExtension/ShareViewController.swift
T
keepClamDown 329839e467 feat(ios): add share to support (#15340)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added iOS sharing for URLs, text, web pages, and images.
* Shared content can be reviewed, titled, and saved to an AFFiNE
workspace.
  * Choose destinations including workspaces, tags, and collections.
* Added previews, attachment handling, import status, retry support, and
success/error feedback.
* Pending shares are processed when AFFiNE opens or returns to the
foreground.
* **Bug Fixes**
* Improved workspace profile handling across different workspace types.
* Onboarding completion is now recorded immediately after successful
sign-in verification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DarkSky <25152247+darkskygit@users.noreply.github.com>
2026-08-27 19:09:51 +08:00

90 lines
2.5 KiB
Swift

//
// ShareViewController.swift
// ShareExtension
//
import SwiftUI
import UIKit
final class ShareViewController: UIViewController {
private let viewModel = ShareViewModel()
private var hostingController: UIHostingController<ShareExtensionView>?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let rootView = ShareExtensionView(
viewModel: viewModel,
onCancel: { [weak self] in
self?.cancel()
},
onSave: { [weak self] in
self?.save()
}
)
let hosting = UIHostingController(rootView: rootView)
hostingController = hosting
addChild(hosting)
view.addSubview(hosting.view)
hosting.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hosting.view.topAnchor.constraint(equalTo: view.topAnchor),
hosting.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hosting.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hosting.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
hosting.didMove(toParent: self)
Task {
await viewModel.load(from: extensionContext)
}
}
private func cancel() {
extensionContext?.completeRequest(returningItems: nil)
}
private func save() {
Task { [weak self] in
guard let self else { return }
let success = await viewModel.save()
guard success else { return }
_ = await openMainAppIfPossible()
extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
}
@discardableResult
private func openMainAppIfPossible() async -> Bool {
let url = ShareInboxConstants.openInboxURL
let openedByContext = await withCheckedContinuation { continuation in
extensionContext?.open(url) { success in
continuation.resume(returning: success)
} ?? continuation.resume(returning: false)
}
let opened = openedByContext || openMainAppViaResponderChain(url)
#if DEBUG
NSLog(
"[AFFiNE Share] open url=%@ extensionContext=%@ final=%@",
url.absoluteString,
openedByContext ? "YES" : "NO",
opened ? "YES" : "NO"
)
#endif
return opened
}
private func openMainAppViaResponderChain(_ url: URL) -> Bool {
var responder: UIResponder? = self
while let current = responder {
if let application = current as? UIApplication {
application.open(url, options: [:], completionHandler: nil)
return true
}
responder = current.next
}
return false
}
}