fix(dialog): improve file type handling and media type checks

pull/2061/head
garyhai 8 months ago
parent 7f025e5240
commit 4bdfb1c2cd

@ -39,7 +39,6 @@ struct SaveFileDialogOptions: Decodable {
} }
class DialogPlugin: Plugin { class DialogPlugin: Plugin {
var filePickerController: FilePickerController! var filePickerController: FilePickerController!
var onFilePickerResult: ((FilePickerEvent) -> Void)? = nil var onFilePickerResult: ((FilePickerEvent) -> Void)? = nil
@ -52,50 +51,45 @@ class DialogPlugin: Plugin {
let args = try invoke.parseArgs(FilePickerOptions.self) let args = try invoke.parseArgs(FilePickerOptions.self)
let parsedTypes = parseFiltersOption(args.filters ?? []) let parsedTypes = parseFiltersOption(args.filters ?? [])
Logger.error("Parsed Types: %@", parsedTypes)
var isMedia = !parsedTypes.isEmpty
var uniqueMimeType: Bool? = nil var hasImage = false
var mimeKind: String? = nil var hasVideo = false
if !parsedTypes.isEmpty { var hasFile = false
uniqueMimeType = true
for mime in parsedTypes { for type in parsedTypes {
let kind = mime.components(separatedBy: "/")[0] let kind = kindOfMedia(type)
if kind != "image" && kind != "video" { if kind == "file" {
isMedia = false hasFile = true
} break
if mimeKind == nil { } else if kind == "image" {
mimeKind = kind hasImage = true
} else if mimeKind != kind { } else if kind == "video" {
uniqueMimeType = false hasVideo = true
}
} }
} }
onFilePickerResult = { (event: FilePickerEvent) -> Void in onFilePickerResult = { (event: FilePickerEvent) in
switch event { switch event {
case .selected(let urls): case let .selected(urls):
invoke.resolve(["files": urls]) invoke.resolve(["files": urls])
case .cancelled: case .cancelled:
invoke.resolve(["files": nil]) invoke.resolve(["files": nil])
case .error(let error): case let .error(error):
invoke.reject(error) invoke.reject(error)
} }
} }
if uniqueMimeType == true || isMedia { if !hasFile {
DispatchQueue.main.async { DispatchQueue.main.async {
if #available(iOS 14, *) { if #available(iOS 14, *) {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1 configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1
if hasImage && !hasVideo {
if uniqueMimeType == true { configuration.filter = .images
if mimeKind == "image" { } else if hasVideo && !hasImage {
configuration.filter = .images configuration.filter = .videos
} else if mimeKind == "video" {
configuration.filter = .videos
}
} }
let picker = PHPickerViewController(configuration: configuration) let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self.filePickerController picker.delegate = self.filePickerController
picker.modalPresentationStyle = .fullScreen picker.modalPresentationStyle = .fullScreen
@ -104,8 +98,12 @@ class DialogPlugin: Plugin {
let picker = UIImagePickerController() let picker = UIImagePickerController()
picker.delegate = self.filePickerController picker.delegate = self.filePickerController
if uniqueMimeType == true && mimeKind == "image" { if hasImage && hasVideo {
picker.sourceType = .photoLibrary picker.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String]
} else if hasImage {
picker.mediaTypes = [kUTTypeImage as String]
} else if hasVideo {
picker.mediaTypes = [kUTTypeMovie as String]
} }
picker.sourceType = .photoLibrary picker.sourceType = .photoLibrary
@ -146,13 +144,13 @@ class DialogPlugin: Plugin {
try "".write(to: srcPath, atomically: true, encoding: .utf8) try "".write(to: srcPath, atomically: true, encoding: .utf8)
} }
onFilePickerResult = { (event: FilePickerEvent) -> Void in onFilePickerResult = { (event: FilePickerEvent) in
switch event { switch event {
case .selected(let urls): case let .selected(urls):
invoke.resolve(["file": urls.first!]) invoke.resolve(["file": urls.first!])
case .cancelled: case .cancelled:
invoke.resolve(["file": nil]) invoke.resolve(["file": nil])
case .error(let error): case let .error(error):
invoke.reject(error) invoke.reject(error)
} }
} }
@ -169,27 +167,56 @@ class DialogPlugin: Plugin {
} }
private func presentViewController(_ viewControllerToPresent: UIViewController) { private func presentViewController(_ viewControllerToPresent: UIViewController) {
self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil) manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
} }
private func parseFiltersOption(_ filters: [Filter]) -> [String] { private func parseFiltersOption(_ filters: [Filter]) -> [String] {
var parsedTypes: [String] = [] var parsedTypes: [String] = []
for filter in filters { for filter in filters {
for ext in filter.extensions ?? [] { for ext in filter.extensions ?? [] {
guard if #available(iOS 14.0, *) {
let utType: String = UTTypeCreatePreferredIdentifierForTag( if let utType = UTType(filenameExtension: ext) {
kUTTagClassMIMEType, ext as CFString, nil)?.takeRetainedValue() as String? parsedTypes.append(utType.identifier)
else { }
continue } else {
if let unmanagedUTI = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension,
ext as CFString,
nil
) {
let uti = unmanagedUTI.takeRetainedValue() as String
parsedTypes.append(uti)
}
} }
parsedTypes.append(utType)
} }
} }
return parsedTypes return parsedTypes
} }
private func kindOfMedia(_ uti: String) -> String {
if #available(iOS 14.0, *) {
let utType = UTType(uti)
if utType?.conforms(to: .image) == true {
return "image"
} else if utType?.conforms(to: .movie) == true {
return "video"
} else if utType?.conforms(to: .video) == true {
return "video"
}
} else {
if UTTypeConformsTo(uti as CFString, kUTTypeImage) {
return "image"
} else if UTTypeConformsTo(uti as CFString, kUTTypeMovie) {
return "video"
} else if UTTypeConformsTo(uti as CFString, kUTTypeVideo) {
return "video"
}
}
return "file"
}
public func onFilePickerEvent(_ event: FilePickerEvent) { public func onFilePickerEvent(_ event: FilePickerEvent) {
self.onFilePickerResult?(event) onFilePickerResult?(event)
} }
@objc public func showMessageDialog(_ invoke: Invoke) throws { @objc public func showMessageDialog(_ invoke: Invoke) throws {
@ -198,21 +225,23 @@ class DialogPlugin: Plugin {
DispatchQueue.main.async { [] in DispatchQueue.main.async { [] in
let alert = UIAlertController( let alert = UIAlertController(
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert) title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert
)
let cancelButtonLabel = args.cancelButtonLabel ?? "" let cancelButtonLabel = args.cancelButtonLabel ?? ""
if !cancelButtonLabel.isEmpty { if !cancelButtonLabel.isEmpty {
alert.addAction( alert.addAction(
UIAlertAction( UIAlertAction(
title: cancelButtonLabel, style: UIAlertAction.Style.default, title: cancelButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in handler: { _ in
Logger.error("cancel") Logger.error("cancel")
invoke.resolve([ invoke.resolve([
"value": false, "value": false,
"cancelled": false, "cancelled": false,
]) ])
})) }
))
} }
let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "") let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "")
@ -220,14 +249,15 @@ class DialogPlugin: Plugin {
alert.addAction( alert.addAction(
UIAlertAction( UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default, title: okButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in handler: { _ in
Logger.error("ok") Logger.error("ok")
invoke.resolve([ invoke.resolve([
"value": true, "value": true,
"cancelled": false, "cancelled": false,
]) ])
})) }
))
} }
manager.viewController?.present(alert, animated: true, completion: nil) manager.viewController?.present(alert, animated: true, completion: nil)

@ -25,8 +25,8 @@ pub enum OpenResponse {
} }
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
pub struct DialogFilter { pub struct DialogFilter {
name: String, name: String,
extensions: Vec<String>, extensions: Vec<String>,

Loading…
Cancel
Save