Files
ux/ios/Classes/FilePlugin.swift
2026-04-22 22:42:53 +03:00

116 lines
4.2 KiB
Swift

import Flutter
import QuickLook
import UIKit
public class FilePlugin: NSObject, NativePlugin {
private var channel: FlutterMethodChannel?
private var previewDataSource: FilePreviewDataSource?
public func register(with registrar: FlutterPluginRegistrar) {
let c = FlutterMethodChannel(name: "ux/file", binaryMessenger: registrar.messenger())
c.setMethodCallHandler { [weak self] call, result in
self?.handle(call, result: result)
}
channel = c
}
private func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "share": handleShare(call, result: result)
case "open": handleOpen(call, result: result)
default: result(FlutterMethodNotImplemented)
}
}
private func handleShare(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? [String: Any],
let path = args["path"] as? String else {
return result(FlutterError(code: "bad_args", message: "path is required", details: nil))
}
guard let topVC = UxWindow.topViewController else {
return result(FlutterError(code: "no_view", message: "no top view controller", details: nil))
}
let url = URL(fileURLWithPath: path)
let title = args["title"] as? String
let items: [Any] = title.map { [FileActivityItemSource(url: url, title: $0)] } ?? [url]
let vc = UIActivityViewController(activityItems: items, applicationActivities: nil)
if UIDevice.current.userInterfaceIdiom == .pad,
let popover = vc.popoverPresentationController {
popover.sourceView = topVC.view
if let r = args["sourceRect"] as? [String: Any],
let x = (r["x"] as? NSNumber)?.doubleValue,
let y = (r["y"] as? NSNumber)?.doubleValue,
let w = (r["w"] as? NSNumber)?.doubleValue,
let h = (r["h"] as? NSNumber)?.doubleValue {
popover.sourceRect = CGRect(x: x, y: y, width: w, height: h)
} else {
let b = topVC.view.bounds
popover.sourceRect = CGRect(x: b.midX, y: b.midY, width: 1, height: 1)
popover.permittedArrowDirections = []
}
}
topVC.present(vc, animated: true) {
result(true)
}
}
private func handleOpen(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? [String: Any],
let path = args["path"] as? String else {
return result(FlutterError(code: "bad_args", message: "path is required", details: nil))
}
guard let topVC = UxWindow.topViewController else {
return result(FlutterError(code: "no_view", message: "no top view controller", details: nil))
}
let url = URL(fileURLWithPath: path)
let ds = FilePreviewDataSource(url: url)
// QLPreviewController requires a strong-retained data source.
previewDataSource = ds
let vc = QLPreviewController()
vc.dataSource = ds
topVC.present(vc, animated: true) {
result(true)
}
}
}
private final class FilePreviewDataSource: NSObject, QLPreviewControllerDataSource {
let url: URL
init(url: URL) { self.url = url }
func numberOfPreviewItems(in controller: QLPreviewController) -> Int { 1 }
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
url as QLPreviewItem
}
}
private final class FileActivityItemSource: NSObject, UIActivityItemSource {
let url: URL
let title: String
init(url: URL, title: String) {
self.url = url
self.title = title
}
func activityViewControllerPlaceholderItem(_ controller: UIActivityViewController) -> Any {
url
}
func activityViewController(_ controller: UIActivityViewController, itemForActivityType type: UIActivity.ActivityType?) -> Any? {
url
}
func activityViewController(_ controller: UIActivityViewController, subjectForActivityType type: UIActivity.ActivityType?) -> String {
title
}
}