// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT import MobileCoreServices import Photos import PhotosUI import SwiftRs import Tauri import UIKit import WebKit enum FilePickerEvent { case selected([URL]) case cancelled case error(String) } struct MessageDialogOptions: Decodable { let title: String? let message: String let okButtonLabel: String? let cancelButtonLabel: String? } struct Filter: Decodable { var extensions: [String]? } struct FilePickerOptions: Decodable { var multiple: Bool? var filters: [Filter]? } class DialogPlugin: Plugin { var filePickerController: FilePickerController! var pendingInvoke: Invoke? = nil var pendingInvokeArgs: FilePickerOptions? = nil override init() { super.init() filePickerController = FilePickerController(self) } @objc public func showFilePicker(_ invoke: Invoke) throws { let args = try invoke.parseArgs(FilePickerOptions.self) let parsedTypes = parseFiltersOption(args.filters ?? []) var isMedia = !parsedTypes.isEmpty var uniqueMimeType: Bool? = nil var mimeKind: String? = nil if !parsedTypes.isEmpty { uniqueMimeType = true for mime in parsedTypes { let kind = mime.components(separatedBy: "/")[0] if kind != "image" && kind != "video" { isMedia = false } if mimeKind == nil { mimeKind = kind } else if mimeKind != kind { uniqueMimeType = false } } } pendingInvoke = invoke pendingInvokeArgs = args if uniqueMimeType == true || isMedia { DispatchQueue.main.async { if #available(iOS 14, *) { var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1 if uniqueMimeType == true { if mimeKind == "image" { configuration.filter = .images } else if mimeKind == "video" { configuration.filter = .videos } } let picker = PHPickerViewController(configuration: configuration) picker.delegate = self.filePickerController picker.modalPresentationStyle = .fullScreen self.presentViewController(picker) } else { let picker = UIImagePickerController() picker.delegate = self.filePickerController if uniqueMimeType == true && mimeKind == "image" { picker.sourceType = .photoLibrary } picker.sourceType = .photoLibrary picker.modalPresentationStyle = .fullScreen self.presentViewController(picker) } } } else { let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes DispatchQueue.main.async { let picker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import) picker.delegate = self.filePickerController picker.allowsMultipleSelection = args.multiple ?? false picker.modalPresentationStyle = .fullScreen self.presentViewController(picker) } } } private func presentViewController(_ viewControllerToPresent: UIViewController) { self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil) } private func parseFiltersOption(_ filters: [Filter]) -> [String] { var parsedTypes: [String] = [] for filter in filters { for ext in filter.extensions ?? [] { guard let utType: String = UTTypeCreatePreferredIdentifierForTag( kUTTagClassMIMEType, ext as CFString, nil)?.takeRetainedValue() as String? else { continue } parsedTypes.append(utType) } } return parsedTypes } public func onFilePickerEvent(_ event: FilePickerEvent) { switch event { case .selected(let urls): pendingInvoke?.resolve(["files": urls]) case .cancelled: pendingInvoke?.resolve(["files": nil]) case .error(let error): pendingInvoke?.reject(error) } } @objc public func showMessageDialog(_ invoke: Invoke) throws { let manager = self.manager let args = try invoke.parseArgs(MessageDialogOptions.self) DispatchQueue.main.async { [] in let alert = UIAlertController( title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert) let cancelButtonLabel = args.cancelButtonLabel ?? "" if !cancelButtonLabel.isEmpty { alert.addAction( UIAlertAction( title: cancelButtonLabel, style: UIAlertAction.Style.default, handler: { (_) -> Void in Logger.error("cancel") invoke.resolve([ "value": false, "cancelled": false, ]) })) } let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "") if !okButtonLabel.isEmpty { alert.addAction( UIAlertAction( title: okButtonLabel, style: UIAlertAction.Style.default, handler: { (_) -> Void in Logger.error("ok") invoke.resolve([ "value": true, "cancelled": false, ]) })) } manager.viewController?.present(alert, animated: true, completion: nil) } } } @_cdecl("init_plugin_dialog") func initPlugin() -> Plugin { return DialogPlugin() }